Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Java – Convert JSON data to java objects without knowing explicitly the type

Supposedly that I need to retrieve data from an external API (Not designed by me) that returns data in JSON format, as following (Only for illustrating my question, not the actual output format/info)

{
  "variables":[{"a":15, "b":true, "c":10.05, "d":"2022-10-15"},
               {"a":16, "b":false, "c":11.25, "d":"2022-01-01"}]
}

For the result returned by this API, it is known that the data type can only be String,Boolean,Date or Double. However, the type is not explicitly given in the JSON. As far as converting JSON to Java objects, I can convert them all to Object.

However, to allow further manipulation under the appropriate class, is there a better way to convert those objects to the appropriate types?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

I have thought about using try/catch to try to convert the Object to these 4 types. However, I imagine this won’t be the ideal way. So I am wondering what other ways can be better for this situation.

PS: I understand that the best solution would to ask whoever designed the API to include the data type. But I would like to see if it can work without having explicit information on the data type of each variable.

>Solution :

If you use Jackson’s ObjectMapper, you can parse the JSON data as a JsonNode. After your access the values, you can inspect the class name of each ValueNode.

Note: The values True and False are not valid in JSON. They must be lowercase variants true and false respectively. I called toLowerCase() on the JSON string as a lazy way to make it parseable.

import java.util.Locale;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.node.ValueNode;

public class JsonParser {
    private static final String JSON_DATA = "{\"variables\":[{\"a\":15,\"b\":True,\"c\":10.05,\"d\":\"2022-10-15\"},{\"a\":16,\"b\":False,\"c\":11.25,\"d\":\"2022-01-01\"}]}";

    private static ObjectMapper objectMapper = new ObjectMapper();

    public static String getTypeOfNode(ValueNode node) {
        if (node != null) {
            switch (node.getClass().getSimpleName()) {
                case "IntNode":
                    return "int";
                case "BooleanNode":
                    return "boolean";
                case "DoubleNode":
                    return "double";
                case "TextNode":
                    return "String"; // timestamp
            }
        }

        return null;
    }

    public static void main(String[] args) throws JsonMappingException, JsonProcessingException {
        JsonNode root = objectMapper.readTree(JSON_DATA.toLowerCase(Locale.US));
        JsonNode variables = root.get("variables"); // variables.isArray() == true

        for (JsonNode variable : variables) {
            System.out.printf("Type of a: %s%n", getTypeOfNode((ValueNode) variable.get("a")));
            System.out.printf("Type of b: %s%n", getTypeOfNode((ValueNode) variable.get("b")));
            System.out.printf("Type of c: %s%n", getTypeOfNode((ValueNode) variable.get("c")));
            System.out.printf("Type of d: %s%n", getTypeOfNode((ValueNode) variable.get("d")));
        }
    }
}

You can use this information to unwrap the underlying value from the ValueNode and set it on your own model data object.

Output:

Type of a: int
Type of b: boolean
Type of c: double
Type of d: String
Type of a: int
Type of b: boolean
Type of c: double
Type of d: String

A better way

Using a custom Jackson deserializer module and Lombok, we can shorten this code and get true type inference during deserialization.

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;

import lombok.Data;

public class JsonParserAdvanced {
    private static final String JSON_DATA = "{\"variables\":[{\"a\":15,\"b\":True,\"c\":10.05,\"d\":\"2022-10-15\"},{\"a\":16,\"b\":False,\"c\":11.25,\"d\":\"2022-01-01\"}]}";

    private static ObjectMapper objectMapper;

    static {
        objectMapper = new ObjectMapper();

        SimpleModule module = new SimpleModule();
        module.addDeserializer(LocalDate.class, new CustomLocalDateDeserializer());
        objectMapper.registerModule(module);
    }

    public static void main(String[] args) throws JsonMappingException, JsonProcessingException {
        Payload payload = objectMapper.readValue(JSON_DATA.toLowerCase(Locale.US), Payload.class);
        for (Variable variable : payload.getVariables()) {
            System.out.println(variable);
        }
    }

    private static class CustomLocalDateDeserializer extends StdDeserializer<LocalDate> {
        private DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;

        public CustomLocalDateDeserializer() {
            this(null);
        }

        public CustomLocalDateDeserializer(Class<?> valueClass) {
            super(valueClass);
        }

        @Override
        public LocalDate deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
            return LocalDate.parse(jsonParser.getText(), formatter);
        }
    }

    @Data
    private static class Variable {
        private int a;
        private boolean b;
        private double c;
        private LocalDate d;
    }

    @Data
    private static class Payload {
        private Variable[] variables;
    }
}

Output:

JsonParserAdvanced.Variable(a=15, b=true, c=10.05, d=2022-10-15)
JsonParserAdvanced.Variable(a=16, b=false, c=11.25, d=2022-01-01)
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading