Jackson Polymorphic Deserialization and serialize - json

I will two different types of in separate calls. I want to deserialize or serialize depending on the business requirement.
one type of json:
{
"type": "foo",
"data": [{
"someCommonProperty": "common property",
"fooProperty": "foo specific property"
},{
"someCommonProperty": "common property1",
"fooProperty": "foo specific property1"
}]
}
Another type of json:
{
"type": "bar",
"data": [{
"someCommonProperty": "common property",
"barProperty": "bar specific property",
"type": "type1"
},{
"someCommonProperty": "common property1",
"barProperty": "bar specific property1",
"type": "type1"
}]
}
I have classes as below
public class Parent {
private String type;
#JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = Foo.class, name = "foo"),
#JsonSubTypes.Type(value = Bar.class, name = "bar")
})
private List<AbstractData> data;
// Getters and setters
}
public abstract class AbstractData {
private String someCommonProperty;
// Getters and setters
}
public class Foo extends AbstractData {
private String fooProperty;
// Getters and setters
}
public class Bar extends AbstractData {
private String barProperty;
private String type;
// Getters and setters
}
When I try to deserialize as below, I am getting empty string on writing out java object as json.
ObjectMapper mapper = new ObjectMapper();
Parent parent = mapper.readValue(json, Parent.class);
I get this error:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class com.DemoJsonToJavaDeserialize.AbstractData
at [Source: C:<projectpath>\target\classes\foo.json; line: 3, column: 12] (through reference chain: com.DemoJsonToJavaDeserialize.Parent["data"]->java.util.ArrayList[0])

Option 1
If you have access to the AbstractData class, you might want to use the new 2.12 Jackson Deduction feature :
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = Foo.class),
#JsonSubTypes.Type(value = Bar.class)
})
#JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
public abstract class AbstractData {
private String someCommonProperty;
// getters setters
}
Then, no need to annotate Foo, Bar and Parent : everything will get instantiated automagically as long as your subclasses have different property names. type json property becomes useless as well.
(If you are using SpringBoot, you can set the jackson-bom.version property to upgrade jackson inside spring boot)
Option 2
the javadoc of JsonTypeInfo.As.EXTERNAL_PROPERTY says : Note also that this mechanism can not be used for container values (arrays, Collections... . I believe this is your problem: you're applying it to a List.
If you can't modify any of the Foo/Barr/AbstractData classes, the classic way is to use a custom Deserializer.
On the Parent class :
#JsonDeserialize(contentUsing = FooBarDeserializer.class)
private List<AbstractData> data;
And the Deserializer:
public class FooBarDeserializer extends JsonDeserializer<AbstractData> {
#Override
public AbstractData deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
JsonNode node = jsonParser.readValueAsTree();
if(node.has("fooProperty"))
return jsonParser.getCodec().treeToValue(node,Foo.class);
else
return jsonParser.getCodec().treeToValue(node,Bar.class);
}
}
If you find a solution that make use of your Parent.type , please share, I'm interested.

Related

How to deserialize single json property into multiple Java fields (if possible with converter)

