Search code examples
javajsoninterfacejacksonjson-serialization

How to serialize/ deserialize via jackson when class contains variables of type interface?


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
}

Solution

  • 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
    }