Handling Inconsistent Json Data using Jackson

You might find that you need to unmarshall a json data into a domain object but the json data structure is not consistent. In cases such as this, the unmarshalling process will fail. Jackson allows you to write your own custom Json Deserializer to resolve this issue.

If we have the following Json data as an example:

{
  "name": "John Doe",
  "email": "john.doe@mail.com",
  "contacts": {
    "address": "48 Home Drive",
    "telephones": [
      {
        "telephone": "07253636789",
        "type": "mobile"
      },
      {
        "telephone": "02087366789",
        "type": "home"
      },
      {
        "telephone": "02088366789",
        "type": "office"
      }
    ]
  }
}

But then we also get the below Json data:

{
  "name": "John Doe",
  "email": "john.doe@mail.com",
  "contacts": {
    "address": "48 Home Drive",
    "telephones": {
      "telephone": "07253636789",
      "type": "mobile"
    }
  }
}

As we can see from the above, the first Json data contains a json array in the telephones field while the second contains a Json object

A simple solution to this problem is to create our own custom deserializer that will always return our contacts object which will contain a list of telephone objects even if the telephones field has an array or object.

package main;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import domain.Contacts;
import domain.Telephone;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class ContactDeserializer extends StdDeserializer<Contacts> {

    public ContactDeserializer() {
        this(null);
    }

    public ContactDeserializer(Class<?> c) {
        super(c);
    }

    @Override
    public Contacts deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        List<Telephone> telephones = new ArrayList<>();
        JsonNode telephonesNode = node.get("telephones");
        if (telephonesNode.isArray()) {
            ArrayNode telephonesArrayNode = (ArrayNode) telephonesNode;
            telephonesArrayNode.forEach((JsonNode jsonNode) -> telephones.add(new Telephone(jsonNode.get("telephone").asText(), jsonNode.get("type").asText())));
        } else {
            telephones.add(new Telephone(telephonesNode.get("telephone").asText(), telephonesNode.get("type").asText()));
        }
        return new Contacts( node.get("address").asText(), telephones);
    }
}

And then you can specify your custom deserializer in your contacts domain object:

package domain;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import main.ContactDeserializer;

import java.util.List;

@JsonDeserialize(using = ContactDeserializer.class)
public class Contacts {
    private String address;
    private List<Telephone> telephones;

    public Contacts(String address, List<Telephone> telephones) {
        this.address = address;
        this.telephones = telephones;
    }

    public String getAddress() {
        return address;
    }

    public List<Telephone> getTelephones() {
        return telephones;
    }
}

And your telephone domain object:

package domain;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(
        ignoreUnknown = true
)
public class Telephone {
    private String telephone;
    private String type;

    public Telephone(String telephone, String type) {
        this.telephone = telephone;
        this.type = type;
    }

    public String getTelephone() {
        return telephone;
    }

    public String getType() {
        return type;
    }
}

Leave a comment