Jackson Returns empty object - java

I'm new to Jackson. I've tried to parse Json string to an object but jackson returns an object with all null values. Here is code of my parser:
ObjectMapper mapper = new ObjectMapper();
FullTextRetrievalResponse object =
mapper.readValue(response.getBody().getObject().toString(),
FullTextRetrievalResponse.class);
Here is my FullTextRetrievalResponse class:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"coredata",
"scopus-id",
"scopus-eid",
"link",
"originalText"
})
public class FullTextRetrievalResponse {
#JsonProperty("coredata")
private Coredata coredata;
#JsonProperty("scopus-id")
private String scopusId;
#JsonProperty("scopus-eid")
private String scopusEid;
#JsonProperty("link")
private Link_ link;
#JsonProperty("originalText")
private OriginalText originalText;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("coredata")
public Coredata getCoredata() {
return coredata;
}
#JsonProperty("coredata")
public void setCoredata(Coredata coredata) {
this.coredata = coredata;
}
#JsonProperty("scopus-id")
public String getScopusId() {
return scopusId;
}
#JsonProperty("scopus-id")
public void setScopusId(String scopusId) {
this.scopusId = scopusId;
}
#JsonProperty("scopus-eid")
public String getScopusEid() {
return scopusEid;
}
#JsonProperty("scopus-eid")
public void setScopusEid(String scopusEid) {
this.scopusEid = scopusEid;
}
#JsonProperty("link")
public Link_ getLink() {
return link;
}
#JsonProperty("link")
public void setLink(Link_ link) {
this.link = link;
}
#JsonProperty("originalText")
public OriginalText getOriginalText() {
return originalText;
}
#JsonProperty("originalText")
public void setOriginalText(OriginalText originalText) {
this.originalText = originalText;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
and here is part of JSON:
{
"full-text-retrieval-response": {
"coredata": {
"prism:url": "http://api.elsevier.com/content/article/pii/S1751157716302140",
"dc:identifier": "doi:10.1016/j.joi.2016.11.002",
"eid": "1-s2.0-S1751157716302140",
"prism:doi": "10.1016/j.joi.2016.11.002",
"pii": "S1751-1577(16)30214-0",
"dc:title": "The specific shapes of gender imbalance in scientific authorships: A network approach ",
"prism:publicationName": "Journal of Informetrics",
"prism:aggregationType": "Journal",
"prism:issn": "17511577",
"prism:coverDate": "2017-02-28",
"prism:coverDisplayDate": "February 2017",
"openaccess": "0",
"openaccessArticle": false,
"openaccessType": null,
"openArchiveArticle": false,
"openaccessSponsorName": null,
"openaccessSponsorType": null,
"openaccessUserLicense": null,
"link": [
{
"#rel": "self",
"#href": "http://api.elsevier.com/content/article/pii/S1751157716302140",
"#_fa": "true"
},
{
"#rel": "scidir",
"#href": "http://www.sciencedirect.com/science/article/pii/S1751157716302140",
"#_fa": "true"
}
]
}
}
}

The issue is that in your json object you have the field full-text-retrieval-response wrapping all your object, but in your java classes, the FullTextRetrievalResponse is the root.
I think you have 3 options
Change the json structure (supposing you can do that), removing the full-text-retrieval-response label (https://pastebin.com/MtxXSeDW)
Create a new class having an instance of FullTextRetrievalResponse as a json property:
public class FullTextRetrievalResponseWrapper {
#JsonProperty("full-text-retrieval-response")
private FullTextRetrievalResponse fullTextRetrievalResponse;
//setters and getters
}
And then make the serialization using this new class: mapper.readValue(response.getBody().getObject().toString(),
FullTextRetrievalResponseWrapper .class);
Create a custon json deserializer (http://www.baeldung.com/jackson-deserialization) to convert yourself the json object to your class.
Just another quick tip: if you are defining a field as a json property (#JsonProperty), you do not need to define the #JsonSetter, #JsonGetter or even the #JsonProperty in the setters and getters.

Related

Creating complex JSON payload from Java Pojo Jackson

I want to create below JSON payload
{
"maxResults":3,
"counter":0,
"customerParameters":{
"filters":[
{
"name":"customerId",
"operator":"=",
"value":["hello"]
}
]
},
"dealerParameters":[
{
"name":"club"
},
{
"name":"token"
}
]
}
Coded so far:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"maxResults",
"counter",
"customerParameters",
"dealerParameters"
})
public class CustomerModel {
#JsonProperty("maxResults")
private Integer maxResults;
#JsonProperty("counter")
private Integer counter;
#JsonProperty("customerParameters")
private CustomerParameters customerParameters;
#JsonProperty("dealerParameters")
private List<DealerParameter> dealerParameters = null;
#JsonProperty("customerParameters")
public CustomerParameters getCustomerParameters() {
return customerParameters;
}
#JsonProperty("customerParameters")
public void setCustomerParameters(CustomerParameters customerParameters) {
this.customerParameters = customerParameters;
}
#JsonProperty("dealerParameters")
public List<DealerParameter> getDealerParameters() {
return dealerParameters;
}
#JsonProperty("dealerParameters")
public void setDealerParameters(List<DealerParameter> dealerParameters) {
this.dealerParameters = dealerParameters;
}
// Getter/Setter for other params
}
CustomerParameters.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"filters"
})
public class CustomerParameters {
#JsonProperty("filters")
private List<Filter> filters = null;
// Setter and Getter for filters parameter
}
DealerParameters.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"name"
})
public class DealerParameter {
#JsonProperty("name")
private String name;
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("name")
public void setName(String name) {
this.name = name;
}
}
Filter.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"name",
"operator",
"value"
})
public class Filter {
#JsonProperty("name")
private String name;
#JsonProperty("operator")
private String operator;
#JsonProperty("value")
private List<String> value = null;
#JsonProperty("value")
public List<String> getValue() {
return value;
}
#JsonProperty("value")
public void setValue(List<String> value) {
this.value = value;
}
// Setter and Getter for other properties
}
Missing Part:
#Controller
public class TestContoller {
RestTemplate restTemplate;
Should I instantiate each pojo class with new operator as below and set all required parameters ? or any other approach of creating JSON payload?
CustomerModel customerModel= new CustomerModel();
customerModel.setMaxResults(1);
Filter filter= new Filter();
filter.setName("customerID");
filter.setOperator("-");
filter.setValue(Arrays.asList("club"));
CustomerParameters customerParameters = new CustomerParameters();
customerParameters.setFilters(Arrays.asList(filter));
customerModel.setCustomerParameters(customerParameters);
For DealerParameter class, I want to create multiple objects with same key different value(see the json payload I mentioned above). Below code creates only one object "name":"dealerId"
DealerParameter dealerParameter = new DealerParameter();
dealerParameter.setName("dealerId");
customerModel.setDealerParameters(dealerParameter);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValueAsString(customerModel);
restTemplate.exchange(todo); // restful service call
}
you are already using "ObjectMapper", And ObjectMapper has readValue() method. By using readValue() method you can populate all data at a time like below:--
ObjectMapper objectMapper = new ObjectMapper();
//populating data from json string to POJO
CustomerModel customerModel = objectMapper.readValue(<json String>,CustomerModel.class);
System.out.println(objectMapper.writeValueAsString(customerModel); // print all data

json to java Object using jackson

Hi i want to convert this json to json object in java so that i can pass it to http request to call an api
{
"aliasNaming": true,
"dataServiceType": "BROWSE",
"deviceName": "MyDevice",
"langPref": " ",
"maxPageSize": "2000",
"outputType": "VERSION1",
"password": "!jshjhsdhshdj",
"query": {
"autoClear": true,
"autoFind": true,
"condition": [
{
"controlId": "F4211.CO",
"operator": "EQUAL",
"value": [
{
"content": "00098",
"specialValueId": "LITERAL"
}
]
},
{
"controlId": "F4211.DCTO",
"operator": "EQUAL",
"value": [
{
"content": "SM",
"specialValueId": "LITERAL"
}
]
},
{
"controlId": "F4211.UPMJ",
"operator": "GREATER_EQUAL",
"value": [
{
"content": "01/01/17",
"specialValueId": "LITERAL"
}
]
}
],
"matchType": "MATCH_ALL"
},
"returnControlIDs": "F4211.DOCO|F4211.TRDJ|F4211.CRCD|F4211.AN8|F4211.DSC2|F4211.DSC1|F4211.LITM|F4211.LOTN|F4211.UORG|F4211.UPRC|F4211.AEXP",
"targetName": "F4211",
"targetType": "table",
"token": "044biPNadxNVGhyAKdrImoniK98OOa2l86ZA63qCr4gE5o=MDIwMDA4LTIyNDU5MjUxMTY2MzY3NTA3MTRNeURldmljZTE1Mzc0MjYwMjAyNTk=",
"username": "Ali"
}
i have created 4 models using http://www.jsonschema2pojo.org.
those models just have getter setter in it. look something like this
#JsonProperty("aliasNaming")
private Boolean aliasNaming;
#JsonProperty("dataServiceType")
private String dataServiceType;
#JsonProperty("deviceName")
private String deviceName;
#JsonProperty("langPref")
private String langPref;
#JsonProperty("maxPageSize")
private String maxPageSize;
#JsonProperty("outputType")
private String outputType;
#JsonProperty("password")
private String password;
#JsonProperty("query")
private Query query;
#JsonProperty("returnControlIDs")
private String returnControlIDs;
#JsonProperty("targetName")
private String targetName;
#JsonProperty("targetType")
private String targetType;
#JsonProperty("token")
private String token;
#JsonProperty("username")
private String username;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("aliasNaming")
public Boolean getAliasNaming() {
return aliasNaming;
}
#JsonProperty("aliasNaming")
public void setAliasNaming(Boolean aliasNaming) {
this.aliasNaming = aliasNaming;
}
#JsonProperty("dataServiceType")
public String getDataServiceType() {
return dataServiceType;
}
#JsonProperty("dataServiceType")
public void setDataServiceType(String dataServiceType) {
this.dataServiceType = dataServiceType;
}
#JsonProperty("deviceName")
public String getDeviceName() {
return deviceName;
}
#JsonProperty("deviceName")
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
#JsonProperty("langPref")
public String getLangPref() {
return langPref;
}
#JsonProperty("langPref")
public void setLangPref(String langPref) {
this.langPref = langPref;
}
#JsonProperty("maxPageSize")
public String getMaxPageSize() {
return maxPageSize;
}
#JsonProperty("maxPageSize")
public void setMaxPageSize(String maxPageSize) {
this.maxPageSize = maxPageSize;
}
#JsonProperty("outputType")
public String getOutputType() {
return outputType;
}
#JsonProperty("outputType")
public void setOutputType(String outputType) {
this.outputType = outputType;
}
#JsonProperty("password")
public String getPassword() {
return password;
}
#JsonProperty("password")
public void setPassword(String password) {
this.password = password;
}
#JsonProperty("query")
public Query getQuery() {
return query;
}
#JsonProperty("query")
public void setQuery(Query query) {
this.query = query;
}
#JsonProperty("returnControlIDs")
public String getReturnControlIDs() {
return returnControlIDs;
}
#JsonProperty("returnControlIDs")
public void setReturnControlIDs(String returnControlIDs) {
this.returnControlIDs = returnControlIDs;
}
#JsonProperty("targetName")
public String getTargetName() {
return targetName;
}
#JsonProperty("targetName")
public void setTargetName(String targetName) {
this.targetName = targetName;
}
#JsonProperty("targetType")
public String getTargetType() {
return targetType;
}
#JsonProperty("targetType")
public void setTargetType(String targetType) {
this.targetType = targetType;
}
#JsonProperty("token")
public String getToken() {
return token;
}
#JsonProperty("token")
public void setToken(String token) {
this.token = token;
}
#JsonProperty("username")
public String getUsername() {
return username;
}
#JsonProperty("username")
public void setUsername(String username) {
this.username = username;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
Now i want to set the values in these models by creating their respective objects and finally i got one main object with all the data. like this
Value Vobj1 = new Value();
Vobj1.setContent("00098");
Vobj1.setSpecialValueId("LITERAL");
List<Value> valueList1= new ArrayList<Value>();
valueList1.add(Vobj1);
Value Vobj2 = new Value();
Vobj2.setContent("SM");
Vobj2.setSpecialValueId("LITERAL");
List<Value> valueList2= new ArrayList<Value>();
valueList2.add(Vobj2);
Value Vobj3 = new Value();
Vobj3.setContent("01/01/17");
Vobj3.setSpecialValueId("LITERAL");
List<Value> valueList3= new ArrayList<Value>();
valueList3.add(Vobj3);
Condition Cobj1 = new Condition();
Cobj1.setControlId("F4211.CO");
Cobj1.setOperator("EQUAL");
Cobj1.setValue(valueList1);
Condition Cobj2 = new Condition();
Cobj2.setControlId("F4211.DCTO");
Cobj2.setOperator("EQUAL");
Cobj2.setValue(valueList1);
Condition Cobj3 = new Condition();
Cobj3.setControlId("F4211.UPMJ");
Cobj3.setOperator("GREATER_EQUAL");
Cobj3.setValue(valueList1);
List<Condition> conditionList1 = new ArrayList<Condition>();
conditionList1.add(Cobj1);
conditionList1.add(Cobj2);
conditionList1.add(Cobj3);
Query Qobj1= new Query();
Qobj1.setAutoClear(true);
Qobj1.setAutoFind(true);
Qobj1.setCondition(conditionList1);
Qobj1.setMatchType("MATCH_ALL");
JSONStructure obj=new JSONStructure();
obj.setAliasNaming(true);
obj.setDataServiceType("BROWSE");
obj.setDeviceName("MyDevice");
obj.setLangPref(" ");
obj.setMaxPageSize("2000");
obj.setOutputType("VERSION1");
obj.setPassword("!J0g3t6000");
obj.setQuery(Qobj1);
obj.setReturnControlIDs("F4211.DOCO|F4211.TRDJ|F4211.CRCD|F4211.AN8|F4211.DSC2|F4211.DSC1|F4211.LITM|F4211.LOTN|F4211.UORG|F4211.UPRC|F4211.AEXP");
obj.setTargetName("F4211");
obj.setTargetType("table");
obj.setToken(Token);
obj.setUsername("JOGET");
Now obj is my final object that i am going to pass to an http request and call the api and get the data from it. i want to make sure that my json is created correct, how am i suppose to print the all the data inside this object? and am i going correct with this approach?
if you use maven put gson into your pom.xml
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
then print your object like this
System.out.println(new Gson().toJson(yourObj));
your object will print in json
I found two full working examples that is familiar with your case.
1) Using Gson refer to the tutorial Parse json string and java object into Gson tree model
2) Using Jackson refer to the tutorial Convert Java Object to/from JSON using JACKSON API
Hope this help.

