Lucas Lissandrello
Lucas Lissandrello

Reputation: 23

Java Spring, unmarshal dynamic json

I am trying to unmarshal a json response which comes from a server. But I would like to know which is the best way, approach to use when the json response changes.

For example, if I have a json response like this:

{
  "name": "John",
  "last_name": "John Last name",
  "date_of_birth": "01.01.1990"
} 

With jackson, I could deserialize the json object into a Person.class like this:

@NoARgsConstructor
@Setter
public class Person(){
 private String name;
 private String lastName;
 private String dateOfBirth;
}

But what if the json struct changes, and now the attributes of the person comes inside a person object.

{
   "person": {
      "name": "John",
      "last_name": "John Last name",
      "date_of_birth": "01.01.1990"
    } 
 }

What would be the best way to avoid this things or to avoid this problems? Is there any possible solution or approach to implement in java spring?

Upvotes: 1

Views: 562

Answers (3)

pirho
pirho

Reputation: 12215

A generic way to deserialize such wrapped is to write a deserializer of your own, like:

@SuppressWarnings("serial")
public class UnwrappingDeserializer<T> extends StdDeserializer<T> {

    @Setter
    private String fieldName;
    private ObjectMapper innerMapper = new ObjectMapper();

    public UnwrappingDeserializer(Class<T> vc) {
        super(vc);
        fieldName = handledType().getSimpleName().toLowerCase();
    }

    @SuppressWarnings("unchecked")
    @Override
    public T deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        JsonNode rootT = p.readValueAsTree();
        // Check if there is a node with specified field name.
        // There is also a setter for it if the field name is not 
        // directly resolvable
        JsonNode valueT = rootT.get(fieldName);
        if (valueT == null) {
            // If no such node it is the root tha has the value
            valueT = rootT;
        }
        return innerMapper.convertValue(valueT, (Class<T>)handledType());
    }
}

Assuming you have Person as:

@Getter @Setter
// Below is because of your JSON key format
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Person {
    private String name;
    private String lastName;
    private String dateOfBirth;
}

you can just add the deserializer to your ObjectMapper like:

ObjectMapper om = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addDeserializer(Person.class, new UnwrappingDeserializer<Person>(Person.class));
om.registerModule(sm);

Upvotes: 0

anish sharma
anish sharma

Reputation: 590

To make it completely dynamic, I have used reflection api and json-simple-1.1 jar and jackson

import com.fasterxml.jackson.annotation.JsonProperty;

public class Person {
@JsonProperty("name")
private String name;
@JsonProperty("last_name")
private String lastName;
@JsonProperty("date_of_birth")
private String dateOfBirth;
}

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Demo {

public static void main(String a[]) {
    try {
        String json1 = "{\r\n" + "   \"person\": {\r\n" + "      \"name\": \"John\",\r\n"
                + "      \"last_name\": \"John Last name\",\r\n" + "      \"date_of_birth\": \"01.01.1990\"\r\n"
                + "    } \r\n" + " }";

        String json2 = "{\r\n" + "  \"name\": \"John\",\r\n" + "  \"last_name\": \"John Last name\",\r\n"
                + "  \"date_of_birth\": \"01.01.1990\"\r\n" + "} ";

        extractedPersonObject(json1);
        System.out.println("*****************************");
        extractedPersonObject(json2);

    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

private static void extractedPersonObject(String json2) throws ParseException {
    Person person = new Person();

    Object obj = new JSONParser().parse(json2);
    JSONObject jo = (JSONObject) obj;
    Map finalMap;
    Field[] fields = Person.class.getDeclaredFields();
    if (jo.get(fields[0].getName()) == null) {
        finalMap = ((Map) jo.get("person"));
    } else
        finalMap = (Map) jo;

    for (Field field : fields) {
        JsonProperty jsonProperty = field.getDeclaredAnnotation(JsonProperty.class);
        invokeSetter(person, field.getName(), finalMap.get(jsonProperty.value()));
    }

    System.out.println(person.getDateOfBirth());
    System.out.println(person.getLastName());
    System.out.println(person.getName());
}

public static void invokeSetter(Object obj, String propertyName, Object variableValue) {
    PropertyDescriptor pd;
    try {
        pd = new PropertyDescriptor(propertyName, obj.getClass());
        Method setter = pd.getWriteMethod();
        try {
            setter.invoke(obj, variableValue);
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
    } catch (IntrospectionException e) {
        e.printStackTrace();
    }

}

}

Upvotes: 0

skubski
skubski

Reputation: 1606

How about searching in the JSON yourself?

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class Foo {
   public static void main(String args[]) throws Exception {
      String jsonString = "{\"foo\":\"bar\"}";
      ObjectMapper mapper = new ObjectMapper();
      ObjectNode node = mapper.readValue(jsonString, ObjectNode.class);
      if(node.has("foo")) {
         System.out.println("foo: " + node.get("foo"));
      }
   }
}

Upvotes: 1

Related Questions