0

There's an interface, let's say A. I have multiple classes that implements that interface A. Those classes also consists of class variable of type A. So, it's like:

@JsonTypeInfo(use = JsonTypeInfo.Id.Class, include = JsonTypeInfo.As.Property, property = "className")

@JsonSubType({
 @Type(value = abstractClass.class, name = "abstractClass"),
 @Type(value = subClass1.class, name = "subClass1"),
 @Type(value = subClass2.class, name = "subClass2"),
 '
 '
 '
})
interface A {
 func1();
 func2();
}

abstract class abstractClass implements A {
 int abstractvar1;
 func1(){//code} 
}

class subClass1 extends abstractClass {
  int var1;
  int var2;
  A var3;
  A var4;
}

class subClass2 extends abstractClass {
  int var1;
  int var2;
  A var3;
}

class subClass3 extends abstractClass {
  float var1;
  int var2;
  A var3;
}

 and more classes defined trying to extend abstractClass..

Constructors, getters and setters are already defined.

Class which consists of all the variables

class Implementor {
 int implementorVar1;
 String implementorVar2;
 A implementorVar3;
 int implementorVar4;
}

So, I want to serialize the Implementor class into JSON. I'm using Jackson for the same. So, I added @jsonTypeInfo and @type to interface so that they have a concrete class to work upon. But when I try to serialize the subClasses, only var1 and var2 are serialized which is of int type, and not var3/var4 which are of type A. How can I serialize those variables too?

Json I'm getting if I try to serialize Implementor:

{
  "implementorVar1": 1,
  "implementorVar2": "hello",
  "implementorVar3": {
    "className": "subClass2",
    "abstractVar1": 45,
  },
  "implementorVar4": 1000
}

Json I'm expecting:

{
  "implementorVar1": 1,
  "implementorVar2": "hello",
  "implementorVar3": {
    "className": "subClass2",
    "abstractVar1" : 45,
    "var1": 45,
    "var2": 56,
    "var3": {
      "className": "subClass3",
      "var1": 2,
      "var2": 5,
      "var3" : {
        "className" : "" ...
     }
    }
  },
  "implementorVar4": 1000
}
TCodeMav
  • 3
  • 3
  • 1
    As an interface has no properties, I guess at best var3 and var4 would be serialised as empty objects. Do you want that outcome? If you deserialize at the other end into instances of subClass1 and subClass2 then the presence or not of those empty properties makes no difference. Maybe this is the rationale behind Jackson not including them, but what happens if for example you create an instance of subClass2 where var3 is initialised with an implementation of A that has properties (eg subClass1) I'm fairly sure Jackson would include var3 as an object with var1 and var2 as nested properties but I – Chris Mar 19 '20 at 21:55
  • @Chris Do you want that outcome? --> Nope, I would like to have the value of var3, var4 to be available in JSON itself, and while deserializing it too. – TCodeMav Mar 21 '20 at 19:01
  • @Chris But what happens if for example you create an instance of subClass2 where var3 is initialised with an implementation of A that has properties (eg subClass1) I'm fairly sure Jackson would include var3 as an object with var1 and var2 as nested properties --> Yes, var1 and var2 will definitely be serialized and deserialized properly, but var3/var4 will not be serialized/deserialized and hence if there's a subClass3 whose properties are defined within a subClass1, then that won't get serialized which means I'll end up losing information. – TCodeMav Mar 21 '20 at 19:01
  • Hi, I'm with you till the last bit about subClass3 - I think I get what you are asking, but to clarify, an implementation of an interface may or may not have properties. If it does they would get serialised as far as I know, but if it just implements behaviour without any properties then it won't. The problem then is when deserializing how do you populate var3 - what type to instantiate if there is no info. This is where I think type information can be added to the serialised JSON as a pseudoproperty - is this what you mean, I can look up a reference – Chris Mar 21 '20 at 20:58
  • Have a read of all of this page to see if it is describing your need - https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization – Chris Mar 21 '20 at 21:02
  • Ok sorry, I can see you are aware of that – Chris Mar 21 '20 at 21:03
  • 1
    So you mentioned your use of annotations, could you include examples of the annotated code, and also any of the generated JSON? Might help clarify – Chris Mar 21 '20 at 21:09
  • @Chris I'll update the question with the above information. – TCodeMav Mar 23 '20 at 08:49
  • Thanks, that extra detail helps illustrate the problem. Nothing jumps out immediately, though not sure you need to define abstractClass as a type. I'll reproduce when I get time to see what's happening. One thing is that for Serialisation, Jackson shouldn't need any extra information as it has the complete object graph there to work with, extra info is really just for deserialization. Hence to narrow down on the problem it's worth trying to serialise with NO annotations. If that works then annotations are wrong, if not then Jackson is struggling with class hierarchy. – Chris Mar 24 '20 at 06:30
  • @Chris, If I don't provide these annotations, then at the time of deserializtion of an iinstance of Implementor Class, Jackson will find in the JSON that there is an instance of interface A, but it will not be able to determine which class(either subClass1, subClass2..) does that particular JSON belong to. So, these annotations not only help at the time of deserialization, but also at the time of serialization to write in the serialized JSON that which particular implementation of the interface, does the following JSON belong to. – TCodeMav Mar 24 '20 at 08:52
  • Chris, sorry. Found another issue too, I didnt create the question coorectly. Apparently is missing out all the variables defined in the subClasses, not just interface type variables. It only seems to be serializing the abstrtact class variable. – TCodeMav Mar 24 '20 at 10:21
  • Hi, idea of removing annotations is just as an experiment to narrow down where the problem is, not a proposed solution - as you say, there would be no way Jackson could deserialize without some steer on types. – Chris Mar 24 '20 at 10:44
  • Yes, actually tried it removing the annotations too. The serialized JSON is same as the one posted in the question above. So clearly something else is missing, and not the annotations. – TCodeMav Mar 24 '20 at 11:23
  • Hi - I have successfully serialised your class structure with all primitives and polymorphic properties, but currently using the class name for the type property info. I just need to look up how to use those custom type mappings and will share what I have. I notice that in your code above, there are some details which must be incorrect eg it should be JsonTypeInfo.Id.CLASS, and JsonSubTypes rather than JsonSubType – Chris Mar 24 '20 at 13:31

1 Answers1

1

I reproduced your code with a few changes and it works for me when implemented as below (serialisation and deserialisation), so let me know if this matches your expectations. The main points to note are a couple of minor corrections to annotations, and I discovered that with default configuration it is absolutely crucial to have correct getters and setters else properties WILL NOT be serialised - that seems like the most likely problem.

Personally I would look into using configuration to allows Jackson to use properties directly as I hate blanket getters and setters that leak all your inner state publicly instead of encapsulating it and exposing specific behaviour but that's just opinion - not related to your question!

Output:

{
  "implementorVar1" : 1,
  "implementorVar2" : "hello",
  "implementorVar3" : {
    "className" : "subClass2",
    "var1" : 1,
    "var2" : 2,
    "var3" : {
      "className" : "subClass3",
      "var1" : 1.0,
      "var2" : 2,
      "var3" : {
        "className" : "subClass1",
        "var1" : 1,
        "var2" : 2
      }
    }
  },
  "implementorVar4" : 1000
}

Code snippets:

public static void main(String[] args) {
    Implementor target = new Implementor(1, "hello",
            new SubClass2(1, 2,
                    new SubClass3(1F, 2,
                            new SubClass1(1, 2))),
            1000);
    try {
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper
                .writerWithDefaultPrettyPrinter()
                .writeValueAsString(target);
        Implementor deserialised = mapper.readValue(json, Implementor.class);
        System.out.println(json);
        System.out.println(deserialised);
    } catch (Exception e) {
        e.printStackTrace();
    }
}


class Implementor {
    private int implementorVar1;
    private String implementorVar2;
    private A implementorVar3;
    private int implementorVar4;