Java Jackson deserialize an object containing a list of object with/without custom Deserializer?

I've got a JSON input like this
{
"slices": [{
"slice": {
"boundedBy": {
"Envelope": {
"axisLabels": "Lat Long ansi",
"lowerCorner": "-44.975 111.975 \"2003-01-01T00:00:00+00:00\"",
"upperCorner": "-8.975 155.975 \"2003-01-01T00:00:00+00:00\"",
"srsDimension": 3
}
},
"fileReferenceHistory": "/home/rasdaman/rasdaman_community/rasdaman/systemtest/testcases_services/test_all_wcst_import/testdata/wcs_local_metadata_tiff_no_specify_bands/GlobLAI-20030101-20030110-H01V06-1.0_MERIS-FR-LAI-HA.tiff",
"local_metadata_key": "value_1"
}
},
{
"slice": {
"boundedBy": {
"Envelope": {
"axisLabels": "Lat Long ansi",
"lowerCorner": "-44.975 111.975 \"2003-10-01T00:00:00+00:00\"",
"upperCorner": "-8.975 155.975 \"2003-10-01T00:00:00+00:00\"",
"srsDimension": 3
}
},
"fileReferenceHistory": "/home/rasdaman/rasdaman_community/rasdaman/systemtest/testcases_services/test_all_wcst_import/testdata/wcs_local_metadata_tiff_no_specify_bands/GlobLAI-20031001-20031010-H00V10-1.0_MERIS-FR-LAI-HA.tiff",
"local_metadata_key": "value_2"
}
}
],
"Title": "Drought code",
// other keys:values
}
with "slices" is an array of "slice" objects. Out of "slices" is any "keys":"values" but it is not the problem.
Then, I have a POJO class
public class CoverageMetadata {
#JsonProperty(value = "slices")
#JacksonXmlElementWrapper(useWrapping = false)
private List<LocalMetadata> localMetadataList;
private Map<String, String> globalMetadataAttributesMap;
#JsonAnySetter
public void addKeyValue(String key, String value) {
this.globalMetadataAttributesMap.put(key, value);
}
#JsonAnyGetter
public Map<String, String> getGlobalAttributesMap() {
return globalMetadataAttributesMap;
}
// other gettters, setters without Jackson annotations
}
and a class inside the list:
public class LocalMetadata {
public static final String LOCAL_METADATA_TAG = "slice";
private Map<String, String> localMetadataAttributesMap;
private BoundedBy boundedBy;
#JsonAnySetter
// NOTE: To map an unknown list of properties, must use this annotation
public void addKeyValue(String key, String value) {
this.localMetadataAttributesMap.put(key, value);
}
public LocalMetadata() {
this.localMetadataAttributesMap = new LinkedHashMap<>();
this.boundedBy = new BoundedBy();
}
#JsonAnyGetter
// NOTE: to unwrap the "map" from { "map": { "key": "value" } }, only keep { "key": "value" }
public Map<String, String> getLocalMetadataAttributesMap() {
return localMetadataAttributesMap;
}
public BoundedBy getBoundedBy() {
return this.boundedBy;
}
public void setBoundedBy(BoundedBy boundedBy) {
this.boundedBy = boundedBy;
}
public LocalMetadata(Map<String, String> localMetadataAttributesMap, BoundedBy boundedBy) {
this.localMetadataAttributesMap = localMetadataAttributesMap;
this.boundedBy = boundedBy;
}
}
And the basic code to deserialize JSON to object
ObjectMapper objectMapper = new ObjectMapper();
CoveageMetadata coverageMetadata = objectMapper.readValue(metadata, CoverageMetadata.class);
When I try to deserialize the JSON input to CoverageMetadata object, I got the error
Cannot deserialize coverage's metadata in XML/JSON by Jackson, error: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: {"slices":[{"slice":{"boundedBy":{"Envelope":{"axisLabels":"Lat Long ansi","srsDimension":3,"lowerCorner":"-44.975 111.975 \"2003-01-01T00:00:00+00:00\"","upperCorner":"-8.975 155.975 \"2003-01-01T00:00:00+00:00\""}},"local_metadata_key":"value_1","fileReferenceHistory":"/home/rasdaman/rasdaman_community/rasdaman/systemtest/testcases_services/test_all_wcst_import/testdata/wcs_local_metadata_tiff_no_specify_bands/GlobLAI-20030101-20030110-H01V06-1.0_MERIS-FR-LAI-HA.tiff"}}],"Title":"Drought code"}; line: 1, column: 21] (through reference chain: petascope.core.gml.metadata.model.CoverageMetadata["slices"]->java.util.ArrayList[0]->petascope.core.gml.metadata.model.LocalMetadata["slice"]).
How can I deserialize this JSON input String to CoverageMetadataObject with each "slice" element will be mapped to a LocalMetadata object?
The simple answer is I create another POJO class to hold the "slices" list, in CoverageMetadata class, it will have
public class CoverageMetadata {
private Map<String, String> globalMetadataAttributesMap;
#JsonProperty(value = "slices")
private LocalMetadata localMetadata;
...
}
New POJO class (class LocalMetadata before was renamed to LocalMetadataChild)
public class LocalMetadata {
#JsonProperty(value = "slice")
// This is the most important thing to avoid duplicate <slices><slices> when serializing in XML.
#JacksonXmlElementWrapper(useWrapping = false)
private List<LocalMetadataChild> localMetadataList;
public LocalMetadata(List<LocalMetadataChild> localMetadataList) {
this.localMetadataList = localMetadataList;
}
public LocalMetadata() {
this.localMetadataList = new ArrayList<>();
}
public List<LocalMetadataChild> getLocalMetadataList() {
return localMetadataList;
}
public void setLocalMetadataList(List<LocalMetadataChild> localMetadataList) {
this.localMetadataList = localMetadataList;
}
}

