15

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

Zoe
  • 27,060
  • 21
  • 118
  • 148
Eugen Martynov
  • 19,888
  • 10
  • 61
  • 114
  • 1
    it would be helpful if you posted the actual class hierarchy - the code above does not look like it would compile – Tom Carchrae Jan 28 '13 at 20:21
  • 1
    Modified code to be java code. The full source is here: https://github.com/emartynov/spil-games-assignment/tree/master/service-core/src/main/java/com/spilgames/core/condition – Eugen Martynov Jan 28 '13 at 20:51
  • 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__ – Visruth Feb 04 '13 at 05:13

2 Answers2

18

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);
}
Chris Hinshaw
  • 6,967
  • 2
  • 39
  • 65
  • 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. – Eugen Martynov Jan 29 '13 at 15:38
1

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.

Tom Carchrae
  • 6,398
  • 2
  • 37
  • 36
  • Tom, thank you for reply. Could you propose classes hierarchy from json example above which will be correctly de-serialized by Jackson? – Eugen Martynov Jan 28 '13 at 20:45
  • 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 condition; }" should de-serialize by default, then inspect the result and slowly add more specific mappings for each element. – Tom Carchrae Jan 28 '13 at 21:02
  • How to map `Expresion` class? It's unnamed object in json – Eugen Martynov Jan 29 '13 at 09:08
  • 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 expressions - where expression has three fields. – Tom Carchrae Jan 29 '13 at 16:50
  • 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 – Eugen Martynov Jan 29 '13 at 19:38
  • 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! – Tom Carchrae Jan 29 '13 at 20:07
  • So you suggest me to remove interface Condition and have base real class Expression and all other operations should be inherited from it? – Eugen Martynov Jan 30 '13 at 06:04