Having this class:
#Getter
#Setter
public class Result {
private String positionText;
private Integer positionNumber;
.. many many other properties ..
}
and deserializing this json:
[
{
"position": "1",
.. many many other properties ..
},
{
"position": "FOO",
.. many many other properties ..
},
..
}
how can the position json property deserialized into both the positionText and positionNumber Java fields?
public abstract class ResultMixIn {
#JsonProperty("position")
abstract String getPositionText();
#JsonProperty("position")
abstract Integer getPositionNumber();
}
but this gives a:
Conflicting getter definitions for property "position": com.example.domain.Result#getPositionText() vs com.example.domain.Result#getPositionNumber()
Also changing the abstract getters to setters does not make a difference.
If possible I would like to avoid a fully fledged ResultDeserializer extending StdDeserializer as the Result class has many more properties which I would prefer not to deserialize "by hand".
PS: I'm not concerned about serializing. I'm only deserializing the model.
First you need to annotate the properties of the Result class,
so that Jackson will deserialize the positionText property,
but not the positionNumber.
You will do the latter by yourself in a taylor-made deserializer.
#Getter
#Setter
public class Result {
#JsonProperty("position")
private String positionText;
#JsonIgnore
private Integer positionNumber;
.. many many other properties ..
}
By default Jackson would use a BeanDeserializer for deserializing Result objects.
But you want a slightly modified implementation of this deserializer.
The rest of this answer is largely an adaptation of the accepted answer given to
the question How do I call the default deserializer from a custom deserializer in Jackson.
As usual your deserializer extends from StdDeserializer<Result>,
but it also implements the ResolvableDeserializer interface.
In the deserialize method most of the work is delegated to the default deserializer
(in this case a BeanDeserializer) which we got from Jackson.
We only add a small extra logic for setting the positionNumber property
based on the positionText property.
public class ResultDeserializer extends StdDeserializer<Result> implements ResolvableDeserializer {
private final JsonDeserializer<?> defaultDeserializer;
public ResultDeserializer(JsonDeserializer<?> defaultDeserializer) {
super(Result.class);
this.defaultDeserializer = defaultDeserializer;
}
#Override
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
if (defaultDeserializer instanceof ResolvableDeserializer) {
// We need to resolve the default deserializer, or else it won't work properly.
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
}
#Override
public Result deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// let defaultDeserializer do the work:
Result result = (Result) defaultDeserializer.deserialize(p, ctxt);
// here you do your custom logic:
String positionText = result.getPositionText();
if (positionText != null) {
try {
result.setPositionNumber(Integer.valueOf(positionText));
} catch(NumberFormatException e) {
// positionText is not a valid integer
}
}
return result;
}
}
Finally you need to tell Jackson that you want the above ResultDeserializer
to be used for deserializing Result objects.
This is done by the following customization of the ObjectMapper,
which will wrap your ResultDeserializer around Jackson's
default deserializer, only if a Result object is to be deserialized:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new SimpleModule()
.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (Result.class == beanDesc.getBeanClass())
return new ResultDeserializer(deserializer); // your deserializer
return deserializer;
}
}));
Then you can deserialize your JSON content as usual, for example:
File file = new File("example.json");
List<Result> results = objectMapper.readValue(file, new TypeReference<List<Result>>() {});

How to solve circular reference when serializing an object which have a class member with the same type of that object