Working with multiple JSON objects in the same file with unknown key names using Jackson

Working on building the model for an application dealing with physical buildings.
Ideally, we'd want something like this:
City has multiple Offices, which have multiple Rooms, which have properties.
We're using jackson to parse the JSON payload received from the API datasource, and it ends up looking a bit differently than the examples I've seen.
The format we're getting is:
{
"CityName1":
{ "OfficeName1":
[
{"name": RoomName1, "RoomProperty2": RoomValue1},
{"name": RoomName2, "RoomProperty2": RoomValue2}
]
},
{ "OfficeName2": [{...}]},
{ "OfficeNameX" : [{...}] },
"CityName2": {...},
"CityNameN": {...}}
Java classes:
public class City {
private Map<String, Object> additionalProperties = new HashMap<String, Object();
private List<Office> _offices = new ArrayList<Office>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value)
throws IOException {
_cityName = name;
String officeJson = _mapper.writeValueAsString(value);
StringBuilder sb = new StringBuilder(officeJson);
_offices.add(_mapper.readValue(officeJson, Office.class));
this.additionalProperties.put(name, value);
}
}
public class Office {
private String _officeName;
private static final ObjectMapper _mapper = new ObjectMapper();
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
private List<Room> _rooms = new ArrayList<Room>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value)
throws IOException {
_officeName = name;
String roomJson = _mapper.writeValueAsString(value);
Room[] rooms = _mapper.readValue(roomJson, Room[].class);
_rooms.addAll(Arrays.asList(rooms));
this.additionalProperties.put(name, value);
}
public List<Room> getRooms() {
return _rooms;
}
public void setRooms(List<Room> rooms) {
_rooms = rooms;
}
}
public class Room {
private static final String NAME = "name";
private static final String PROP_2 = "RoomProperty2";
#JsonProperty(PROP_2)
private String _propertyTwo;
#JsonProperty(NAME)
private String name;
#JsonProperty(PROP_2)
public String getPropertyTwo() {
return _propertyTwo;
}
#JsonProperty(PROP_2)
public void setPropertyTwo(String propTwo) {
_propertyTwo = propTwo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
So how would I go about parsing this with jackson ? Currently, I am using an #JsonAnySetter to grab the name, and saving that as the city or office name and then sending the value sent to JsonAnySetter to the appropriate nested class. The real issue comes with getting a list of Offices in the City. When using a mapper.readvalues(String, Office.class), I get returned an iterator of only the last office for each city. Any ideas guys?
Sorry if that seemed confusing! Would love to answer any questions I've created.
Thanks for the help!
I think the best solution is to write your own deserialiser here since your JSON document doesn't really map well to the class structure you want.
The solution below reads each city as a Map<String, List<Room>> and the collection of cities as a Map<String, City> and then create City and Office objects from these inside the deserialisers.
Room.java is the same as yours, here are the rest:
Cities.java:
#JsonDeserialize(using=CitiesDeserializer.class)
public class Cities implements Iterable<City> {
private final List<City> cities;
public Cities(final List<City> cities) {
this.cities = cities;
}
public Cities() {
this.cities = new ArrayList<>();
}
public List<City> getCities() {
return cities;
}
#Override
public Iterator<City> iterator() {
return cities.iterator();
}
}
CitiesDeserialiser.java:
public class CitiesDeserializer extends JsonDeserializer<Cities> {
private static final TypeReference<Map<String, City>> TYPE_REFERENCE = new TypeReference<Map<String, City>>() {};
#Override
public Cities deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
final Map<String, City> map = jp.readValueAs(TYPE_REFERENCE);
List<City> cities = new ArrayList<>();
for(Map.Entry<String, City> entry : map.entrySet()) {
City city = entry.getValue();
city.setName(entry.getKey());
cities.add(city);
}
return new Cities(cities);
}
}
City.java:
#JsonDeserialize(using=CityDeserialzer.class)
public class City {
private String name;
private List<Office> offices;
// Setters and getters
}
CityDeserializer.java:
public class CityDeserialzer extends JsonDeserializer<City> {
private static final TypeReference<Map<String, List<Room>>> TYPE_REFERENCE = new TypeReference<Map<String, List<Room>>>() {};
#Override
public City deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
final Map<String, List<Room>> map = jp.readValueAs(TYPE_REFERENCE);
List<Office> offices = new ArrayList<>();
for(Map.Entry<String, List<Room>> entry : map.entrySet()) {
Office office = new Office();
office.setName(entry.getKey());
office.setRooms(entry.getValue());
offices.add(office);
}
City city = new City();
city.setOffices(offices);
return city;
}
}
Office.java:
public class Office {
private String name;
private List<Room> rooms;
// Setters and getters
}
And here's a test to show that it works:
JSON:
{
"CityName1": {
"OfficeName1": [ {
"name": "RoomName1",
"RoomProperty2": "RoomValue1"
}, {
"name": "RoomName2",
"RoomProperty2": "RoomValue2"
} ],
"OfficeName2": [ {
"name": "RoomName3",
"RoomProperty2": "RoomValue3"
}, {
"name": "RoomName4",
"RoomProperty2": "RoomValue4"
} ]
},
"CityName2": {
"OfficeName3": [ {
"name": "RoomName5",
"RoomProperty2": "RoomValue5"
}, {
"name": "RoomName6",
"RoomProperty2": "RoomValue6"
} ],
"OfficeName4": [ {
"name": "RoomName7",
"RoomProperty2": "RoomValue7"
}, {
"name": "RoomName8",
"RoomProperty2": "RoomValue8"
} ]
}
}
Test.java:
public class Test {
public static void main(String[] args) {
String json = ...
ObjectMapper mapper = new ObjectMapper();
Cities cities = mapper.readValue(json, Cities.class);
for(City city : cities) {
System.out.println(city.getName());
for(Office office : city.getOffices()) {
System.out.println("\t" + office.getName());
for(Room room : office.getRooms()) {
System.out.println("\t\t" + room.getName());
System.out.println("\t\t\t" + room.getPropertyTwo());
}
}
}
}
}
Output:
CityName1
OfficeName1
RoomName1
RoomValue1
RoomName2
RoomValue2
OfficeName2
RoomName3
RoomValue3
RoomName4
RoomValue4
CityName2
OfficeName3
RoomName5
RoomValue5
RoomName6
RoomValue6
OfficeName4
RoomName7
RoomValue7
RoomName8
RoomValue8

