Deserializing flattened JSON to Java Object using Jackson - java

So I am currently using Jackson to deserialize JSON into complex java objects. Everything works well but I also have some fields such as:
{
"foo.bar.baz":"qux"
}
which correspond to java objects such as:
class Foo {
AnotherClass bar;
}
class AnotherClass {
String baz;
}
Jackson is unable to figure out that the dots correspond to inner objects. Is there a way to get Jackson to be able to deserialize even on flattened fields such as the field in my example?

No Jackson JSON library will not detect this as different object levels. You can use this instead:
{
"foo": {
"bar": {
"baz":"qux"
}
}
}
And you will have to create:
Class WrapperClass containing "foo" of type FooClass
Class FooClass containing "bar" of type BarClass
Class BarClass containing "baz" of type String

You can do something like that by using #JsonUnwrapped:
public class Wrapper {
#JsonUnwrapped(prefix="foo.bar.")
public AnotherClass foo; // name not used for property in JSON
}
public class AnotherClass {
String baz;
}

ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonString);
Iterator<String> iterator = root.fieldNames();
while (iterator.hasNext()) {
String fieldName = iterator.next();
if (fieldName.contains(".")) {
String[] items = fieldName.split("\\.");
if (items[0].equals("foo")) {
Foo foo = new Foo();
if (items[1].equals("bar")) {
AnotherClass bar = new AnotherClass();
foo.bar = bar;
if (items[2].equals("baz")) {
bar.baz = root.get(fieldName).asText();
}
}
}
}
}

Related

Deserialize JSON Array with random name - GSON

Am trying to deserialize a complex JSON structure using GSON. The API provider complicates things by providing an array in the results with a random name.
This is the (simplified/generified) JSON:
{
"field_1": "value",
"field_2": "value",
"field_3": {
"RANDOM_NAME": [
{
"array_field_1": "value",
"array_field_2": "value",
"array_field_3": "value"
},
{
"array_field_1": "value",
"array_field_2": "value",
"array_field_3": "value"
}
]
},
"field_4": "value"
}
and this is the corresponding (highly simplified) POJO:
public class responseObject {
String field_1;
String field_2;
Field3 field_3;
String field_4;
class Field3{
ArrayObject[] arrayObjects;
}
class ArrayObject{
String array_field_1;
String array_field_2;
String array_field_3;
}
}
However, when i run responseObject response = new Gson().fromJson(getJSON(),responseObject.class); i get the following call stack:
indicating that field_3 was not properly deserialized and does not contain an array of ArrayObject.
In this post the answers reference how to convert the data to a map, but in my case the data structure of each item in the array is actually much larger than this simplified example, and it defeats the purpose of using GSON if i have to manually pick the data i need out of a complex list of nested maps. also having trouble getting these answers to work in my scenario where the random object is an array an not a plain json object.
how do i get the randomly named array in the JSON to properly deserialize into the variable responseObject.Field3.arrayObjects??
You can avoid the complexity of using a TypeAdapeter by making the type of field_3 Map<String, List<ArrayObject>>
public class responseObject {
String field_1;
String field_2;
Map<String, List<ArrayObject>> field_3;
String field_4;
class ArrayObject{
String array_field_1;
String array_field_2;
String array_field_3;
}
}
And then to get the first item out of the Map without knowing its key you can use:
public List<ResponseObject.ArrayObject> getFirstValue(Map<String, List<ResponseObject.ArrayObject>> field_3) {
return field_3.values().iterator().next();
}
This can be solved by writing a custom TypeAdapter for Field3 which ignores the name of the property and only reads the value. The TypeAdapter has to be created by a TypeAdapterFactory to allow getting the delegate adapter for ArrayObject[]:
class Field3TypeAdapterFactory implements TypeAdapterFactory {
public Field3TypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Only support Field3 class
if (type.getRawType() != Field3.class) {
return null;
}
TypeAdapter<ArrayObject[]> fieldValueAdapter = gson.getAdapter(ArrayObject[].class);
// Cast is safe, check at beginning made sure type is Field3
#SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<Field3>() {
#Override
public void write(JsonWriter out, Field3 value) throws IOException {
throw new UnsupportedOperationException("Serialization is not supported");
}
#Override
public Field3 read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
in.beginObject();
// Skip the random property name
in.skipValue();
ArrayObject[] fieldValue = fieldValueAdapter.read(in);
in.endObject();
Field3 object = new Field3();
object.arrayObjects = fieldValue;
return object;
}
};
return adapter;
}
}
You can then either register the factory with a GsonBuilder, or you can annotate your Field3 class with #JsonAdapter. When using #JsonAdapter the factory class should have a no-args constructor.

