When I unmarshal my JSON, the Warehouses instance is ok with however many warehouse instances are in it's list.
Each warehouse instance has the url field but the WarehouseField list has one instance with blank values.
I'm not sure what I'm missing.
JSON
{
"warehouses": [
{
"warehouse": {
"PRiyA": "0",
"WHID": "1 ALABO",
"PRixA": ""
},
"url": "http://ACL-HPDV6:8080/HSyncREST/api/v1/warehouses/PLL/1 ALABO"
},
{
"warehouse": {
"PRiyA": "0",
"WHID": "1000 EDWAR",
"PRixA": ""
},
"url": "http://ACL-HPDV6:8080/HSyncREST/api/v1/warehouses/PLL/1000 EDWAR"
},
],
"url": "http://ACL-HPDV6:8080/HSyncREST/api/v1/warehouses/PLL",
"status": " "
}
Code used to unmarshall
public static void main(String[] args) throws Exception {
Class<?>[] ctx = {Warehouses.class, Warehouse.class, WarehouseField.class};
JAXBContext jc = JAXBContext.newInstance(ctx);
Unmarshaller um = jc.createUnmarshaller();
um.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
um.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
Source json = new StreamSource(new File("D:/warehouses.json"));
Warehouses warehouses = um.unmarshal(json, Warehouses.class).getValue();
Model classes
public class Warehouses {
public List<Warehouse> warehouses;
public String url;
public String status;
<getters and setters>
}
public class Warehouse {
public List<WarehouseField> warehouse;
public String url;
<getters and setters>
}
public class WarehouseField {
#XmlAttribute
public String implName;
#XmlValue
public String value;
<getters and setters>
}
First of all I suggest you make all fields private, you have getters & setters for your fields.
It is a also a good idea to separate the response(?) DTO class name from the field and actual type naming.
Assuming that field names in the response DTOs tell the actual type, then do a bit refactoring like Warehouses to WarehousesResponse and Warehouse to WarehouseResponse.
Then about the "array", clip from the JSON:
"warehouse": {
"PRiyA": "0",
"WHID": "1 ALABO",
"PRixA": ""
}
this is not an array named warehouse so it not deserializing to a List nicely.
It is an Object of type Warehouse (that is why distinction WarehouseResponse, for clarity but see also mention about Map later) that is a field named warehouse in Object of type WarehouseResponse (assuming you agree on naming policy).
One option is to create a class like:
#Getter #Setter
public class Warehouse {
private String PRiyA;
private String WHID;
private String PRixA;
}
and change WarehouseResponse like:
#Getter #Setter
public class WarehouseResponse {
// Change the list to warehouse object as it is in response
// private List<WarehouseField> warehouse;
private Warehouse warehouse;
private String url;
private Date date = new Date();
}
Usually it is also possible to set key/value-pairs simply - for an example - to a Map<String,String> so in this case WarehouseResponses could also have private HashMap<String, String> warehouse and no class Warehouse would be needed. However I could not get it working with my Moxy knowledge.
So I presented how you can deserialize (and serialize) the format you gave in your JSON but I can not know it this then suits your possible XML needs
Related
I have a json string that I would like to map to my java object. I am currently using gson to do so. The problem is however, I have setup part of my POJO to contain an abstract class. How can I map the json that corresponds to this abstract class correctly?
To clarify:
Here is an example of a json string I am currently receiving:
{
"Items" : [
{
"id" : "ID1",
"seller_id": 17,
"item_plan": {
"action" : "Sell"
}
},
{
"id" : "ID2",
"seller_id": 27,
"item_plan": {
"action": "Remove",
}
}
]
}
My request object is setup like so:
public class RequestObject {
#SerializedName("Items")
#Expose
private List<Item> items = null;
public class Item {
#SerializedName("id")
#Expose
private String id;
#SerializedName("seller_id")
#Expose
private Integer sellerID;
#SerializedName("item_Plan")
#Expose
private ItemPlan item_plan;
public abstract class ItemPlan {
#SerializedName("action")
#Expose
private String action;
public abstract void executePlan()
As you can see, my request object has an abstract class that represents item_plan. The idea here is that item_plan actions will have their own way of execution and therefore have a parent class called ItemPlan where each child class would represent the possible action plans and their own executionPlan ie. (SellPlan is a child class of ItemPlan, where SellPlan has its own implementation of the function executionPlan()).
How can I map my example json string to the following Java classes?
I have tried the following:
RuntimeTypeAdapterFactory<ItemPlan> itemPlanRuntimeTypeAdapterFactory =
RuntimeTypeAdapterFactory
.of(ItemPlan.class, "action")
.registerSubtype(SellPlan.class, "Sell")
.registerSubtype(RemovePlan.class, "Remove");
Gson gson = new
GsonBuilder().registerTypeAdapterFactory(itemPlanRuntimeTypeAdapterFactory).create();
RequestObject request = gson.fromJson(jsonString, RequestObject.class);
This, however, does not work. It is able to map everything I need but it fails to create the correctly create the abstracted class objects ie. while it will create the corresponding child objects (SellPlan for Sell and RemovePlan for Remove), it will make the action string of those classes null. There is a workaround where I can simply set the action string manually in the constructor of these classes but I would rather not. Is there a way to fix this?
Thank you.
You probably have to use the RuntimeTypeAdapterFactory.of overload with the additional maintainType parameter for which you then pass true as value. Otherwise, as you have noticed, Gson removes the type field value during serialization, and therefore the field keeps its default value null.
I have a list of documents called customers that I retrieved using mongotemplate, bellow some of the documents:
{"customer": {"entityPerimeter": "abp", "name": "ZERZER", "siren": "6154645", "enterpriseId": "546456", "ic01": "", "marketingOffer": "qlksdjf", "irType": "Router", "offerSpecificationOfferLabel": "2Mb"}}
{"customer": {"entityPerimeter": "sdf", "name": "qazer", "siren": "156", "enterpriseId": "546456", "ic01": "", "marketingOffer": "qlksdjddddsqf", "irType": "Ruter", "offerSpecificationOfferLabel": "2Mb"}}
{"customer": {"entityPerimeter": "zer", "name": "fazdsdfsdgg", "siren": "sdfs", "enterpriseId": "1111", "ic01": "", "marketingOffer": "qsdfqsd", "irType": "Router", "offerSpecificationOfferLabel": "2Mb"}}
That what I did in mongodb to have this result:
public List<DBObject> findAllCustomersByExtractionDateMongo(LocalDate extractionDate) {
Aggregation aggregation = newAggregation(
match(Criteria.where(EXTRACTION_DATE).is(extractionDate)),
project(CUSTOMER).andExclude("_id"),
group().addToSet("$customer").as("distinct_customers"),
unwind("distinct_customers"),
project().andExclude("_id").and("distinct_customers").as("customer"),
project().andExclude("distinct_customers")
);
return template
.aggregate(aggregation, COLLECTION, DBObject.class)
.getMappedResults();
}
Now what I really want is to map those Documents to a Class called Customer:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Customer {
private String entityPerimeter;
private String name;
private String siren;
private String enterpriseId;
private String ic01;
private String marketingOffer;
private String product;
private String irType;
}
I tried to do that by creating a DTO interface:
public interface DocumentToCustomerMapper {
String NULL = "null";
static Customer getFilter(DBObject document) {
var customer = new Customer();
customer.setSiren(Optional.ofNullable((String) document.get(CustomerAttributes.SIREN.value())).orElse(NULL));
customer.setEnterpriseId(Optional.ofNullable((String) document.get(CustomerAttributes.ENTERPRISE_ID.value())).orElse(NULL));
customer.setEntityPerimeter(Optional.ofNullable((String) document.get(CustomerAttributes.ENTITY_PERIMETER.value())).orElse(NULL));
customer.setName(Optional.ofNullable((String) document.get(CustomerAttributes.NAME.value())).orElse(NULL));
customer.setIc01(Optional.ofNullable((String) document.get(CustomerAttributes.IC_01.value())).orElse(NULL));
customer.setMarketingOffer(Optional.ofNullable((String) document.get(CustomerAttributes.MARKETING_OFFER.value())).orElse(NULL));
customer.setProduct(Optional.ofNullable((String) document.get(CustomerAttributes.PRODUCT.value())).orElse(NULL));
customer.setIrType(Optional.ofNullable((String) document.get(CustomerAttributes.IR_TYPE.value())).orElse(NULL));
return customer;
}
}
Then in the findAllCystomersByExtractionDateMongo() I'm doing this:
public List<Customer> findAllCustomersByExtractionDateMongo(LocalDate extractionDate) {
Aggregation aggregation = newAggregation(
match(Criteria.where(EXTRACTION_DATE).is(extractionDate)),
project(CUSTOMER).andExclude("_id"),
group().addToSet("$customer").as("distinct_customers"),
unwind("distinct_customers"),
project().andExclude("_id").and("distinct_customers").as("customer"),
project().andExclude("distinct_customers")
);
final Converter<DBObject, Customer> converter = DocumentToCustomerMapper::getFilter;
MongoCustomConversions cc = new MongoCustomConversions(List.of(converter));
((MappingMongoConverter) template.getConverter()).setCustomConversions(cc);
return template
.aggregate(aggregation, COLLECTION, Customer.class)
.getMappedResults();
}
But unfortunately it's giving me an exception:
Couldn't resolve type arguments for class com.obs.dqsc.api.repository.mongo_template.CustomerRepositoryImpl$$Lambda$1333/0x00000008012869a8!
I tried to remove this code:
final Converter<DBObject, Customer> converter = DocumentToCustomerMapper::getFilter;
MongoCustomConversions cc = new MongoCustomConversions(List.of(converter));
((MappingMongoConverter) template.getConverter()).setCustomConversions(cc);
Then all what I'm getting is some null values in my customer objects:
Customer(entityPerimeter=null, name=null, siren=null, enterpriseId=null, ic01=null, marketingOffer=null, product=null, irType=null)
Customer(entityPerimeter=null, name=null, siren=null, enterpriseId=null, ic01=null, marketingOffer=null, product=null, irType=null)
Customer(entityPerimeter=null, name=null, siren=null, enterpriseId=null, ic01=null, marketingOffer=null, product=null, irType=null)
Note: for performance issues, I don't want to do any mapping in the java side, also I don't want to use a global converter in my mongo configuration.
The problem is that you are using a method reference to express your converter:
final Converter<DBObject, Customer> converter = DocumentToCustomerMapper::getFilter;
(Expanding the method reference to a lambda won't work either.)
Try rewriting that snippet to something else (such as an anonymous inner class).
Here is a very similar issue reported, including info on how to work around this problem: https://github.com/arangodb/spring-data/issues/120
To explain my issue, let's say that I'm retrieving the following OData V2 Entity:
{
"d": {
"EmployeeID": 1,
"LastName": "Davolio",
"FirstName": "Nancy",
"Orders": {
"results": [
{
"OrderID": 10258
}
]
},
"Territories": {
"results": [
{
"TerritoryID": "06897"
}
]
}
}
}
And I have the corresponding model Class:
#JsonRootName(value = "d")
public class Employee {
#JsonProperty("EmployeeID")
private int employeeId;
#JsonProperty("LastName")
private String lastName;
#JsonProperty("FirstName")
private String firstName;
#JsonProperty("Orders")
private List<Order> orders;
#JsonProperty("Territories")
private List<Territory> territories;
...
}
As expected the exception com.fasterxml.jackson.databind.exc.MismatchedInputException is being triggered because the "Orders" and "Territories" collections are actually within the property "results", as the OData V2 states.
Do you guys have any idea how to ignore the nested "results" property and get the lists straight away?
Is a custom deserializer or wrapper class really needed in this case?
Thanks!
There are always multiple ways to tackle this problem.
One is to create a wrapper class and have Employee Directly reference it.
For example:
public class WrapperDto<T> implements Serializable {
#JsonProperty("results")
private List<T> elements;
}
public class Employee {
...
#JsonProperty("Orders")
private WrapperDto<Order> orders;
#JsonProperty("Territories")
private WrapperDto<Territory> territories;
...
}
when you want to get the orders, you have to call orders.getResults() to get the List
another solution can be found here where you have a custom wrapper:
How to map a nested value to a property using Jackson annotations?
In response to api call, i'm sending Json Class Object as response.
I need response like this without empty objects being removed.
{
"links": {
"products": [],
"packages": []
},
"embedded":{
"products": [],
"packages": []
}
}
but final Response is looking like this
{
"links": {},
"embedded": {}
}
Two things to be aware of:
null and empty are different things.
AFAIK Jackson is configured to serialize properties with null values by default.
Make sure to properly initialize your properties in your object. For example:
class Dto {
private Link link;
private Embedded embedded;
//constructor, getters and setters...
}
class Link {
//by default these will be empty instead of null
private List<Product> products = new ArrayList<>();
private List<Package> packages = new ArrayList<>();
//constructor, getters and setters...
}
Make sure your classes are not extending another class with this annotation #JsonInclude(JsonInclude.Include.NON_NULL). Example:
//It tells Jackson to exclude any property with null values from being serialized
#JsonInclude(JsonInclude.Include.NON_NULL)
class BaseClass {
}
//Any property with null value will follow the rules stated in BaseClass
class Dto extends BaseClass {
private Link link;
private Embedded embedded;
//constructor, getters and setters...
}
class Link extends BaseClass {
/* rest of the design */
}
If you have the latter and you cannot edit BaseClass then you can define different rules in the specific classes:
class Link extends BaseClass{
//no matter what rules are defined elsewhere, this field will be serialized
#JsonInclude(JsonInclude.Include.ALWAYS)
private List<Product> products;
//same here
#JsonInclude(JsonInclude.Include.ALWAYS)
private List<Package> packages;
//constructor, getters and setters...
}
I'm kind of new in jackson subject and I did not find any answer which would help me resolve the problem.
For a sec let's assume that I have this class:
public class Airport {
private String name;
private String code;
...
}
My json looks like this:
"XXX": {
"name": "SomeName",
}
I would like to force Jackson to put XXX (root of tree) into code property from the class. Standard way I use to create objects from JSON is using treeToValue:
ObjectMapper mapper = new ObjectMapper();
String airports = "above Json";
JsonNode airportsTree = mapper.readTree(airports.toString());
Airport airport = mapper.treeToValue(airportsTree, Airport.class);
However when I enable DeserializationFeature.UNWRAP_ROOT_VALUE I'm getting
JsonMappingException: Root name 'XXX' does not match expected ('JsonNode') for type [simple type, class com.fasterxml.jackson.databind.JsonNode]
You need put root name hint for jackson
#JsonRootName(value = "XXX")
public class Airport {
private String name;
private String code;
...
}
When you enable DeserializationFeature.UNWRAP_ROOT_VALUE it must works