Подтвердить что ты не робот

Неизменяемая/полиморфная POJO ↔ JSON-сериализация с Jackson

Я пытаюсь выполнить сериализацию неизменяемого POJO в JSON и с помощью JSON, используя Jackson 2.1.4, без необходимости писать собственный сериализатор и с минимальными аннотациями. Мне также нравится избегать добавления ненужных геттеров или конструкторов по умолчанию, чтобы удовлетворить библиотеку Джексона.

Теперь я застрял в исключении:

JsonMappingException: не найдено подходящего конструктора для типа [simple type, class Circle]: невозможно создать экземпляр из объекта JSON (нужно добавить/включить информацию о типе?)

Код:

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

Код проверки:

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);

Любая помощь очень ценится, спасибо!

4b9b3361

Ответ 1

Вы можете (согласно API) аннотировать конструктор @JsonCreator и параметры @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;
    }
}

Изменить: возможно, вам нужно будет аннотировать класс Shape с помощью @JsonSubTypes, чтобы можно было определить конкретный подкласс Shape.

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

Ответ 2

Посмотрите на библиотеку Genson, некоторые из ее ключевых функций касаются вашей точной проблемы: полиморфизм, не требующий аннотаций и наиболее важных неизменяемых POJOs. Все работает в вашем примере с помощью 0 аннотаций или тяжелых сообщений.

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 дает вам также возможность использовать псевдонимы (вместо имен классов).

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

Ответ 3

Прямоугольник имеет два параметра, а FAQ говорит:

Deserializing простых типов

Если я хочу десериализовать простые значения JSON (строки, integer/ десятичные числа) в типы, отличные от поддерживаемых по умолчанию, мне нужно написать собственный десериализатор?

Не обязательно. Если класс десериализации в имеет один из:

  • Конструктор с одним аргументом с подходящим типом (String, int/double) или
  • Статический метод с одним аргументом с именем "valueOf()" и тип соответствующего аргумента

Джексон будет использовать такой метод, передавая соответствующее значение JSON как аргумент.

Я боюсь, что вы должны написать свой дескриптор как показано в документации Jackson:

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