Immutable/polymorphic POJO <-> JSON serialization with Jackson

蓝咒 提交于 2019-12-09 05:27:48

问题


I'm trying to serialize a immutable POJO to and from JSON, using Jackson 2.1.4, without having to write a custom serializer and with as few annotations as possible. I also like to avoid having to add unnecessary getters or default constructors just to satisfy the Jackson library.

I'm now stuck on the exception:

JsonMappingException: No suitable constructor found for type [simple type, class Circle]: can not instantiate from JSON object (need to add/enable type information?)

The code:

public abstract class Shape {}


public class Circle extends Shape {
  public final int radius; // Immutable - no getter needed

  public Circle(int radius) {
    this.radius = radius;
  }
}


public class Rectangle extends Shape {
  public final int w; // Immutable - no getter needed
  public final int h; // Immutable - no getter needed

  public Rectangle(int w, int h) {
    this.w = w;
    this.h = h;
  }
}

The test code:

ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); // Adds type info

Shape circle = new Circle(10);
Shape rectangle = new Rectangle(20, 30);

String jsonCircle = mapper.writeValueAsString(circle);
String jsonRectangle = mapper.writeValueAsString(rectangle);

System.out.println(jsonCircle); // {"@class":"Circle","radius":123}
System.out.println(jsonRectangle); // {"@class":"Rectangle","w":20,"h":30}

// Throws:
//  JsonMappingException: No suitable constructor found.
//  Can not instantiate from JSON object (need to add/enable type information?)
Shape newCircle = mapper.readValue(jsonCircle, Shape.class);
Shape newRectangle = mapper.readValue(jsonRectangle, Shape.class);

System.out.println("newCircle = " + newCircle);
System.out.println("newRectangle = " + newRectangle);

Any help is greatly appreciated, thanks!


回答1:


You could (according to the API) annotate the constructor with @JsonCreator and the parameters with @JsonProperty.

public class Circle extends Shape {
    public final int radius; // Immutable - no getter needed

    @JsonCreator
    public Circle(@JsonProperty("radius") int radius) {
        this.radius = radius;
    }
}

public class Rectangle extends Shape {
    public final int w; // Immutable - no getter needed
    public final int h; // Immutable - no getter needed

    @JsonCreator        
    public Rectangle(@JsonProperty("w") int w, @JsonProperty("h") int h) {
        this.w = w;
        this.h = h;
    }
}

Edit: Maybe you have to annotate the Shape class with @JsonSubTypes so that the concrete subclass of Shape could be determined.

@JsonSubTypes({@JsonSubTypes.Type(Circle.class), @JsonSubTypes.Type(Rectangle.class)})
public abstract class Shape {}



回答2:


Have a look at Genson library some of its key features are adressing your exact problem: polymorphism, not requiring annotations and most important immutable pojos. Everything works in your example with 0 annotations or heavy conf.

Genson genson = new Genson.Builder().setWithClassMetadata(true)
                            .setWithDebugInfoPropertyNameResolver(true)
                            .create();

String jsonCircle = genson.serialize(circle);
String jsonRectangle = genson.serialize(rectangle);

System.out.println(jsonCircle); // {"@class":"your.package.Circle","radius":123}
System.out.println(jsonRectangle); // {"@class":"your.package.Rectangle","w":20,"h":30}

// Throws nothing :)
Shape newCircle = genson.deserialize(jsonCircle, Shape.class);
Shape newRectangle = genson.deserialize(jsonRectangle, Shape.class);

Genson gives you also the ability to use aliases (used instead classes names).

new Genson.Builder().addAlias("shape", Shape.class)
                .addAlias("circle", Circle.class)
                .create();



回答3:


Rectangle has two parameters, and the FAQ says:

Deserializing simple types

If I want to deserialize simple JSON values (Strings, integer / decimal numbers) into types other than supported by default, do I need to write a custom deserializer?

Not necessarily. If the class to deserialize into has one of:

  • Single-argument constructor with matching type (String, int/double), or
  • Single-argument static method with name "valueOf()", and matching argument type

Jackson will use such method, passing in matching JSON value as argument.

I'm afraid you have to write your own deserializer as show in the Jackson documentation:

ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule =
   new SimpleModule("MyModule", new Version(1, 0, 0, null))
      .addDeserializer( MyType.class, new MyTypeDeserializer());
mapper.registerModule( testModule );


来源:https://stackoverflow.com/questions/15121643/immutable-polymorphic-pojo-json-serialization-with-jackson

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!