Reputation: 7068
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:
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?
Upvotes: 1
Views: 2675
Reputation: 38625
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]]]]
Upvotes: 2
Reputation: 3959
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
Upvotes: 2