Search code examples
javaclassjacksondeserializationvariable-declaration

How to declare a variable or Object of any class type in Java


I am quite new to Java and I am trying to deserialize the JSON using Jackson and I facing some minor issue with regards to declaring the Object/Variable type. I will provide all the codes then explain the issue for easy understanding.

I have an enum that will have the required type values:

public enum IdentifierTypeValues {
    Type1,                  
    Type2,                      
    Type3,                  
    //Constructor and Getter of enum values
}

Then for each of these type, I have different classes which will have different input and do a completely different type of process:

public class GenerateType1 {
    private String name;
    private String age;
    //Getter and Setter
    //Some required process based on these values
}

public class GenerateType2 {
    private String address;
    private String city;
    private String country;
    //Getter and Setter
    //Some required process based on these values
}

public class GenerateType3 {
    private String firstName;
    private String lastName;
    private String fullName;
    //Getter and Setter
    //Some required process based on these values
}

Now I have a wrapper class for these type of classes which will take the type based on enum and typeInfo values. I want the typeInfo values to be any of the class based type something like this:

public class TypeSyntax {
    private IdentifierTypeValues indeitiferType;
    private GenerateType1 / GenerateType2 / GenerateType3 identifierTypeValues;

    //Here the identifierTypeValues can have the values for anytype
    //How to declare a variable of any of these class type?
}

This is the class that will be used by my JSON for deserializing. I know I can add a wrapper class of those 3 types and provide that wrapper class as a type class for this. Something like this:

public class WrapperClass{
    private GenerateType1 type1;
    private GenerateType2 type2;
    private GenerateType3 type3;
}

public class TypeSyntax{
    private IdentifierTypeValues indeitiferType;
    private WrapperClass identifierTypeValues;
    //But using this approach will change my JSON structure which I do not want to do.
}

My JSON structure is something like this and I would like to keep it in the same way.

{
    "indeitiferType":"Type1",
    "identifierTypeValues":{
      "name":"Batman",
      "age":"2008"
    }
}

Is there a way I can declare the variable of multiple type class? or any better approach to handle this by keeping the json format same? I tried searching but I am unable to search what exactly so any help would be really appriciated.


Solution

  • Because the type identifier exists on a different level than the other properties a wrapper class TypeSyntax needed. There are several open feature requests to add wrapping functionality to Jackson e.g. https://github.com/FasterXML/jackson-databind/issues/512

    Fortunately polymorphism is supported in Jackson with @JsonTypeInfo and @JsonSubTypes annotations.

    Wrapper class should look like:

    public class TypeSyntax {
        @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
                include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
                property = "identifierType")
        private GenerateTypeBase identifierTypeValues;
    
    // getters and setters (omitted for brevity)
    }
    

    GenerateTypeBase is the common parent class

    @JsonSubTypes({  
            @JsonSubTypes.Type(value = GenerateType1.class, name = "Type1"),
            @JsonSubTypes.Type(value = GenerateType2.class, name = "Type2"),  
    })  
    public abstract class GenerateTypeBase {  
        private String name;  
        private String age;  
    
    // getters and setters (omitted for brevity)
    }
    

    In this different children classes will instantiated based on the identifierType property. The children must extend this base class:

    public class GenerateType2 extends GenerateTypeBase {  
    
    // additional properties
    
    }
    

    In a short test it will be:

    @Test  
    void wrapperTest() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        GenerateType2 a = new GenerateType2();
        a.setName("Foo");
        a.setAge("13");
        TypeSyntax w = new TypeSyntax();
        w.setIdentifierTypeValues(a);
        String json = mapper.writeValueAsString(w);
        System.out.println(json);  
    }
    

    and the output:

    {
        "identifierTypeValues":
        {
            "name":"Foo",
            "age":"13"
        },
        "identifierType":"Type2"
    }
    

    Deserialization

    @Test  
    void wrapperTest() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        String input = "{\"identifierTypeValues\": \"name\":\"Foo\",\"age\":\"13\"},\"identifierType\":\"Type2\"}";
        TypeSyntax w = mapper.readValue(new StringReader(input), TypeSyntax.class);
        assertAll(  
            () -> assertEquals(GenerateType2.class, o.getIdentifierTypeValues().getClass()),
            () -> assertEquals("13", o.getIdentifierTypeValues().getAge())
        );
    }
    

    If you want more flexibility you can write custom (de)serializer and / or custom resolver. Using custom TypeIdResolver that will possible to convert identifiers to types programmatically instead of using "key-value pairs" in @JsonSubTypes