Search code examples
javajsonjacksonpojo

How to create Jackson XML POJO class for a JsonObject of JsonObjects


I'm trying to create POJOs for the following JSON structure. The Fields node is easy enough to wire up, but I'm unsure how to use annotations to wire up the Description node. If I had been defining the JSON structure for that node, I'd have create an JsonArray of JsonObjects, which would make the java class easy, but since I didn't, I need to figure out how to serialize the structure below:

{
    "Fields": {
        "Required": ["ftp.hostname"],
        "Optional": ["ftp.rootDirectory"]
    },
    "Description": {
        "ftp.hostname": {
            "label": "SFTP Hostname",
            "description": "SFTP server hostname or IP address"
        },
        "ftp.rootDirectory": {
            "label": "Root Directory",
            "description": "The root path on the Data Store accessible by this connector"
        }
    }
}

Note that the nodes in the Description object have names that correlate to the values defined in the Fields node, which means their node names can vary from payload to payload.

The class for the Fields node:

public class FieldDetails {

    public static final String REQUIRED = "Required";
    public static final String OPTIONAL = "Optional";

    @JsonProperty(value = REQUIRED, required = true)
    private List<String> required;

    @JsonProperty(value = OPTIONAL, required = true)
    private List<String> optional;
}

And what I have so far for the entire object:

public class FieldDefinitions {

    public static final String FIELDS = "Fields";
    public static final String DESCRIPTION = "Description";

    @JsonProperty(value = FIELDS, required = true)
    private FieldDetails fields;

    @JsonProperty(value = DESCRIPTION , required = true)
    private ??? descriptions;
}

Solution

  • Generally, you can always map any JSON object to Map<String, Object>. If JSON is complicated with many nested objects, Jackson will automatically pick correct type: Map for objects and List for arrays.

    You can also declare class like below for Description properties.

    class Description {
        private String label;
        private String description;
        // getters, setters, toString
    }
    

    The whole Description is a big JSON which you can map to Map<String, Description>. So, it could look like below:

    class FieldDefinitions {
    
        public static final String FIELDS = "Fields";
        public static final String DESCRIPTION = "Description";
    
        @JsonProperty(value = FIELDS, required = true)
        private FieldDetails fields;
    
        @JsonProperty(value = DESCRIPTION, required = true)
        private Map<String, Description> descriptions;
    
        // getters, setters, toString
    }
    

    Rest is the same. Example app:

    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import java.io.File;
    import java.util.List;
    import java.util.Map;
    
    public class JsonApp {
    
        public static void main(String[] args) throws Exception {
            File json = new File("./resource/test.json").getAbsoluteFile();
    
            ObjectMapper mapper = new ObjectMapper();
    
            FieldDefinitions fields = mapper.readValue(json, FieldDefinitions.class);
            System.out.println("Required");
            fields.getFields().getRequired().forEach(r ->
                    System.out.println(r + " = " + fields.getDescriptions().get(r)));
            System.out.println("Optional");
            fields.getFields().getOptional().forEach(r ->
                    System.out.println(r + " = " + fields.getDescriptions().get(r)));
        }
    }
    

    For given JSON payload prints:

    Required
    ftp.hostname = Description{label='SFTP Hostname', description='SFTP server hostname or IP address'}
    Optional
    ftp.rootDirectory = Description{label='Root Directory', description='The root path on the Data Store accessible by this connector'}