    public Implementor() {}

    public Implementor(int implementorVar1, String implementorVar2, A implementorVar3, int implementorVar4) {
        this.implementorVar1 = implementorVar1;
        this.implementorVar2 = implementorVar2;
        this.implementorVar3 = implementorVar3;
        this.implementorVar4 = implementorVar4;
    }

    public int getImplementorVar1() {
        return implementorVar1;
    }

    public void setImplementorVar1(int implementorVar1) {
        this.implementorVar1 = implementorVar1;
    }
    // Other getters/setters omitted
    // Default configuration ABSOLUTELY requires getters and setters for all properties in all serialised classes
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "className")
@JsonSubTypes({
        @JsonSubTypes.Type(value = SubClass1.class, name = "subClass1"),
        @JsonSubTypes.Type(value = SubClass2.class, name = "subClass2"),
        @JsonSubTypes.Type(value = SubClass3.class, name = "subClass3")
})
interface A {
    int func1();
    int func2();
}


class SubClass1 extends AbstractClass {
    private int var1;
    private int var2;

    public SubClass1() {}

    public SubClass1(int var1, int var2) {
        this.var1 = var1;
        this.var2 = var2;
    }

    @Override
    public int func1() {
        return 0;
    }

    @Override
    public int func2() {
        return 0;
    }

    // getters and setters omitted but they HAVE to be there
}

class SubClass2 extends AbstractClass {
    private int var1;
    private int var2;
    private A var3;

    public SubClass2() {}

    public SubClass2(int var1, int var2, A var3) {
        this.var1 = var1;
        this.var2 = var2;
        this.var3 = var3;
    }
    // getters and setters omitted but they HAVE to be there
}

class SubClass3 extends AbstractClass {
    private float var1;
    private int var2;
    private A var3;

    public SubClass3() {}

    public SubClass3(float var1, int var2, A var3) {
        this.var1 = var1;
        this.var2 = var2;
        this.var3 = var3;
    }

    @Override
    public int func1() {
        return 0;
    }

    @Override
    public int func2() {
        return 0;
    }
    // getters and setters omitted but they HAVE to be there
}
Chris
  • 1,644
  • 1
  • 11
  • 15