Неизменяемая/полиморфная 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);
Любая помощь очень ценится, спасибо!
Ответы
Ответ 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 );