Jackson json two way object reference - java

I have a pair of objects like
public class Obj1 {
public int id;
public String name;
public Obj2 obj2;
}
public class Obj2 {
public int id;
public String name;
public List<Obj1> obj1list;
}
I want to be able to convert this to Json via Jackson. I found the JsonManagedReference and JsonBackReference and annotated them but when you do that, the serialization only works in one way. It will only show when the class with the JsonManagedReference side is serialized.
If I serialize an "Obj1" I want to see the "Obj2" that is attached to it. And if I serialize the "Obj2" I want to see the list of "Obj1"s that is attached to it.
I also tried using JsonIdentityInfo annotation like so
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
and this seems to work except that it adds the "id" value of the parent object into the subobject (or every subobject in the list case) which is a bit odd. Though I guess I can live with it.
Is there a way to get this to behave as I expect?

You are looking at #JsonIgnoreProperties, it will give what is needed and avoid json recursion.
public class Obj1 {
public int id;
public String name;
#JsonIgnoreProperties("obj1list")
public Obj2 obj2;
}
public class Obj2 {
public int id;
public String name;
#JsonIgnoreProperties("obj2")
public List<Obj1> obj1list;
}
UPDATE
I always perfers #JsonIgnoreProperties over JsonBackReference and JsonManagedReference. As it not only does the trick, but also not escapes any data serialization (which is the case required here).
I also have a sample project on github deals with this situation. Look for entity class codes School.java & District.java. Its an mvn spring-boot executable code, if you wanna run a test.
From Javadoc, starting with Jackson 2.0, #JsonIgnoreProperties annotation can be applied both to classes and to properties. If used for both, actual set will be union of all ignorals: that is, you can only add properties to ignore, not remove or override. So you can not remove properties to ignore using per-property annotation.
HOW IT WORKS
When you define #JsonIgnoreProperties at propety level, while serialization/deserization it will work on the refered property object.
So here, when Obj1 is being parsed, you asked parser to ignore obj1list property of obj2. And similary, while parsing Obj2 it will ignore contained obj2 references in the Obj collection. So your parsing will look like below:
Obj1 : {
id : int,
name : string,
Obj2 : {
id : int,
name : string
obj1list : //ignored avoids recursion
}
}
Obj2 : {
id : int,
name : string,
obj1list : [{
id : int,
name : string,
obj2 : //ignored avoids recursion
},
{
id : int,
name : string
obj2 : //ignored avoids recursion
}
]
}

Related

Spring Boot MongoDB Persistance MappingException: Cannot convert Java.util.ArrayList into an instance of class java.lang.Object

