简体   繁体   中英

Generate a custom JSON Schema in JAVA using Jackson

I'm new to this and struggling for the use cases when a POJO/Bean has nested parameters in it.

Requirement -

Generate a JSON schema using Jackson(latest versions are OK) for a JAVA Bean/POJO class, such that it includes the structure of nested Objects properly and also want to add custom attributes to the nested pojos(in my case, want to add a fully classified classname attribute for each nested POJO parameter).

Use Case -

Say, I've a Person class which is as follows. And I'm using this Person as parameter to my some operation.-

public class Person {

    private String name;
    private String id;
    private int i;
    private Person2 p;
    private List<String> strList;
    private HashMap<String, String> strMap;
    private Person3[] p3;

    public void setName(String name){
        this.name = name;
    }

    public void setId(String id){
        this.id = id;
    }

    public void setI(int i){
        this.i = i;
    }

    public void setP(Person2 p){
        this.p = p;
    }

    public String getName(){
        return this.name;
    }

    public String getId(){
        return this.id;
    }

    public int getI(){
        return this.i;
    }

    public Person2 getP(){
        return this.p;
    }

    public void setStrList(List<String> strList){
        this.strList = strList;
    }

    public List<String> getStrList(){
        return this.strList;
    }

    public void setStrMap(HashMap<String, String> strMap){
        this.strMap = strMap;
    }

    public HashMap<String, String> getStrMap(){
        return this.strMap;
    }

    public void setP3(Person3[] p3){
        this.p3 = p3;
    }

    public Person3[] getP3(){
        return this.p3;
    }
}

For eg currently this generates following JSON Schema, when above Person class is used as a parameter -

{
    "type": "object",
    "properties": {
        "name": {
            "type": "string"
        },
        "id": {
            "type": "string"
        },
        "i": {
            "type": "integer"
        },
        "p": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "id": {
                        "type": "string"
                    },
                    "i": {
                        "type": "integer"
                    },
                    "p1": {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string"
                            },
                            "id": {
                                "type": "string"
                            },
                            "i": {
                                "type": "integer"
                            }
                        }
                    }
                }
            }
        },
        "strList": {
            "type": "array",
            "items": {
                "type": "string"
            }
        },
        "strMap": {
            "type": "object"
        },
        "p3": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "id": {
                        "type": "string"
                    },
                    "i": {
                        "type": "integer"
                    }
                }
            }
        }
    },
    "classname": "com.agent.Person"
}

Person class has some multi-value data structures like MAP , ARRAYs and can also have nested POJOs . So I wan't to generate a JSON schema for these type of BEAN/POJO classes and also want to put a "classname" node for each nested POJO/BEAN , having a fully classified classname .

I'm going through a lot of stuff for this, but I'm unable to figure out a short hand for these kind of situations using Jackson.

The requirement to be noted here is to put "classname" attribute in the nested POJO attribute schema.

This question definitely related to this - How to traverse generated json schema using jackson and put custom attribute in json schema

This can be one of the approach for the above question could be as follows -

Approach 1

This is my code of putting the classnames in the generated schema. This code handles the case when the an array or a non-array parameter is provided. ie Person.class and Person[].class can be handled successfully. This code cannot handle the self reference issue which is still open on Jackson - https://github.com/FasterXML/jackson-databind/issues/339

The code below can be instantiated as follows -

public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Class<?> cls = Person[].class;
        if(cls.isArray()){
            cls = cls.getComponentType();
        }
        String s = "{\"rootNode\":{\"classname\":\"" + cls.getName() + "\"},"
                + getAttributeClassnames(cls) + "}";
        s = s.replace("\",}", "\"}").replace("},}", "}}");
        System.out.println(s);
        s = mapper.generateJsonSchema(cls).getSchemaNode().put("type", "array")
                .put("classnames", s).toString();
        s = s.replace("\\", "").replace("\"{", "{").replace("}\"", "}");        
        System.out.println(s);
    }

static String getAttributeClassnames(Class<?> cls) {
    String s = "";      
        Field[] field = cls.getDeclaredFields();
        int i = 0;
        while (i < field.length) {              
            if (!(field[i].getType() == Boolean.class)
                    && !(field[i].getType() == Integer.class)
                    && !(field[i].getType() == Character.class)
                    && !(field[i].getType() == Byte.class)
                    && !(field[i].getType() == Short.class)
                    && !(field[i].getType() == Long.class)
                    && !(field[i].getType() == Float.class)
                    && !(field[i].getType() == Double.class)
                    && !(field[i].getType().isPrimitive())
                    && !(field[i].getType() == String.class)
                    && !(Collection.class.isAssignableFrom(field[i]
                            .getType()))
                    && !(Map.class.isAssignableFrom(field[i].getType()))
                    && !(Arrays.class.isAssignableFrom(field[i].getType()))) {
                if(field[i].getType() == cls){
                    if (i == field.length - 1) {
                        Class<?> name = null;
                        if(field[i].getType().isArray()){
                            name = field[i].getType().getComponentType();
                        }else{
                            name = field[i].getType();
                        }
                        s = s + "\"" + field[i].getName() + "\""
                                + ":{\"classname\":\""
                                + name.getName() + "\","
                                +"}";
                    } else {
                        Class<?> name = null;                       
                        if(field[i].getType().isArray()){
                            name = field[i].getType().getComponentType();
                        }else{
                            name = field[i].getType();
                        }
                        s = s + "\"" + field[i].getName() + "\""
                                + ":{\"classname\":\""
                                + name.getName() + "\","
                                + "}" + ",";
                    }

                }else{
                    if (i == field.length - 1) {
                        Class<?> name = null;
                        if(field[i].getType().isArray()){
                            name = field[i].getType().getComponentType();
                        }else{
                            name = field[i].getType();
                        }
                        s = s + "\"" + field[i].getName() + "\""
                                + ":{\"classname\":\""
                                + name.getName() + "\","
                                + getAttributeClassnames(name)
                                + "}";
                    } else {
                        Class<?> name = null;                       
                        if(field[i].getType().isArray()){
                            name = field[i].getType().getComponentType();
                        }else{
                            name = field[i].getType();
                        }
                        s = s + "\"" + field[i].getName() + "\""
                                + ":{\"classname\":\""
                                + name.getName() + "\","
                                + getAttributeClassnames(name)
                                + "}" + ",";
                    }
                }
            }
            i++;
        }
    return s;
}

Approach 2

Other approach could be, to maintain a graph of classnames for the Jackson generated schema.

eg

"classnames":{
          "<attribute_name>":{
                                "classname":"<classname>"
                             }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM