Deserialize JSON to classes

20,069

Solution 1

I had to accomplish something very similar, here is an excerpt.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@class")
@JsonSubTypes({
    @JsonSubTypes.Type(value=IMetricCollection.class, name="MetricCollection"),
    @JsonSubTypes.Type(value=IMetricDouble.class, name="MetricDouble"),
    @JsonSubTypes.Type(value=IMetricInteger.class, name="MetricInteger"),
    @JsonSubTypes.Type(value=IMetricPlot.class, name="MetricPlot"),
    @JsonSubTypes.Type(value=IMetricString.class, name="MetricString"),
    @JsonSubTypes.Type(value=IMetricMatrix.class, name="MetricMatrix")
})

public interface IMetric extends HasViolations<IViolation>, Serializable {

    /**
     * Getter for the name of the object.
     * 
     * @return
     */
    public abstract String getName();

    /**
     * Set the name of the object.
     * 
     * @param name
     */
    public abstract void setName(String name);

    /**
     * Returns true if metric has violations.
     * @return
     */
    public abstract boolean hasMetricViolations();
}

This may seem kind of counter intuitive for using an interface but I was able to get this all working by telling the interface what concrete class to use. I also have another chunk of code in a separate project that overrides the JsonSubTypes to instantiate it's own type of classes below, if this helps.

@JsonDeserialize(as=MetricMatrix.class)
public interface IMetricMatrix<C extends IColumn> extends IMetric {

    public static interface IColumn extends IMetricCollection<IMetric> {
    }

    public static interface IIntegerColumn extends IColumn {
    }

    public static interface IDoubleColumn extends IColumn {
    }

    public static interface IStringColumn extends IColumn {
    }


    public abstract List<C> getValue();

    public abstract void setValue(List<C> value);

    public abstract void addColumn(C column);
}

In this class I can parse the same REST message but I am overriding the original projects concrete types and the subtypes for this project make them persistent. Since the type names are the same I can override what interface to use for this object type. Please keep in mind that I am using the @class property but this is completely arbitrary could be @whatever annotation but it would need to match on both sides. This is not using the JsonTypeInfo.Id.Class annotation.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@class")
@JsonSubTypes({
    @JsonSubTypes.Type(value=IMetricCollectionEntity.class, name="MetricCollection"),
    @JsonSubTypes.Type(value=IMetricDoubleEntity.class, name="MetricDouble"),
    @JsonSubTypes.Type(value=IMetricIntegerEntity.class, name="MetricInteger"),
    @JsonSubTypes.Type(value=IMetricPlotEntityEntity.class, name="MetricPlot"),
    @JsonSubTypes.Type(value=IMetricStringEntity.class, name="MetricString"),
    @JsonSubTypes.Type(value=IMetricMatrixEntity.class, name="MetricMatrix")
})
public interface IMetricEntity extends IDatastoreObject, IMetric {

    public String getContext();

    public List<IViolation> getViolations();
}



@JsonDeserialize(as=MetricMatrixEntity.class)
public interface IMetricMatrixEntity extends IMetricEntity {

    public static interface IColumnEntity extends IColumn {
        public String getName();
    }

    public static interface IIntegerColumnEntity extends IColumnEntity {
    }

    public static interface IDoubleColumnEntity extends IColumnEntity {
    }

    public static interface IStringColumnEntity extends IColumnEntity {
    }

    public abstract List<IColumnEntity> getValue();

    public abstract void setValue(List<IColumnEntity> value);

    public abstract void addColumn(IColumnEntity column);
}

Solution 2

You should use a class, not an interface. Otherwise, Jackson cannot create an instance.

I believe you also need to create default (aka no-arg) constructors for your POJOs for Jackson to work.

Also, a good general approach for creating a Jackson mapping is to instantiate a Java instance of your classes and then create the JSON from that, Java -> JSON. This makes it much easier to understand how the mapping is different - going from JSON -> Java is harder to debug.

Share:
20,069
Eugen Martynov
Author by

Eugen Martynov

My biggest challenge is bringing up mobile development to desktop/server side quality and processes level I'm interested in extending my knowledge and skills in writing clean, readable, maintainable code, as well as progress in quick continuous quality automated software releases I'm in love with Scrum, and I'm looking to extended relationship with Kanban Specialties: Android, iOS, BlackBeryy, J2ME, Java Mobile application development, unit testing, design patterns, continuous integration, automated testing, build management

Updated on June 15, 2020