tl;dr
Atempting to add an ArrayList in which Object may be an ArrayList to Persistance.
Tried to add an AttributeConverter > Failed
Plz Help
I have no idea what I am doing.
How stupid am I?
The Problem
Dependencies
spring-boot-starter-data-jpa 2.0.0
spring-boot-starter-data-mongodb 2.0.0
eclipselink 2.7.1 <- Probably don't need this one, not sure.
So here is my problem I am trying to add persistence in a Spring Boot Application for a MongoDB in this case I am using tables, the problem comes exactly on the TableRaw bean (a striped down version of Table just for persistance).
Document(collection = "rule_tables")
public class TableRaw {
#Id
private String _id;
private String key;
private String name;
private String returns;
private ArrayList<AxisRaw> axis;
private ArrayList<Object> values = new ArrayList<>();
}
Everything else is just the default constructor (without _id) and getsetters.
So everything works fine with the exception of the values ArrayList. It works fine if it just a simple ArrayList with number and whatnot however in my case I want something like what I am inserting into the database (this is done every time it runs for testing purposes and the values inserted are using the MongoRepository, it works fine)
{
"_id":"5ac20c8b8ee6e6360c8947be",
"key":"1",
"name":"Table 1",
"returns":"Number",
"axis":[
{
"name":"potato",
"values":[
{
"_id":"BottomEdge","value":0
},{
"_id":"Range",
"value":[1,2]
},{
"_id":"TopEdge",
"value":3
}
]
}
],
"values":[
[1,2,3],
[1,2,3],
[1,2,3]
],
"_class":"pt.i2s.gm.gm.rulehandler.tables.model.TableRaw"
}
(For usage in the code the axis length and number of axis matters but in this case it is completely irrelevant.)
Anyway as stated previously it inserts fine into MongoDB but when attempting to get the value the following error is presented.
org.springframework.data.mapping.MappingException: Cannot convert [1, 2, 3] of type class java.util.ArrayList into an instance of class java.lang.Object! Implement a custom Converter<class java.util.ArrayList, class java.lang.Object> and register it with the CustomConversions. Parent object was: [empty]
First thing first I don't exactly know what Parent object was: [empty] means.
Second I tried creating an AttributeConverter as such:
#Component
#Converter(autoApply = true)
public class ArrayList2ObjectConverter implements
AttributeConverter<ArrayList<Object>,Object> {
#Override
public Object convertToDatabaseColumn(ArrayList<Object> attribute) {
return attribute;
}
#SuppressWarnings("unchecked") //If you don't like it suppress it
#Override
public ArrayList<Object> convertToEntityAttribute(Object dbData) {
System.out.println("Converting...");
return (ArrayList<Object>)dbData;
}
}
And adding #Convert(converter = ArrayList2ObjectConverter.class) above the values attribute. However this wasn't even called.
For some reason I couldn't find any answers to this problem, possibly due to my bad coding and making something that is just stupid to do so nobody would do it like this cause it doesn't work.
So how do I do this? And thank you for reading.
Update regarding the Axis and Value amounts
thomi sugested something that would work if I knew from the get go what type of values the table added. I apreciate the answere however some clarification should be made regarding this.
I do not know how many Axis, and therefore nested arrays I will have, it may be 1 it may be 30.
I do not know what the class type of objects will be, it may be numbers, Strings, Booleans, dates, etc. the options are limited but still extensive.
Possible Solution Which I Do Not don't want to use
I could simply create an Object that held a string and an ArrayList which would probably work fine, however I wanted to avoid this resolution, as I don't want to add irrelevant information to the database.
Adopted Solution
By request of #user_531 I will add the solution to this problem.
As this was not working I altered my aproach to the utilization of a new object called ValueList which is simply a wrapper class for a single Object
private ArrayList<ValueList> values;
ValueList Class
public class ValueList {
public Object value;
}
This allows me to add any type of object I want to the list, this does result however in tables looking like this:
{
"key":1,
...... (Same as above)
"values": [
{
"value": [
{
"value":1
},
{
"value":2
}
]
},
{
"value": [
{
"value":3
},
{
"value":4
}
]
}
]
}
Which does look hidious but it doesn't fail anymore and allows me to read values relativelly consistently by calling the "getValue()" method or "getValueList()" method acording to the result from "isValueList()".
I think you should not map something to an object. In your DB, you will surely have an idea of what datatype there will be in your Array, In your case, try and replace with:
#Document(collection = "rule_tables")
public class TableRaw {
#Id
private String _id;
private String key;
private String name;
private String returns;
private ArrayList<AxisRaw> axis;
private List<List<Integer>> values; // no initialization.
}
This should map your structure just fine.

Changing json to java pojo

Let's say I have a JSON file example.json
example.json
{
"BaggageMaxSize" : {
"mesurement" : "sum",
"width" : 70,
"height" : 50,
"depth" : 40
}
}
And create the POJO class:
public class pojoexample{
private BaggageMaxSize BaggageMaxSize;
// getter
// setter
}
And then:
public class BaggageMaxSize
{
private String height;
private String width;
private String depth;
private String mesurement;
// getter
// setter
}
Now, I want to use the mapper.readValue to change file to BaggageInfoPolicy.class:
BaggageInfoPolicy bip = mapper.readValue(file, BaggageInfoPolicy.class);
But bip.getBaggageMaxSize().getMesurement() returns null value. Any suggestions?
Try using mapper.writeValue first and check how your resulting JSON object will look like. Very likely, there's an issue with int -> string conversion in your BaggageMaxSize when deserialized from JSON.
Also, check your getters/setters to be publicly visible and be available both on pojoexample and BaggageMaxSize.
Actually your JSON represents a pojoexample class instance and not a BaggageInfoPolicy object, which you haven't shared in your post.
So you need to change your code to:
PojoExample bip = mapper.readValue(file, PojoExample.class);
So it reads the PojoExample object correctly.
Note:
Your class should follow the java naming convention and
start with an uppercase, that's why I changed it to PojoExample,
change it in the class definition as well.
And Make sure your class fields have the same types as in the JSON, and their getters and setters are correctly implemented.

Jackson: can json specify the target type?