JSON data binding with custom logic using Jackson

I have defined JSON response which I want to deserialize into Java Objects. I managed to do it "manually" with the Tree Model but if possible I would like to use Data Binding instead. The problem is that I need some custom logic for some parts.
The JSON looks like this:
{
"resourcedescriptions": [
{
"path": "somePath",
"tag_pagetype": "default",
"tag_bookingcenter": [
"bc_ch",
"bc_de"
],
"resources": [
{
"path": "somePathDe.html",
"lang": "de",
"lastmodified": 1399020442914,
"mimetype": "text/html"
},
{
"path": "somePathEn.html",
"lang": "en",
"lastmodified": 1399907224208,
"mimetype": "text/html"
}
],
"lastmodified": 1399907224208
},
{
"path": "someOtherPath",
"tag_pagetype": "special",
"tag_bookingcenter": [
"bc_ch"
],
"resources": [
{
"path": "someOtherPathDe.html",
"lang": "de",
"lastmodified": 1399020442914,
"mimetype": "text/html"
},
{
"path": "someOtherPathEn.html",
"lang": "en",
"lastmodified": 1399907224208,
"mimetype": "text/html"
}
],
"lastmodified": 1399907224208
}
]
}
My Java Classes would be:
public class ResourceDescription {
private String path;
private LocalDateTime lastModified;
private String chartConfig;
private final List<Tag> tags = new ArrayList<Tag>();
private final List<Resource> resources = new ArrayList<Resource>();
}
public class Resource {
private String lang;
private String path;
private String mimeType;
private LocalDateTime lastModified;
}
public class Tag {
private String namespace;
private String name;
}
First question which I still don't fully understand even with reading many posts here. How do I deserialize this array of Resources from the JSON into my List of the ResourceDescription?
Second and most complex question. The JSON properties prefixed with "tag_" need to be transformed into the Tag class, whereas the the property name represents the namespace and the value (single or array) represent the name. So if the pattern is "namespace:name", the first ResourceDescription would have the following tags:
tag_pagetype:default
tag_bookingcenter:bc_ch
tag_bookingcenter:bc_de
Third the "lastmodified" should be transformed into DateTime from Joda-Time.
Is this even possible with data binding or should I stick to the Tree Model?
How do I deserialize this array of Resources from the JSON into my
List of the ResourceDescription?
You have to create additional root class which contains resourcedescriptions property. For example:
class Root {
private List<ResourceDescription> resourcedescriptions;
public List<ResourceDescription> getResourcedescriptions() {
return resourcedescriptions;
}
public void setResourcedescriptions(List<ResourceDescription> resourcedescriptions) {
this.resourcedescriptions = resourcedescriptions;
}
#Override
public String toString() {
return String.valueOf(resourcedescriptions);
}
}
The JSON properties prefixed with "tag_" need to be transformed into
the Tag class, whereas the the property name represents the namespace
and the value (single or array) represent the name.
You can handle this case using #JsonAnySetter annotation. You have to add new method to ResourceDescription class which could look like this:
#JsonAnySetter
public void setAnyValues(String propertyName, Object value) {
if (propertyName.startsWith("tag_")) {
if (value instanceof String) {
tags.add(new Tag(propertyName, value.toString()));
} else if (value instanceof List) {
List<?> values = (List<?>) value;
for (Object v : values) {
tags.add(new Tag(propertyName, v.toString()));
}
}
// throw exception?
} else {
// handle another unknown properties
}
}
Third the "lastmodified" should be transformed into DateTime from
Joda-Time.
You can handle JodaTime types by adding jackson-datatype-joda library. When you add it you can register JodaModule module.
mapper.registerModule(new JodaModule());
Additional problem that your JSON contain properties written using lowercase, but your POJO properties are written using camel-case. You can change JSON or POJO or use #JsonProperty("property-name-from-JSON") annotation or implement your own naming strategy. For example:
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PropertyNamingStrategyBase() {
#Override
public String translate(String propertyName) {
return propertyName.toLowerCase();
}
});
Full Java example how to you can deserialize your JSON:
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.datatype.joda.JodaModule;
public class JacksonProgram {
public static void main(String[] args) throws Exception {
String json = "{ ... }";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PropertyNamingStrategyBase() {
#Override
public String translate(String propertyName) {
return propertyName.toLowerCase();
}
});
System.out.println(mapper.readValue(json, Root.class));
}
}
class Root {
private List<ResourceDescription> resourcedescriptions;
public List<ResourceDescription> getResourcedescriptions() {
return resourcedescriptions;
}
public void setResourcedescriptions(List<ResourceDescription> resourcedescriptions) {
this.resourcedescriptions = resourcedescriptions;
}
#Override
public String toString() {
return String.valueOf(resourcedescriptions);
}
}
class ResourceDescription {
private String path;
private LocalDateTime lastModified;
private String chartConfig;
private final List<Tag> tags = new ArrayList<Tag>();
private final List<Resource> resources = new ArrayList<Resource>();
#JsonAnySetter
public void setAnyValues(String propertyName, Object value) {
if (propertyName.startsWith("tag_")) {
if (value instanceof String) {
tags.add(new Tag(propertyName, value.toString()));
} else if (value instanceof List) {
List<?> values = (List<?>) value;
for (Object v : values) {
tags.add(new Tag(propertyName, v.toString()));
}
}
// throw exception?
} else {
// handle another unknown properties
}
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public LocalDateTime getLastModified() {
return lastModified;
}
public void setLastModified(LocalDateTime lastModified) {
this.lastModified = lastModified;
}
public String getChartConfig() {
return chartConfig;
}
public void setChartConfig(String chartConfig) {
this.chartConfig = chartConfig;
}
public List<Tag> getTags() {
return tags;
}
public List<Resource> getResources() {
return resources;
}
#Override
public String toString() {
return "ResourceDescription [path=" + path + ", lastModified=" + lastModified
+ ", chartConfig=" + chartConfig + ", tags=" + tags + ", resources=" + resources
+ "]";
}
}
class Resource {
private String lang;
private String path;
private String mimeType;
private LocalDateTime lastModified;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public LocalDateTime getLastModified() {
return lastModified;
}
public void setLastModified(LocalDateTime lastModified) {
this.lastModified = lastModified;
}
#Override
public String toString() {
return "Resource [lang=" + lang + ", path=" + path + ", mimeType=" + mimeType
+ ", lastModified=" + lastModified + "]";
}
}
class Tag {
private String namespace;
private String name;
public Tag() {
}
public Tag(String namespace, String name) {
this.namespace = namespace;
this.name = name;
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Tag [namespace=" + namespace + ", name=" + name + "]";
}
}
Above program prints:
[ResourceDescription [path=somePath, lastModified=2014-05-12T17:07:04.208, chartConfig=null, tags=[Tag [namespace=tag_pagetype, name=default], Tag [namespace=tag_bookingcenter, name=bc_ch], Tag [namespace=tag_bookingcenter, name=bc_de]], resources=[Resource [lang=de, path=somePathDe.html, mimeType=text/html, lastModified=2014-05-02T10:47:22.914], Resource [lang=en, path=somePathEn.html, mimeType=text/html, lastModified=2014-05-12T17:07:04.208]]], ResourceDescription [path=someOtherPath, lastModified=2014-05-12T17:07:04.208, chartConfig=null, tags=[Tag [namespace=tag_pagetype, name=special], Tag [namespace=tag_bookingcenter, name=bc_ch]], resources=[Resource [lang=de, path=someOtherPathDe.html, mimeType=text/html, lastModified=2014-05-02T10:47:22.914], Resource [lang=en, path=someOtherPathEn.html, mimeType=text/html, lastModified=2014-05-12T17:07:04.208]]]]
You will need to create a custom deserializer for ResourceDescription in order to accomplish what you need to do. The syntax for specifying a custom deserializer for ResourceDescription will look like this:
#JsonDeserialize(using=ResourceDescriptionDeserializer.class)
public class ResourceDescription { ... }
This deserializer will have to iterate through each of the keys for each resource description to see if it begins with "tag_", strip off the prefix and use the remaining for the namespace and populate the name/value for the Tag before adding it to the array of the ResourceDescription being created.
For all other attributes/types I think you can just defer to the default deserialization and set those attributes on their respective fields.
Then, to deserialize the list of ResourceDescriptions you can specify a TypeReference to avoid writing a custom deserializer for ResourceDescriptions. The code will look something like this:
Map<String, List<ResourceDescription>> resultMap =
objectMapper.readValue(JSON, new TypeReference<Map<String, List<ResourceDescription>>>() {});
List<ResourceDescription> descriptions = resultMap.get("resourcedescriptions");
Here's an article that doesn't quite pair with what you're doing but I think will help with the general idea:
Using Jackson to deserialize array nested within array in JSON object

Categories