Ramishka Dasanayaka Ramishka Dasanayaka - 1 year ago 84
Java Question

How to deserialize JSON string into different complex class types?

I have a JSONNode object that can contain any JSON content. Example :

{
"fieldA": "aStringValue",
"fieldB": 10,
"fieldC": {
"TypeAFieldA": "aValue"
},
"fieldD": {
"TypeBFieldA": "aValue",
"TypeBFieldB": {
"TypeCFieldA": "aValue",
"TypeCFieldB": "bValue"
}
}
}


I want to deserialize each JSON field in this string into different types of java objects as below :

fieldA -> String object
fieldB -> int
fieldC -> TypeA object
fieldD -> TypeB object


Assume that I know the class type that each field should deserialize into.
What is the best and most optimal way to go about this?

Edit: To further clarify my requirement :

The approach I have thought of is I have created objects for TypeA, TypeB, TypeC etc and have annotated them with relevant JsonPropery annotations.
What I am unclear about is how do I go about deserializing each field individually? For this I would need to extract the json string from the JsonNode one by one and run through an object mapper with the relevant class type?

Example: To deserialize "fieldC" and its value into a class of TypeC, don't I have to do something like :


  1. Extract full Json string :

    String jsonString = "fieldC": { "TypeAFieldA": "aValue" }";

  2. Run it through an object mapper:

    mapper.readValue( jsonString, TypeC.class );



How do I extract the full json string for each field by looping through the JsonNode? Is this the most optimal way to go about this?

Answer Source

Inspired by the solutions posted here, I was able to come up with my own implementation for the problem.

I wrote a function that takes in a JsonNode, and a java.lang.reflect.Type parameter. This function would check the node for each primitive and non primitive data type that I will be using in my application and deserialize it into the appropriate type.

/**
     * This function takes in a JSON node, a type info and converts the JSON into 
     * the given type.
     * @param node - node to deserialize
     * @param typeInfo - data type to deserialize into
     * @throws JsonMappingException
     * @throws JsonParseException
     * @throws IOException
     */
    private void deserializeNode ( JsonNode node, Type typeInfo ) throws JsonMappingException, JsonParseException, IOException {

        Object deserializedValue = null;

        if ( node.isDouble()   ) {
            deserializedValue = node.asDouble();

        } else if ( node.isInt() ) {
            deserializedValue = node.asInt();

        } else if ( node.isLong() ) {
            deserializedValue = node.asLong();

        } else if ( node.isBoolean() ) {
            deserializedValue = node.asBoolean();

        } else if ( node.isArray() ) {
            //Json array is translated into a Java List. If this is a known type, it will translate
            //into a List<Type> instance.
            CollectionType collectionType = this.getActualTypeOfCollection( typeInfo );
            deserializedValue = mapper.readValue( node.toString(),  collectionType );

        } else if ( node.isObject() ) {
            JavaType objectType = mapper.getTypeFactory().constructType( typeInfo );
            deserializedValue = mapper.readValue( node.toString(), objectType );

        } else if ( node.isTextual() ) {
            deserializedValue = node.asText();

        } 

        this.deserializedValues.add( deserializedValue );

    }


    /**
     * This function returns the actual collection type of a generic parameter.
     * I.e. It returns the proper Collection data complete with the generic type so
     * that Jackson could determine the proper type to deserialize the field into.
     * @param genericParameterType - java parameter type
     * @return Jackson collection type
     */
    private CollectionType getActualTypeOfCollection ( Type genericParameterType ) {

        CollectionType collectionType = null;

        if(genericParameterType instanceof ParameterizedType){

            ParameterizedType aType = (ParameterizedType) genericParameterType;
            Type[] parameterArgTypes = aType.getActualTypeArguments();
            for ( Type parameterArgType : parameterArgTypes ) {
                collectionType = mapper.getTypeFactory().constructCollectionType(List.class, (Class<?>) parameterArgType ) ;
                break;
            }
        }

        return collectionType;      
    }

Comments are welcome on the pros/cons of this approach.