Java, Jackson Serialize/Deserialize Generic

With Jackson 2.6.7. I am trying to serialize/deserialize Foo.
public class Foo {
#JsonProperty("bar")
private ValueObject<String> bar;
#JsonProperty("baz")
private ValueObject<Integer> baz;
// potentially we be stretching to have something like
// private ValueObject<OtherClass> otherObject;
// but now just the above
// getters, setters
}
public class ValueObject<T> {
private static ObjectMapper MAPPER = new ObjectMapper();
Class<T> containedClass;
JsonNode value; // This is a requirement to store it as JsonNode
String otherContext; // This is not in the original json, but something populated after the serialize/deserialization.
String anotherContext; // This is not in the original json, but something populated after the serialize/deserialization.
public ValueObject(Class<T> containedClass) {
this.containedClass = containedClass;
}
public T get() {
return MAPPER.convertValue(this.value, containedClass);
}
public void set(T value) {
if (value.getClass() != containedClass) {
throw new RuntimeException("cannot set value");
}
this.value = MAPPER.valueToTree(value);
}
// getters, setters
}
Sample json
{
"bar": "BAR",
"baz": 1
}
Expected equivalent object
Foo expect = new Foo();
ValueObject<String> bar = new ValueObject(String.class);
bar.set("bar");
ValueObject<Integer> baz = new ValueObejct(Integer.class);
baz.set(1);
expected.setBar(bar);
expected.setBaz(baz);
Currently, I am thinking of just implementing a CustomSerializer for each ValueObject type, like ValueObjectStringSerializer, ValueObjectIntegerSerializer. Is there another of approaching this?

Deserialize JSON Array to Object with private list property using Jackson

A JSON string like:
[
"a", "b", "c"
]
Would usually be deserialized to List<String>. But I have a class that looks like this:
public class Foo {
private List<String> theList;
public Foo(List<String> theList) {
this.theList = theList;
}
public String toString() {
return new ObjectMapper().writeValueAsString(theList);
}
// ... more methods
}
Now I want to deserialize the above JSON string into an object of class Foo like:
Foo foo = new ObjectMapper().readValue(jsonString, Foo.class);
How is that possible?
I've already tried to use #JsonCreator with the constructor but always get:
JsonMappingException: Can not deserialize instance of ... out of START_ARRAY token
With Jackson 2.4.3, this
#JsonCreator
public Foo(List<String> theList) {
this.theList = theList;
}
...
String jsonString = "[\"a\", \"b\", \"c\"]";
Foo foo = new ObjectMapper().readValue(jsonString, Foo.class);
System.out.println(foo.getTheList());
works for me. It prints
[a, b, c]

Parse JSON on Android device using latest Jackson Parser library

