Deserialize object reference in Jackson - java

I am trying to deserialize a object ref ($ref) using ObjectMapper.
public class Foo {
#JsonProperty("bar")
private Bar bar;
#JsonProperty("bar")
public Bar getBar() {
return bar;
}
#JsonProperty("bar")
public void setBar(Bar bar) {
this.bar = bar;
}
}
test.json
This is the json file I am trying to deserialize.
Is this is the correct way to refer to a object/json reference?
{
"bar": {"$ref": "/bar.json"}
}
Deserializer.java
ObjectMapper objectMapper = new ObjectMapper();
//load class
URL url = Deserializer.class.getClassLoader().getResource("test.json");
//deserialize
objectMapper.readValue(url, Foo.class);
the result creates a Foo pojo with additional property of "bar": ""$ref": "/bar.json"" rather than deserializing it. Do I need to implement the deserialize interface and manually deserialize the node?

Traditionally in Comp Sc. this problem is solved using what is known as "Pointer Swizzling".
This means that If you have an Object A that contains a reference to B and you want to serialize this structure (and then deserialize it), you would need to "unswizzle" the pointer to B to a "name" (an identifier that uniquely identifies the instance B) , write it to disk. When deserializing, you would then take that name, find the instance that it points to (B) and "swizzle" the name back to a proper pointer to B.
Now, in Java pointers are called references but it's the same.
Here's an example to illustrate:
originalA = { "id":"id_a", "ref_to_b": originalB}
originalB = { "id":"id_b" }
Applying unswizzling:
readyForSerializationA = { "id":"id_a", "ref_to_b": "id_b"}
readyForSerializationB = { "id": "id_b" }
followed by writing to store/reading back from store.
Applying swizzling:
deserializedB = { "id":"id_b" }
deserializedA = { "id": "id_a", "ref_to_b": deserializedB}
One possible way to do it for your case,is to deserialize all objects first, put them into an HashMap and in a second pass, look up the ObjectReference(s) from the various ObjectID(s) that you have in your JSON (swizzling).
Some further reading: https://en.wikipedia.org/wiki/Pointer_swizzling

You need to store {"$ref": "/bar.json"} this as a Map. That's the simplest way to store it.
Example:
public class Foo {
#JsonProperty("bar")
private Map<String, Bar> bar;
#JsonProperty("bar")
public Map<String, Bar> getBar() {
return bar.get("$ref");
}
#JsonProperty("bar")
public void setBar(Map<String, Bar> bar) {
this.bar = bar;
}
}
Only then it will get the value of $ref in Bar object. Otherwise, the data will be in incorrect format and Bar object will take the entire bar value into it.

Related

How to deserialize fields into object using jackson?

I tried deserialize following json to POJO.
{
"foo": {
"key1":"dummy",
"key2":"dummy"
},
"bar": {
"key1":"dummy",
"key2":"dummy",
"key3":"dummy"
},
"bazKey1":"dummy",
"bazKey2":"dummy",
"bazKey3":"dummy",
"bazKey4":"dummy"
// Many others....
}
You can see above strange baz properties...
But I want to treat baz as an object like foo and bar.
public class Pojo {
private Foo foo;
private Bar bar;
private Baz baz;
// Many others....
}
However, I just found poor solution which uses custom deserializer.
Poor solution
#Override
public Pojo deserialize(JsonParser p, DeserializationContext ctxt) throws Exception {
ObjectCodec codec = p.getCodec();
JsonNode node = codec.readTree(p);
Baz baz = new Baz.Builder()
.key1(node.get("bazKey1").textValue())
.key2(node.get("bazKey2").textValue())
.key3(node.get("bazKey3").textValue())
.key4(node.get("bazKey4").textValue())
.build();
// We have to write annoying (setter/constructor/builder) instead of below method.
// return codec.treeToValue(node, Pojo.class);
return new Pojo.Builder()
.foo(foo)
.bar(bar)
.baz(baz)
.other(other)
.other(other)
.other(other) // Many others...
.build();
}
This solution forces us to use annoying (setter/constructor/builder).
How to deserialize fields into object using jackson?
Additionally, this POJO is Immutable object.
The point of Jackson is that you're not in this situation. The very reason why you use Jackson at all is because when you have a Baz Java object, it's represented with a baz JSON property that contains an object, and when you have several string JSON properties, they are represented in Java with several String fields of the same name.
If that's not the situation you're in, then there is no reason to consider Jackson. Use any JSON parsing library, and build your Java objects from the JSON tree. You can use Java reflection to discover the Java class' fields/methods and set/call them based on the JSON properties' names.

Jackson: how to prevent field serialization (while keeping deserialization)