This is what my class looks like -
public class A {
private Map<String, Object> objects = null;
....
}
My json would be like -
{
"f1" : {
"name" : "some name",
"val" : 3
},
"f2" : {
"arg": {
some field/value pairs
}
}
}
What I want is to specify in the JSON itself the type to which it can be deserialized to. So the value for f1 would be converted to an object of class B and f2 would get converted to object of C.
My code will look like this -
Object o = objects.get("f1");
if (o instanceof B) {
...
} else if (o instanceof C) {
...
}
Is there a way to do this? I want the json to control the deserialization.
Yes, Jackson can use a type identifier if JSON document has it. This is usually done by using annotation #JsonTypeInfo.
There are multiple ways to add/use type identifier, both regarding how it is included in JSON document, and as to what kind of id is being used (type name or Java class name?).
The easiest way to see how things match is to actually start with a POJO, add #JsonTypeInfo annotation, and serialize it to see kind of JSON produced. And once you understood how inclusion works you can modify, if necessary, structure of JSON and/or Java class definition.

Marshaling list of objects containing maps of objects to JSON

I have this object structure which I'm trying to annotate with Jackson to marshal/unmarshal to a JSON file.
public class A {
List<B> bList;
}
public class B {
String attr;
Map<String, C> map;
}
public class C {
#JsonIgnore
String name;
String value;
}
A has a list of B's and B has a map of C's where the key of the map is the name attribute of C. I want the JSON to look like this if possible:
{
"bList" : [
{
"attr":"itsValue"
"KEY_IN_MAP":"VALUE_IN_C",
"KEY_2_IN_MAP":"VALUE_2_IN_C"
}
]
}
Where KEY_IN_MAP is the name of C as the key in B's map and VALUE_IN_C is the value of the value object in the map. I've tried annotating a put method for the map:
#JsonAnySetter
private void put(String name, C value) {
map.put(name, c);
}
But marshaling this gives me:
{
"bList" : [
{
"attr":"itsValue"
"KEY_IN_MAP": {
"value":"VALUE_IN_C",
},
"KEY_2_IN_MAP": {
"value":"VALUE_2_IN_C"
}
}
]
}
Is there any way to get the above mapping with Jackson or any other JSON serializing library? My goal is to get rid of the redundancy of writing "value" every time and compress the file as much as possible.
The map in B could be turned into a list of C but I still need the mapping to be
{"name" : "value"} for each object C
Here are few suggestions that I have:
Make your Map<String,C> to be a Map<String,String>.
You can also make your Map<String,C> to be List<C>.
Please have a look at the other thread which discusses the same.

Jackson Nested Object Deserialization into property

Suppose I have a json object that looks like:
{
id: 1,
name: "john doe"
spouse: 2
}
and the class I want it to deserialize it to:
class Person{
private Long id;
private String name;
private Person spouse;
//getters/setters
}
Is there any way to tell jackson to expand the spouse: 2 property into a new Person POJO with id=2 when deserializing the JSON?
I have run into this issue as a result of deserializing JSON into persistent entities and would like to be able to easily persist the relationships between these entities.
Aside from a full deserializer, there is a simpler way: define a POJO with a single int-arg constructor like so:
class Person {
int id;
public Person(int id) {
this.id = id;
}
}
This actually works, as Jackson will try to find limited number of special constructors (single-arg public constructors that that take String, int, long, double or boolean).
You can optionally also denote these with #JsonCreator -- and if constructor is not public, you must do it, to make it discoverable. But for public ctors this is not needed.
It is impossible of course for Jackson to infer a fully populated Person object representing the spouse from the number 2. You would likely need to register a custom deserializer that checks if the input is an integer, and if so, looks up the spouse from wherever it is stored. I have not done this kind of thing for classes that contain references to themselves (e.g. your Person contains a Person) so I can only give you rough guidance.
I believe this may only work with Jackson version 1.9 or later. Basically, you can register a module with the object mapper that tells Jackson to use a custom deserializer.
SimpleModule module = new SimpleModule("PeopleModule", new Version(1, 1, 0, null);
module.addDeserializer(Person.class, new JacksonPersonDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Alternately on the Person class itself, you can do something like:
class Person {
#JsonDeserialize(using=JacksonPersonDeserializer.class)
Person spouse;
}
This works before 1.9 but pollutes your object. Either way, you will need to write a custom deserializer.

Categories

Resources