JSON 通用集合反序列化

JSON generic collection deserialization

我有这样的 DTO classes 写在 Java:

public class AnswersDto {
    private String uuid;
    private Set<AnswerDto> answers;
}

public class AnswerDto<T> {
    private String uuid;
    private AnswerType type;
    private T value;
}

class LocationAnswerDto extends AnswerDto<Location> {
}

class JobTitleAnswerDto extends AnswerDto<JobTitle> {
}

public enum AnswerType {
    LOCATION,
    JOB_TITLE,
}

class Location {
    String text;
    String placeId;
}

class JobTitle {
    String id;
    String name;
}

在我的项目中有 Jackson 库用于 JSON 的序列化和反序列化。

如何配置 AnswersDto(使用特殊注释)或 AnswerDto(也使用注释)classes 以便能够正确反序列化其中包含 AnswersDto 的请求正文,例如:

{
    "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
    "answers": [
        {
            "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
            "type": "LOCATION",
            "value": {
                "text": "Dublin",
                "placeId": "121"
            }
        },
        {
            "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
            "type": "JOB_TITLE",
            "value": {
                "id": "1",
                "name": "Developer"
            }
        }
    ]
}

不幸的是,Jackson 默认将 AnswerDto 对象的值映射到 LinkedHashMap 而不是正确(LocationJobTitle)class 类型的对象。 我应该编写自定义 JsonDeserializer<AnswerDto> 还是使用 @JsonTypeInfo@JsonSubTypes 进行配置就足够了?

只用一个 AnswerDto

的形式正确反序列化请求
{
    "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
    "type": "LOCATION",
    "value": {
        "text": "Dublin",
        "placeId": "121"
    }
}

我正在使用:

AnswerDto<Location> answerDto = objectMapper.readValue(jsonRequest, new TypeReference<AnswerDto<Location>>() {
});

没有任何其他自定义配置。

我的建议是为可能的答案值制作一个单独的界面,并在其上使用 @JsonTypeInfo。您还可以从 AnswerDtoAnswerType 枚举和其他 *AnswerDto classes 中删除 type 字段,因为 jackson 会为您添加类型信息。像这样

public class AnswerDto<T extends AnswerValue> {
    private String uuid;
    private T value;
}

@JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY)
interface AnswerValue {}

class Location implements AnswerValue { /*..*/ }
class JobTitle implements AnswerValue { /*..*/ }

结果 json 将如下所示

{
  "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
  "answers": [
    {
      "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
      "value": {
        "@class": "com.demo.Location",
        "text": "Dublin",
        "placeId": "121"
      }
    },
    {
      "uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
      "value": {
        "@class": "com.demo.JobTitle",
        "id": "1",
        "name": "Developer"
      }
    }
  ]
}

将使用

解析
AnswersDto answersDto = objectMapper.readValue(json, AnswersDto.class);

但此解决方案仅适用于您是 json 数据的生产者并且您不必考虑向后兼容性的情况。

在其他情况下,您必须为 AnswersDto class.

制作自定义 desetializer

我已经使用 Jackson 的自定义注释解决了问题 @JsonTypeInfo@JsonSubTypes:

public class AnswerDto<T> {

    private String uuid;

    private AnswerType type;

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
    @JsonSubTypes({
            @JsonSubTypes.Type(value = Location.class, name = AnswerType.Types.LOCATION),
            @JsonSubTypes.Type(value = JobTitle.class, name = AnswerType.Types.JOB_TITLE)
    })
    private T value;
}