I'm facing this issue when using Gson to serialize an object which has a class member with the same type:
https://github.com/google/gson/issues/1447
The object:
public class StructId implements Serializable {
private static final long serialVersionUID = 1L;
public String Name;
public StructType Type;
public StructId ParentId;
public StructId ChildId;
And since StructId contains ParentId/ChildId with the same type I was getting infinite loop when trying to serialize it, so what I did is:
private Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
public boolean shouldSkipClass(Class<?> clazz) {
return false; //(clazz == StructId.class);
}
/**
* Custom field exclusion goes here
*/
public boolean shouldSkipField(FieldAttributes f) {
//Ignore inner StructIds to solve circular serialization
return ( f.getName().equals("ParentId") || f.getName().equals("ChildId") );
}
})
/**
* Use serializeNulls method if you want To serialize null values
* By default, Gson does not serialize null values
*/
.serializeNulls()
.create();
But this is not good enough cause I need the data inside Parent/Child and ignoring them while serializing is not a solution.
How is it possible to solve it?
Related to the answer marked as Solution:
I have such a struct:
- Struct1
-- Table
--- Variable1
The object before serialization is:
And Json that is generated is:
As you can see, the ParentId of Table is "Struct1" but the ChildId of "Struct1" is empty and it should be "Table"
B.R.
I think using ExclusionStrategy is not the right approach to solve this problem.
I would rather suggest to use JsonSerializer and JsonDeserializer
customized for your StructId class.
(May be an approach using TypeAdapter would be even better,
but I didn't have enough Gson experience do get this working.)
So you would create your Gson instance by:
Gson gson = new GsonBuilder()
.registerTypeAdapter(StructId.class, new StructIdSerializer())
.registerTypeAdapter(StructId.class, new StructIdDeserializer())
.setPrettyPrinting()
.create();
The StructIdSerializer class below is responsible for converting a StructId to JSON.
It converts its properties Name, Type and ChildId to JSON.
Note that it does not convert the property ParentId to JSON,
because doing that would produce infinite recursion.
public class StructIdSerializer implements JsonSerializer<StructId> {
#Override
public JsonElement serialize(StructId src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("Name", src.Name);
jsonObject.add("Type", context.serialize(src.Type));
jsonObject.add("ChildId", context.serialize(src.ChildId)); // recursion!
return jsonObject;
}
}
The StructIdDeserializer class below is responsible for converting JSON to a StructId.
It converts the JSON properties Name, Type and ChildId
to corresponding Java fields in StructId.
Note that the ParentId Java field is reconstructed from the JSON nesting structure,
because it is not directly contained as a JSON property.
public class StructIdDeserializer implements JsonDeserializer<StructId> {
#Override
public StructId deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
StructId id = new StructId();
id.Name = json.getAsJsonObject().get("Name").getAsString();
id.Type = context.deserialize(json.getAsJsonObject().get("Type"), StructType.class);
JsonElement childJson = json.getAsJsonObject().get("ChildId");
if (childJson != null) {
id.ChildId = context.deserialize(childJson, StructId.class); // recursion!
id.ChildId.ParentId = id;
}
return id;
}
}
I tested the code above with this JSON input example
{
"Name": "John",
"Type": "A",
"ChildId": {
"Name": "Jane",
"Type": "B",
"ChildId": {
"Name": "Joe",
"Type": "A"
}
}
}
by deserializing it with
StructId root = gson.fromJson(new FileReader("example.json"), StructId.class);,
then by serializing that with
System.out.println(gson.toJson(root));
and got the original JSON again.
Just to show one way to make serialization (so I do not handle de-serialization) with TypeAdapter and ExclusionStrategy. This might not be the most beautiful implementation but it is quite generic anyway.
This solution makes use of the fact that your struct is some kind of a bi-directional linked list and given any node in that list we just need to separate the serialization of parents and children so that those are serialized just in one direction to avoid circular references.
First we need configurable ExclusionStrategy like:
public class FieldExclusionStrategy implements ExclusionStrategy {
private final List<String> skipFields;
public FieldExclusionStrategy(String... fieldNames) {
skipFields = Arrays.asList(fieldNames);
}
#Override
public boolean shouldSkipField(FieldAttributes f) {
return skipFields.contains(f.getName());
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
Then the TypeAdapter would be like:
public class LinkedListAdapter extends TypeAdapter<StructId> {
private static final String PARENT_ID = "ParentId";
private static final String CHILD_ID = "ChildId";
private Gson gson;
#Override
public void write(JsonWriter out, StructId value) throws IOException {
// First serialize everything but StructIds
// You could also use type based exclusion strategy
// but for brevity I use just this one
gson = new GsonBuilder()
.addSerializationExclusionStrategy(
new FieldExclusionStrategy(CHILD_ID, PARENT_ID))
.create();
JsonObject structObject = gson.toJsonTree(value).getAsJsonObject();
JsonObject structParentObject;
JsonObject structChildObject;
// If exists go through the ParentId side in one direction.
if(null!=value.ParentId) {
gson = new GsonBuilder()
.addSerializationExclusionStrategy(new FieldExclusionStrategy(CHILD_ID))
.create();
structObject.add(PARENT_ID, gson.toJsonTree(value.ParentId));
if(null!=value.ParentId.ChildId) {
gson = new GsonBuilder()
.addSerializationExclusionStrategy(new FieldExclusionStrategy(PARENT_ID))
.create();
structParentObject = structObject.get(PARENT_ID).getAsJsonObject();
structParentObject.add(CHILD_ID, gson.toJsonTree(value.ParentId.ChildId).getAsJsonObject());
}
}
// And also if exists go through the ChildId side in one direction.
if(null!=value.ChildId) {
gson = new GsonBuilder()
.addSerializationExclusionStrategy(new FieldExclusionStrategy(PARENT_ID))
.create();
structObject.add(CHILD_ID, gson.toJsonTree(value.ChildId));
if(null!=value.ChildId.ParentId) {
gson = new GsonBuilder()
.addSerializationExclusionStrategy(new FieldExclusionStrategy(CHILD_ID))
.create();
structChildObject = structObject.get(CHILD_ID).getAsJsonObject();
structChildObject.add(PARENT_ID, gson.toJsonTree(value.ChildId.ParentId).getAsJsonObject());
}
}
// Finally write the combined result out. No need to initialize gson anymore
// since just writing JsonElement
gson.toJson(structObject, out);
}
#Override
public StructId read(JsonReader in) throws IOException {
return null;
}}
Testing it:
#Slf4j
public class TestIt extends BaseGsonTest {
#Test
public void test1() {
StructId grandParent = new StructId();
StructId parent = new StructId();
grandParent.ChildId = parent;
parent.ParentId = grandParent;
StructId child = new StructId();
parent.ChildId = child;
child.ParentId = parent;
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(StructId.class, new LinkedListAdapter())
.create();
log.info("\n{}", gson.toJson(parent));
}}
Would give you something like:
{
"Name": "name1237598030",
"Type": {
"name": "name688766789"
},
"ParentId": {
"Name": "name1169146729",
"Type": {
"name": "name2040352617"
}
},
"ChildId": {
"Name": "name302155142",
"Type": {
"name": "name24606376"
}
}
}
Names in my test material are just by default initialized with "name"+hashCode()
Sorry for misleading you guys, based on this post :
Is there a solution about Gson "circular reference"?
"there is no automated solution for circular references in Gson. The only JSON-producing library I know of that handles circular references automatically is XStream (with Jettison backend)."
But that is case you don't use Jackson! If you are using Jackson already for building your REST API controllers so why not to use it for making the serialization. No need for external compopnents like: Gson or XStream.
The solution with Jackson:
Serialization:
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
try {
jsonDesttinationIdString = ow.writeValueAsString(destinationId);
} catch (JsonProcessingException ex) {
throw new SpecificationException(ex.getMessage());
}
De-serialization:
ObjectMapper mapper = new ObjectMapper();
try {
destinationStructId = destinationId.isEmpty() ? null : mapper.readValue(URLDecoder.decode(destinationId, ENCODING), StructId.class);
} catch (IOException e) {
throw new SpecificationException(e.getMessage());
}
And most important, you must use the #JsonIdentityInfo annotation:
//#JsonIdentityInfo(
// generator = ObjectIdGenerators.PropertyGenerator.class,
// property = "Name")
#JsonIdentityInfo(
generator = ObjectIdGenerators.UUIDGenerator.class,
property = "id")
public class StructId implements Serializable {
private static final long serialVersionUID = 1L;
#JsonProperty("id") // I added this field to have a unique identfier
private UUID id = UUID.randomUUID();

type handling by Jackson/Spring conversion of JSON to java POJO

I'm using
Spring 3.1.0.RELEASE
Jackson 1.9.5
I'm using org.springframework.web.client.RestTemplate's getForObject() method:
getForObject(String url, Class<?> responseType, Map<String, ?> urlVariables) throws RestClientException
Here's my JSON:
{
"someObject": {
"someKey": 42,
},
"key2": "valueA"
}
Here's the POJO used to hold it:
SomeClass.java:
#JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
#Generated("org.jsonschema2pojo")
#JsonPropertyOrder({
"someObject",
"key2"
})
public class SomeClass {
#JsonProperty("someObject")
private SomeObject someObject;
#JsonProperty("key2")
private String key2;
#JsonProperty("someObject")
public LocationInfo getSomeObject() {
return someObject;
}
#JsonProperty("someObject")
public void setLocationInfo(SomeObject someObject) {
this.someObject = someObject;
}
}
SomeObject.java:
#JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
#Generated("org.jsonschema2pojo")
#JsonPropertyOrder({
"someKey"
})
public class SomeObject{
#JsonProperty("someKey")
private String someKey;
#JsonProperty("someKey")
public String getSomeKey() {
if(someKey==null){
someKey = "";
}
return someKey.toUpperCase();
}
#JsonProperty("someKey")
public void setSomeKey(String someKey) {
this.someKey = someKey;
}
}
It works. Given the JSON structure, I get a String value of "42" in the property someKey of class SomeObject
I don't understand why. Is there some magical conversion going on behind the scenes of which I'm unaware?
Can the conversion be counted on? Also, i'm not currently getting any whitespace at the beginning or end of the String someKey. Is that something I can count on as well, since the integer value cannot have any whitespace?
Check out the code at https://github.com/joelittlejohn/jsonschema2pojo if you want to really understand how it works.
Yes the conversion can be counted on, yes you can count on their not being whitespace in the String in the pojo.
In a nutshell the fields from the JSON file are read in, then these get mapped to the member variables/setter methods of the Pojos that is passed in as your responseType.

