Search code examples
javajsonscalajacksonjackson2

How to create the POJO to handle JSON data having array of elements as well as array of array of elements


We have a scenario where we have JSONs with 2 different values for a field. We would like to parse all the jsons using the same POJO. Below you can find these 2 JSON payloads:

{
  "values": [
    [
      {
        "name": "item_name",
        "value": "pool"
      }
    ],
    [
      {
        "name": "item_name",
        "value": "Mob"
      }
    ]
  ],
  "name": "lines"
}

And:

{
  "values": [
    {
      "name": "pack",
      "value": "Enter, HD"
    }
  ],
  "name": "lines"
}

Currently, if I specify POJO as below, 2nd json throws exception

class ValuesModel extends Serializable {

  @BeanProperty
  var values: List[List[ValueModel]] = _

}

if I specify POJO as below, 1st json throws exception

class ValuesModel extends Serializable {

  @BeanProperty
  var values: List[ValueModel] = _

}

Is there a way to create one POJO to parse both jsons together rather than catching exception and parsing with another schema? I am using Jackson to parse.


Solution

  • In cases like this where you want to handle more than one JSON schema and be able to deserialize it to the same POJO model you need to implement custom deserialiser and implement all required scenarios.

    Below you can find example in Java how to deserialise both JSON payloads:

    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.ObjectCodec;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.JsonDeserializer;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
    import com.fasterxml.jackson.databind.node.MissingNode;
    import com.fasterxml.jackson.databind.type.SimpleType;
    import com.fasterxml.jackson.databind.util.TokenBuffer;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.Collections;
    import java.util.List;
    import java.util.stream.Collectors;
    import java.util.stream.StreamSupport;
    
    public class JsonApp {
    
        public static void main(String[] args) throws Exception {
            File jsonFile = new File("./src/main/resources/test.json");
    
            ObjectMapper mapper = new ObjectMapper();
            System.out.println(mapper.readValue(jsonFile, ValuesModel.class));
        }
    }
    
    class ValuesModelJsonDeserializer extends JsonDeserializer<List<ValueModel>> {
    
        @Override
        public List<ValueModel> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            final JsonDeserializer<Object> deserializer = ctxt.findRootValueDeserializer(SimpleType.constructUnsafe(ValueModel.class));
            final JsonNode root = p.readValueAsTree();
            // If node is a JSON object
            if (root.isObject()) {
                return Collections.singletonList(deserialize(p.getCodec(), root, deserializer, ctxt));
            }
            if (!root.isArray()) {
                // value is null or primitive
                return Collections.emptyList();
            }
    
            return StreamSupport.stream(root.spliterator(), false)
                    .map(this::unwrap)
                    .filter(node -> !node.isMissingNode())
                    .map(node -> deserialize(p.getCodec(), node, deserializer, ctxt))
                    .collect(Collectors.toList());
        }
    
        private JsonNode unwrap(JsonNode node) {
            if (node.isArray()) {
                if (node.isEmpty()) {
                    return MissingNode.getInstance();
                }
    
                return node.iterator().next();
            }
    
            return node;
        }
    
        private ValueModel deserialize(ObjectCodec codec, JsonNode value, JsonDeserializer<Object> valueDeser, DeserializationContext ctxt) {
            try (JsonParser jsonParser = createNestedParser(codec, value)) {
                return (ValueModel) valueDeser.deserialize(jsonParser, ctxt);
            } catch (IOException e) {
                throw new IllegalArgumentException(e);
            }
        }
    
        private JsonParser createNestedParser(ObjectCodec codec, JsonNode value) throws IOException {
            TokenBuffer buffer = new TokenBuffer(codec, false);
            codec.writeTree(buffer, value);
    
            JsonParser parser = buffer.asParser();
            parser.nextToken();
    
            return parser;
        }
    }
    

    To register custom deserialiser you can use @JsonDeserialize annotation:

    @JsonDeserialize(using = ValuesModelJsonDeserializer.class)
    private List<ValueModel> values;