I'd like to go further on what this question was about, I've been roaming SO for a solid hour now without finding anything.
Basically, what I'm trying to do is having a property properly instanciated through Jackson internal reflection algorithm during deserialization but having this same property not serialized when it comes to serialization.
I know about #JsonIgnore and #JsonIgnoreProperties but apparently I can't seem to use them right : either my property is correctly deserialized when I feed Jackson a proper map of properties but it also appears in the serialized results, either (when using #JsonIgnore) it is not serialized (which is wanted) but also not deserialized (not wanted).
Example :
public class Foo {
/* This is the property I want to be instanciated by Jackson upon deserialization
* but not serialized upon serialization
*/
private final Object bar = null;
public Object getBar() {
return bar;
}
}
To make things worse, as you can see, the property is final (this is why I'm keen on using Jackson reflection ability upon Foo instanciation through deserialization). I've read on potential solution about annotating the setter and the getter differently but I'd like to keep this property final if possible. If not possible, I'd settle for a non-final property.
I would appreciate answers not suggesting custom serializer/deserializer, my code base is currently free of such and if the solution could be of minimal impact, that would be perfect. Again, I'm no Jackson expert so if what I'm asking is not possible I'll obviously accept alternative answers.
I've also read this thread on github but none of the suggested ways of implementation have actually been implemented at the moment.
Thanks
EDIT : to make things clearer
public class Foo {
private final String bar = null;
public String getBar() {
return bar;
}
#Override
public String toString() {
return bar;
}
}
public void testMethod() throws IOException {
String json = "{\"bar\":\"Value\"}";
ObjectMapper mapper = new ObjectMapper();
Foo foo = mapper.readValue(json, Foo.class);
System.out.println(foo); // should have a bar property set to "Value"
System.out.println(mapper.writeValueAsString(foo)); // should return an empty JSON object
}
I am not sure whether it is elegant solution but you can use MixIn feature. You have to create new interface which could look like below:
interface FooMixIn {
#JsonIgnore
String getBar();
}
Assume that your POJO looks like this:
class Foo {
private final String bar = null;
public String getBar() {
return bar;
}
#Override
public String toString() {
return bar;
}
}
Now you have to tell Jackson that you want to ignore this property:
String json = "{\"bar\":\"Value\"}";
System.out.println(json);
ObjectMapper deserializeMapper = new ObjectMapper();
deserializeMapper.addMixInAnnotations(Foo.class, FooMixIn.class);
System.out.println(deserializeMapper.readValue(json, Foo.class));
Above example prints:
{"bar":"Value"}
null
Without deserializeMapper.addMixInAnnotations(Foo.class, FooMixIn.class); line above program prints:
{"bar":"Value"}
Value
EDIT 1
If you want to achieve result like you showed you have to create two ObjectMappers and customize them. See below example:
String json = "{\"bar\":\"Value\"}";
ObjectMapper deserializerMapper = new ObjectMapper();
Foo foo = deserializerMapper.readValue(json, Foo.class);
System.out.println("Foo object: " + foo);
ObjectMapper serializerMapper = new ObjectMapper();
serializerMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
serializerMapper.addMixInAnnotations(Foo.class, FooMixIn.class);
System.out.println("JSON: " + serializerMapper.writeValueAsString(foo));
For serialization you have to use one instance and for deserialization you have to use another instance.
Starting with Jackson 2.6, a property can be marked as read- or write-only. It's simpler than hacking the annotations on both accessors (for non-final fields) and keeps all the information in one place. It's important to note that a final field is considered writable by default by Jackson.
However, it's not enough for a final field to allow deserialization, because you can't have a setter on that field: it needs to be set via the constructor, either directly or using a builder or another type that can be deserialized by Jackson. When using the constructor with the properties as parameters, you need to specify which parameter corresponds to which property, using #JsonProperty:
public class Foo {
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private final String bar;
public Foo(#JsonProperty("bar") String bar) {
this.bar = bar;
}
public String getBar() {
return prop;
}
}

Group list of objects in a list attribute during deserialization using Jackson

I have a JSON Structure looking like this:
[
{
"id": 0,
"name": "Foo"
},
{
"id": 1,
"name": "Bar"
}
]
and a corresponding Java Object for Data binding:
public class Thing {
public int id;
public String name;
}
I know how I could deserialize the JSON list into a list of Thing.
Now here comes the tricky part: What I want to do is deserializing the JSON into a class looking like the following snippet by only doing changes to this class:
public class Things {
private List<Thing> things;
public void setThings(List<Thing> things) {
this.things = things;
}
public List<Thing> getThings() {
return this.things;
}
}
This is because the JSON deserialization is build in deep in our application by using an ObjectMapper like this:
private static <T> T parseJson(Object source, Class<T> t) {
TypeReference<T> ref = new TypeReference<T>() {
};
TypeFactory tf = TypeFactory.defaultInstance();
//[...]
obj = mapper.readValue((String) source, tf.constructType(ref));
//[...]
return obj;
}
Are there any annotations with which I can achieve what I want or do I have to make changes to the mapper-code?
Much thanks in advance, McFarlane
The whole point of TypeReference, as described in this link, is to use the generic type argument to retrieve type information.
Internally it does the following
Type superClass = getClass().getGenericSuperclass();
...
_type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
where getActualTypeArguments()[0] will give you the actual type argument. In this case, that will be the type variable T, regardless of what you pass in for the Class<T> t parameter of your method.
The proper usage is
TypeReference<List<Thing>> ref = new TypeReference<List<Thing>>() {};
...
List<Thing> thingsList = ...;
Things things = new Things();
things.setThings(thingsList);
In other words, no, you'll need to change your mapper code to achieve what you want.
As far as I know, you won't be able to map a root JSON array as a property of a class. The alternatives is the TypeReference example above or some other ones found here.

GSON deserialization : how to know the objects?

I try to use gson library to deserialize a flow of objects sent to me.
In all examples i've seen, when the method fromJson is called, we already know what type of object we expect to have.
In my case, I receive a flow of different objects and i'd like to know the best way to know the classes of objects before deserialize them.
{ A : {...}, B : { B1 : {...}, B2 : {...} }, C : {...} }
In this example, I'd like to have a way to know that 3 objects have been sent to me : A.class, B.class and C.class
Thanks
The documentation contains examples of deserializations using arbitrary classes or in two passes (first general deserialization in a collection, then content deserialization).
This exemple looks exactly like what you need. You could adapt it to use
JsonObject obj = parser.parse(json).getAsJsonObject();
to get a JsonObject instead of an array so that you can iterate on all properties (using entrySet) and deserialize according to the names (a = gson.fromJson(myjsonelement, A.class);) by simply mapping names to classes.
Yeah i too stumbled upon this issue. There is no way gson can figure out actual class of a field value. It simply tries to instantiate class used to define the field. Needless to say it is often not what we want.
so if you had, say
class C {
private A a;
private A c;
}
class B extends A {
}
then at runtime you
C c;
c.a = new B();
c.c = new B();
after deserialisation what you get is
c.a.getClass()==A.class;
c.b.getClass()==A.class;
so you would have to specify the subclass explicitly. Here is a wrapper class that is gson friendly.
public class S<T> {
private String objectClass;
private String rawObjectRepresentation;
// Gson needs no args constructor
public S() {
}
public S(T obj) {
objectClass = obj.getClass().getName();
rawObjectRepresentation = getGson().toJson(obj);
}
#SuppressWarnings("unchecked")
public T extract() throws ClassNotFoundException {
final Class<?> clazz = Class.forName(objectClass);
return (T)getGson().fromJson(rawObjectRepresentation, clazz);
}
private Gson getGson() {
return new GsonBuilder().create();
}
#Override
public String toString() {
return "type:"+objectClass;
}
}
If there is a field on the json object that you can use to identify the subclass you need to use, then you can use Gson on Fire: https://github.com/julman99/gson-fire
It has a feature called Type Selector that does exactly what you need.
Imagine a Base class and two child classes, A and B, then the code would look like this:
GsonFireBuilder builder = new GsonFireBuilder()
.registerTypeSelector(Base.class, new TypeSelector<Base>() {
#Override
public Class<? extends Base> getClassForElement(JsonElement readElement) {
String kind = readElement.getAsJsonObject().get("kind").getAsString();
if(kind.equals("a")){
return A.class; //This will cause Gson to deserialize the json mapping to A
} else if(kind.equals("b")) {
return B.class; //This will cause Gson to deserialize the json mapping to B
} else {
return null; //returning null will trigger Gson's default behavior
}
}
});
Gson gson = builder.createGson();

FlexJSON Exclude Properties Upon Deserialization

I'm receiving a JSON response from a web service, but for various reasons I don't want to have certain properties deserialized in the final response object. For example I have:
public class Foo {
private String bar;
private int baz;
//getters & setters
}
The JSON response I'm getting back has both properties, but upon deserialization I don't want "bar" to be set. The reason for this is that the property they're sending is a long, but ours is a String, so deserializing throws an IllegalArgumentException.
Another option would be to parse the JSON with something like json-simple, remove the properties I want, convert it back to JSON and pass that into the deserializer, but I'd like to avoid that if possible since the JSON is pretty large.
Is there a way to do this with an ObjectFactory perhaps?
Yes an ObjectFactory could be used to allow a conversion from Long to String. Simply register the ObjectFactory on your path like:
new JSONDeserializer().use("some.path.to.bar", new EnhancedStringObjectFactory() ).deserialize( json, new SomeObject() );
public class EnhancedStringObjectFactory implements ObjectFactory {
public Object instantiate(ObjectBinder context, Object value, Type targetType, Class targetClass) {
if( value instanceof String ) {
return value;
} else if( value instanceof Number ) {
return ((Number)value).toString();
} else {
throw context.cannotConvertValueToTargetType(value, String.class);
}
}
}
You could even register that as the default ObjectFactory for String and it would handle that case for any String coming into the deserializer:
new JSONDeserializer().use( String.class, new EnhancedStringObjectFactory() ).deserialize( json, new SomeObject() );

Categories