Forcing JSON JAXB to ignore 'type' properties for polymorphic types with Jersey

I am using an abstract class to localize my models to different languages. This is the inheritance chain that I have set up:
//Base model, contains localized fields
public class Restaurant extends LocalizedModel<LocalizedRestaurantData>{
...
}
//Abstract class to support localized fields for all my models
#XmlRootElement
public abstract class LocalizedModel<T extends LocalizedData> {
private T en;
public T getEn() {
return en;
}
public void setEn(T en) {
this.en = en;
}
...
}
//Implementation of the localized fields for the restaurant class.
#XmlRootElement
public class LocalizedRestaurantData extends LocalizedData{
protected String name;
protected String address;
...
}
This all works fine in my Jersey JSON web service, except for one thing: All the instances of the localized property en contain an extra field type:
Restaurant JSON:
{
"en": {
"type": "localizedRestaurantData",
"address": "1234 Main St.",
"name": "Tacos Folie"
},
...
}
This type field is undesired and undesirable especially since it seems to be also required by Jackson when parsing an object. I've added #JsonIgnoreProperties({"type"}) in my code without success.
After multiple attempts and soliciting help on both the Jackson and the Jersey mailing list, the solution I found is:
My JERSEY context was implementing ContextResolver<JSONJAXBContext>. That needs to be changed to ContextResolver<JacksonJsonProvider> to use the pure JSON parser.
Secondly, the JacksonJsonProvider needs to configured as follows:
JacksonJsonProvider jjp = new JacksonJsonProvider();
jjp.configure(Feature.WRITE_NULL_MAP_VALUES, false);
jjp.configure(Feature.WRITE_NULL_PROPERTIES, false);
And used as the context.
Finally, the following method needs to be overriden as follows in the ContextResolver:
#Override
public Set<Object> getSingletons() {
Set<Object> s = new HashSet<Object>();
JacksonJsonProvider jjp = new JacksonJsonProvider();
jjp.configure(Feature.WRITE_NULL_MAP_VALUES, false);
jjp.configure(Feature.WRITE_NULL_PROPERTIES, false);
s.add(jjp);
return s;
}