Here is example of JSON response str:
{"myServiceMethodResult":[{"BoolPropertyOfFooClass":false,"StringPropertyOfFooClass":"tstString", "Bar":[{"BoolPropertyOfBarClass":false,"StringProperyOfBarClass":"tst"}]
}]
}
Service is returning List
List<Foo> myServiceMethod(){
return new List<Foo> myFooList
}
This are the classes:
#JsonRootName(value = "myServiceMethodResult")
Class Foo{
public boolean BoolPropertyOfFooClass
public String StringPropertyOfFooClass
#JsonProperty(value = "Bar")
public List<Bar> myBar;
public boolean getBoolPropertyOfFooClass(){
return BoolPropertyOfFooClass;
}
public void setBoolPropertyOfFooClass(bool value){
this.BoolPropertyOfFooClass = value
}
public String getStringPropertyOfFooClass(){
return StringPropertyOfFooClass;
}
public void setBoolPropertyOfFooClass(String value){
this.StringPropertyOfFooClass = value
}
public List<Bar> myBar() {
return myBar;
}
public void setmyBar(List<Bar> value) {
this.myBar= value;
}
}
I'm usign Jackson parser and first of all Parsing JSON string to an object is surprising slow (despite a fact that this file is huge (2 MB)
String jsonStr = sh.makeServiceCall(serviceUrl/MethodName, ServiceHandler.POST, json_content_parameters);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
JsonNode node = null;
myFooInstance= mapper.readValue(new StringReader(jsonStr), new TypeReference<List<Foo>>(){});
mapper.readValue is hitting exception myServiceResult does not match expected ('List'). Further more, if I'm using readTree function it takes 5 seconds (but not hittign exception). Is there any better way of getting Object faster,
Further more I'm not able to figure how to map List of Bar objects inside my Foo objects. I'm able to set my properties using this line of code:
TypeReference<List<Foo>> typeRef = new TypeReference<List<Foo>>(){};
myInstanceFoo= mapper.readValue(node.traverse(), typeRef);
So I Have my List of Foo objects but I'm not able to get List inside of list using something simmilar. Any help about problems with duration, or setting inner List object would be appreciated
Trace:
com.fasterxml.jackson.databind.JsonMappingException: Root name 'MyMethodResponse' does not match expected ('List') for type [collection type; class java.util.List, contains [simple type, class com.package.Foo]]
at [Source: java.io.StringReader#411dc790; line: 1, column: 2]
Since it appears that you have the response wrapped in a single-member object instance, you have the option of annotating your Foo class with this:
#JsonRootName("MyMethodResponse")
IMPORTANT: the name is FIXED.
However you are not done yet. You need to configure your ObjectMapper to use this annotation:
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE);
Your have another problem. Namely, your List<Bar> has name myBar in your POJO, but Bar in the produced JSON. You need to annotate your myBar field with #JsonProperty:
#JsonProperty("Bar")
In case Someone stumbles on a same problem I figured it out. To serialize Foo class if JSON is in format
{"response":[{"propertyOfFooClass":"something"
}]
}
you nedd to Create Root Class that contains list of Foo Class
public class RootWrapper {
private List<Foo> foo;
public List<Foo> getFoos() {
return channels;
}
#JsonProperty("response")
public void setFoos(List<Foo> fooList) {
this.foo= fooList;
}
RootWrapper mj = mapper.readValue(jsonStr, RootWrapper.class);
Cheers

jackson delay deserializing field

I have a class like this:
public class DeserializedHeader
int typeToClassId;
Object obj
I know what type of object obj is based on the typeToClassId, which is unfortunately only known at runtime.
I want to parse obj out based on typeToClassId - what's the best approach here? Annotations seem like they're out, and something based on ObjectMapper seems right, but I'm having trouble figuring out what the best approach is likely to be.
Something along the lines of
Class clazz = lookUpClassBasedOnId(typeToClassId)
objectMapper.readValue(obj, clazz)
Obviously, this doesn't work since obj is already deserialized... but could I do this in 2 steps somehow, perhaps with convertValue?
This is really complex and painful problem. I do not know any sophisticated and elegant solution, but I can share with you my idea which I developed. I have created example program which help me to show you how you can solve your problem. At the beginning I have created two simple POJO classes:
class Product {
private String name;
// getters/setters/toString
}
and
class Entity {
private long id;
// getters/setters/toString
}
Example input JSON for those classes could look like this. For Product class:
{
"typeToClassId" : 33,
"obj" : {
"name" : "Computer"
}
}
and for Entity class:
{
"typeToClassId" : 45,
"obj" : {
"id" : 10
}
}
The main functionality which we want to use is "partial serializing/deserializing". To do this we will enable FAIL_ON_UNKNOWN_PROPERTIES feature on ObjectMapper. Now we have to create two classes which define typeToClassId and obj properties.
class HeaderType {
private int typeToClassId;
public int getTypeToClassId() {
return typeToClassId;
}
public void setTypeToClassId(int typeToClassId) {
this.typeToClassId = typeToClassId;
}
#Override
public String toString() {
return "HeaderType [typeToClassId=" + typeToClassId + "]";
}
}
class HeaderObject<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
#Override
public String toString() {
return "HeaderObject [obj=" + obj + "]";
}
}
And, finally source code which can parse JSON:
// Simple binding
Map<Integer, Class<?>> classResolverMap = new HashMap<Integer, Class<?>>();
classResolverMap.put(33, Product.class);
classResolverMap.put(45, Entity.class);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
String json = "{...}";
// Parse type
HeaderType headerType = mapper.readValue(json, HeaderType.class);
// Retrieve class by integer value
Class<?> clazz = classResolverMap.get(headerType.getTypeToClassId());
// Create dynamic type
JavaType type = mapper.getTypeFactory().constructParametricType(HeaderObject.class, clazz);
// Parse object
HeaderObject<?> headerObject = (HeaderObject<?>) mapper.readValue(json, type);
// Get the object
Object result = headerObject.getObj();
System.out.println(result);
Helpful links:
How To Convert Java Map To / From JSON (Jackson).
java jackson parse object containing a generic type object.

Categories

Resources