Json 包含任何字段到 POJO 的模式

Json schema with anyOf fields to POJO

我想知道为具有“anyOf”字段的 Json 模式生成 POJO 的推荐方法是什么?

例如,给定以下 json 模式:

hobby.json
{
    "anyOf": [
        { "type": {"$ref": "./exercise.json" } },
        { "type": {"$ref": "./music.json" } }
    ]    
}
exercise.json
{
    "type": "object"
    "properties" {
        "hobbyType": {"type": "string"}
        "exerciseName": { "type": "string" },
        "timeSpent": { "type": "number" },
        "place": { "type": "string" }
    }
}
music.json
{
    "type": "object"
    "properties" {
        "hobbyType": {"type": "string"}
        "instrument": { "type": "string" },
        "timeSpent": { "type": "number" }
    }
}

如何使用 Jackson 为 Hobby.java 生成 POJO?

我认为有两种方法看起来很自然:

一种方法是生成一个 class 层次结构 Hobby,其公共字段 timeSpent 和 Music / Exercise 是其特定字段的子classes。

另一种方法是将这些字段“联合”成一个 class 爱好。

两者在语义上都不正确,这意味着您可能会遇到 JSON 模式验证正确但 Jackson 抛出错误或 POJO 中由于省略字段而丢失信息的情况。

所以我认为最好的方法是求助于 Map 而不是 pojos。

例如,如果一个人有一个爱好,那么这个人的 POJO 可能是:

class Person {
  String name;
  ...
  Map<String, Object> hobby;

或 List hobbies> 如果一个人可以有多种爱好。

我最终采用的方法是使用 Jackson 提供的多态 marshaling/unmarshaling 功能。

具体来说-

  1. hobby设为接口,并用@JsonTypeInfo@JsonSubTypes
  2. 注解
@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    property = "hobbyType",
    include = JsonTypeInfo.As.EXISTING_PROPERTY,
    visible = true
)
@JsonSubTypes({
        @Type(value = Exercise.class, name = "exercise"),
        @Type(value = Music.class, name = "music")
})
public interface Hobby {
}
  1. 创建实现此接口的Exercise.javaMusic.java
@Builder
@Data
@AllArgsConstructor
public class Exercise implements Hobby {
    @JsonProperty("hobbyType")
    @Builder.Default
    @NonNull
    private final String hobbyType = "exercise";    

    @JsonProperty("exerciseName")
    private String exerciseName;

    @JsonProperty("place")
    private String place;
    //... (other fields)
}
  1. 使用Hobby进行序列化和反序列化。
// create a Hobby object
Hobby exercise = Exercise.builder().exerciseName("swimming").place("swimmingPool").build();

// serialization
String serializedHobby = new ObjectMapper.writeValueAsString(exercise)
/**
serializedHobby looks like this ->
{
    "hobbyType": "exercise",
    "exerciseName": "swimming",
    "place": "swimmingPool"
}
*/

// deserialization
Hobby deserializedObject = new ObjectMapper.readValue(jsonString, Hobby.class)
// deserializedObject.getClass() would return Exercise.java or Music.java based on the hobbyType

参考:https://www.baeldung.com/jackson-inheritance