Comments

  • Eugen Martynov
    Eugen Martynov almost 4 years

    Server returns such part of JSON:

    {"condition": {
        "or": [
            {
                "and": [
                    {
                        "operand": "a",
                        "operator": "==",
                        "value": "true"
                    },
                    {
                        "not": {
                            "operand": "b",
                            "operator": "==",
                            "value": "true"
                        }
                    }
                ]
            },
            {
                "and": [
                    {
                        "operand": "b",
                        "operator": "==",
                        "value": "true"
                    },
                    {
                        "not": {
                            "operand": "a",
                            "operator": "==",
                            "value": "true"
                        }
                    }
                ]
            }
        ]
    }}
    

    I wrote next classes hierarchy:

    public interface Condition {}
    
    
    public class Expression implements Condition { 
       public Expression(String operator, String operand, String value) {
       } 
    }
    
    
    public class Not implements Condition { 
       public Not(Condition condition) {
       }
    }
    
    public abstract class GroupOperation implements Condition {
       public GroupOperation (List<Condition> conditions) {
       }
    }
    
    public class And extends GroupOperation { 
       public And(List<Condition> conditions) {
       }
    }
    
    public class Or extends GroupOperation { 
       public Or(List<Condition> conditions) {
       }
    }
    

    I've added next jackson annotations in hope to deserialize JSON above:

    @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
    @JsonSubTypes({
        @JsonSubTypes.Type(value=Not.class, name="not"),
        @JsonSubTypes.Type(value=And.class, name="and"),
        @JsonSubTypes.Type(value=Or.class, name="or"),
        @JsonSubTypes.Type(value=Expression.class, name=""),
    })
    

    I marked appropriate constructors as @JsonCreator.

    This doesn't work for Expression class.


    If I modify JSON that every expression object has the name "expression":

    "expression" : {
        "operand": "a",
        "operator": "==",
        "value": "true"
    }
    

    And

    @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
    @JsonSubTypes({
        @JsonSubTypes.Type(value=Not.class, name="not"),
        @JsonSubTypes.Type(value=And.class, name="and"),
        @JsonSubTypes.Type(value=Or.class, name="or"),
        @JsonSubTypes.Type(value=Expression.class, name="expression"),
    })
    

    It fails when trying to parse "not" condition saying that "can't instantiate abstract class need more information about type". So looks like it loses annotations declaration in deeper parsing.


    1. I wonder if it's possible to write deserialization with jackson for original JSON

    2. Why second approach doesn't work for Not deserialization

    • Tom Carchrae
      Tom Carchrae over 11 years
      it would be helpful if you posted the actual class hierarchy - the code above does not look like it would compile
    • Eugen Martynov
      Eugen Martynov over 11 years
      Modified code to be java code. The full source is here: github.com/emartynov/spil-games-assignment/tree/master/…
    • Visruth
      Visruth over 11 years
      Correct thess lines : Not implement Condition to Not implements Condition public class And() extends to public class And extends public class Or() extends to public class Or extends
  • Eugen Martynov
    Eugen Martynov over 11 years
    Tom, thank you for reply. Could you propose classes hierarchy from json example above which will be correctly de-serialized by Jackson?
  • Eugen Martynov
    Eugen Martynov over 11 years
    See me comment to question please
  • Tom Carchrae
    Tom Carchrae over 11 years
    Sorry - I'm really busy at the moment. I'd suggest making no-arg constructors to your classes as a first step. Also, work from the top of your hierarchy down. The simple class "public class Condtion { Map<String,Object> condition; }" should de-serialize by default, then inspect the result and slowly add more specific mappings for each element.
  • Eugen Martynov
    Eugen Martynov over 11 years
    How to map Expresion class? It's unnamed object in json
  • Eugen Martynov
    Eugen Martynov over 11 years
    Chris, thank you for answer. Could you provide your json example or look at mine? It is almost working my code but with some modifications to json.
  • Tom Carchrae
    Tom Carchrae over 11 years
    You don't need names for the object - if you are referring to these [{"operand":"a","operator": "==","value": "true"}] that can just be a List<Expression> expressions - where expression has three fields.
  • Eugen Martynov
    Eugen Martynov over 11 years
    Tom, take a look on the json - "and" and "or" contains a list of Condition basically other "and", "or" as well "not" or simple Expression
  • Tom Carchrae
    Tom Carchrae over 11 years
    sure - i saw that. you have disjunction/or at the top level, which contains a list of conjunctions/and at the next level, and then a list of expression in each conjunction. does it need to be more expressive than that? it would seem a badly designed api if you have to inspect the structure of the data to determine the type of something!
  • Eugen Martynov
    Eugen Martynov over 11 years
    So you suggest me to remove interface Condition and have base real class Expression and all other operations should be inherited from it?