将Typescript动态类型Map到Java对象的子类

3duebb1j  于 2023-03-06  发布在  Java
关注(0)|答案(1)|浏览(169)

我有一个Typescript模型作为JSON请求发送到我的后端服务器。我不能将模型更改为更工程化的格式,所以我必须使用客户端将要发送给我的格式。
Typescript接口使用管道执行多态性,如下所示:

export interface Field {
  align: 'center' | 'left'; // css property that does not work  disabled: boolean;
  label: string;
  readonly: boolean;
  ref: string; // unique identifier  size: string; // css width  type: FIELD_TYPES;
  value: string;
  required: boolean;
  visible: string; // 'true' | 'plugin:...'  
  invisible: 'true' | 'false'; // never used  
  optional: string; // just in 3 fields, never used 
  option:
    | string // 'plugin:...'    
    | { value: string; label: string; disable?: string; labelEng?: string, valueOfAnswerInInterestType?: string }[] //FieldOption
    | { 1?: string; 2?: string; 3?: string; 4?: string; 5?: string; 6?: string };  //Map<Integer,String>
}

如您所见,option可以是String、数组或普通POJO。
我需要实现的是创建一个JacksonMap,并使用Java强大的instanceof操作符处理反序列化的类。
看看这个目标模型:

@Data
public class Field {    
    private DetFieldAlignment align; // css property that does not work  disabled: boolean;    
    ....
    private String optional;//  just in 3 fields, never used    
   
    private Object option; // 'plugin:...' | [{ value: 1, label: 'my value' }, ...] | { "1": "Option one ..."}
}

@Data
public class DetFieldOption {
    private String value;
    private String label;
    private String disable;
    ....
}

我需要指导Jackson对Object的可能子类进行多态序列化,但我遇到了困难。我编写了JUnit测试来验证结果(option instanceof String vs检查列表中的每个项目是否都是instanceof FieldOption),但显然它不起作用。
使用上面的模型,在我的测试负载中可以识别String,但是FieldOption数组转换为LinkedHashMap数组
在没有处理Map<Integer,String>的情况下,我尝试将以下多态注解添加到Object option字段

@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)    
    @JsonSubTypes({
        @JsonSubTypes.Type(String.class),            
        @JsonSubTypes.Type(FieldOption.class)     
    })

它不起作用,但以下情况除外

Caused by: java.lang.IllegalStateException: Subtypes java.lang.String and java.lang.Object have the same signature and cannot be uniquely deduced.
    at com.fasterxml.jackson.databind.jsontype.impl.AsDeductionTypeDeserializer.buildFingerprints(AsDeductionTypeDeserializer.java:89)
    at com.fasterxml.jackson.databind.jsontype.impl.AsDeductionTypeDeserializer.<init>(AsDeductionTypeDeserializer.java:48)
    at com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder.buildTypeDeserializer(StdTypeResolverBuilder.java:166)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findPropertyTypeDeserializer(BasicDeserializerFactory.java:2022)
    ... 89 more

我不能控制来自前端的有效负载,这意味着我不能要求FE开发人员在有效负载中添加类型指示,特别是考虑到对象可能是String。

问题

如何定义一个多态序列化,使其反序列化为:

  • java.lang.String
  • java.util.Collection
  • java. util. Map〈整数,字符串〉

当然,我不希望键是整数以外的值

8i9zcol2

8i9zcol21#

因为你想把一个多态值封装到Field类的Object字段中,所以这个任务可以用一个定制的StdDeserializer反序列化器来解决,它可以区分你指定的三种情况(字符串、数组和Map)。

@Data
public class Field {
    @JsonDeserialize(using = OptionDeserializer.class)
    private Object option;
}

@Data
public class DetFieldOption {
    private String value;
    private String label;
}

您可以定义一个自定义stdDeserializer,该自定义stdDeserializer可以在您指定的三种情况下返回不同的对象,检查JsonNode选项是字符串、数组还是对象:

public class OptionDeserializer extends StdDeserializer<Object> {

    public OptionDeserializer() {
        super(Object.class);
    }

    @Override
    public Object deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        ObjectCodec codec = jp.getCodec();
        JsonNode node = codec.readTree(jp);
        //jsonnode is an array so it will be converted to DetFieldOption[]
        if (node.isArray()) {
            return codec.treeToValue(node, DetFieldOption[].class);
        }
        //jsonnode is a pojo so to convert it to a Map<String, String>
        //it is necessary to define a new TypeReference
        //no key as Integer because in a json keys are always string
        //but you can convert them to integer if you need
        if (node.isObject()) {
            TypeReference typeRef = new TypeReference<Map<String, String>>(){};
            Map<String, String> map = codec.readValue(codec.treeAsTokens(node),
                    dc.getTypeFactory().constructType(typeRef));
            return map;
        }
        //default case node is a string
        return node.asText();
    }
}

然后,您可以将如下所示的json字符串转换为Field对象:

String json3 = """
                    {"option": "myString"}
              """;

String json2 = """
                    {"option": [{"value": "myValue"}]}
               """;
String json3 = """
                    {"option": {"1": "myString1"}}
               """;
Field field1 = mapper.readValue(json1, Field.class);
Field field2 = mapper.readValue(json2, Field.class);
Field field3 = mapper.readValue(json3, Field.class);

相关问题