FlexJson deserialize object reference

I'm using Spring Roo which generated set of hibernate and FlexJSON classes.
I have entity called Location and entity called Comment.
Location has many comments (1:M).
I'm trying to generate JSON object, which will, when deserialized and inserted reference existing Location object.
When I omit location field, everything is working fine, for example:
{
"date": 1315918228639,
"comment": "Bosnia is very nice country"
}
I don't know how to reference location field.
I've tried following, but with little success:
{
"location": 10,
"date": 1315918228639,
"comment": "Bosnia is very nice country"
}
where location id is 10.
How can I reference location field in the JSON?
Edit: Added Comment entity:
#RooJavaBean
#RooToString
#RooJson
#RooEntity
public class Komentar {
private String comment;
#ManyToOne
private Location location;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(style = "M-")
private Date date;
}
I've solved issue by adding transient property.
#Transient
public long getLocationId(){
if(location!=null)
return location.getId();
else
return -1;
}
#Transient
public void setLocationId(long id){
location = Location.findLocation(id);
}
Got similar problem, but i can't change incoming json message, so i've changed generated aspect file:
#RequestMapping(value = "/jsonArray", method = RequestMethod.POST, headers = "Accept=application/json")
public ResponseEntity<String> Komentar.createFromJsonArray(#RequestBody String json) {
for (Komentar komentar: Komentar.fromJsonArrayToProducts(json)) {
komentar.setLocation(Location.findLocation(komentar.getLocation().getId()));
komentar.persist();
}
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
return new ResponseEntity<String>(headers, HttpStatus.CREATED);
}
komentar.setLocation(Location.findLocation(komentar.getLocation().getId())); was added by me.
I got same problem and solved it by introducing a custom object factory.
Since JSONDeserializer expect a json object for location attribute (ex:"Location":{"id":10,..}), supplying location id as a String/Integer (ex:"Location":"10") will give you an exception.
Therefore I have written LocationObjectFactory class and telling flexjson how to deserialize a Location class object in the way I want.
public class LocationObjectFactory implements ObjectFactory {
#Override
public Object instantiate(ObjectBinder context, Object value,
Type targetType, Class targetClass) {
if(value instanceof String){
return Location.findProblem(Long.parseLong((String)value));
}
if(value instanceof Integer){
return Location.findProblem(((Integer)value).longValue());
}
else {
throw context.cannotConvertValueToTargetType(value,targetClass);
}
}
}
and deserialize the json string like this
new JSONDeserializer<Komentar>().use(null, Komentar.class).use(Location.class, new LocationObjectFactory()).deserialize(json);