繁体   English   中英

无法使用自定义反序列化器反序列化 JSON 对象

[英]Can't deserialize JSON object using custom deserializer

我有一个像这样的 JSON 文件:

{
  "Properties": {
    "String": "one-string-value",
    "Number": 123,
    "LiteralList": [
      "first-value",
      "second-value"
    ],
    "Boolean": true,
    "ReferenceForOneValue": {
      "Ref": "MyLogicalResourceName"
    }
  }
}

我想使用 Jackson 将其反序列化为适当的Map<String,Property> 为此,我像这样使用TypeReference

public class Template {

    @JsonProperty("Properties")
    @JsonDeserialize(using = PropertyDeserializer.class)
    public Map<String, Object> propertyList;
}

public class PropertyDeserializer extends JsonDeserializer<Map<String, Property>> {
    public Map<String, Property> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
            throws IOException, JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        Map<String,Property> result =
                mapper.readValue(jsonParser, new TypeReference<Map<String,Property>>() {});

        return result;
    }
}

每个Property只能取一些值:字符串、整数、布尔值、字符串列表或具有一个键“Ref”的另一个对象。 所以我写了这个:

public class Property {

    private Object value;

    public Property(String value) {
        this.value = value;
    }

    public Property(int value) {
        this.value = value;
    }

    public Property(List<String> values) {
        this.value = values;
    }
    public Property(boolean value) {
        this.value = value;
    }

    public Property(Ref reference) {
        this.value = reference;
    }

}

public class Ref {

    private String reference;

    @JsonCreator
    public Ref(@JsonProperty("Ref")  String reference) {
        this.reference = reference;
    }
}

但是,这不起作用:

  1. 对于LiteralList属性,我收到错误无法从 START_ARRAY 令牌反序列化Property实例

  2. 对于ReferenceForOneValue属性,我收到错误无法构造Property实例(尽管至少存在一个创建者):无法从对象值反序列化(没有基于委托或属性的创建者)

我已经在这里上传了所有的源代码。

有人可以帮我为此编写一个反序列化器吗?

一种有效的方法是编写您的自定义创建者:

@JsonCreator
public Property(JsonNode node) {
    switch (node.getNodeType()) {
        case STRING:
            value = node.asText();
            break;
        case NUMBER:
            value = node.asInt();
            break;
        case BOOLEAN:
            value = node.asBoolean();
            break;
        case ARRAY:
            Iterator<JsonNode> elements = node.elements();
            List<String> list = new ArrayList<>();
            while (elements.hasNext()) {
                JsonNode element = elements.next();
                if (element.isTextual()) {
                    list.add(element.textValue());
                } else {
                    throw new RuntimeException("invalid data type");
                }
            }
            value = list;
            break;
        case OBJECT:
            try {
                value = new ObjectMapper().treeToValue(node, Ref.class);
            } catch (JsonProcessingException e) {
                throw new RuntimeException("invalid data type", e);
            }
            break;
        default:
            throw new RuntimeException("invalid data type");
    }
}

这是我的解决方案,它使用 Java 反射将属性构造函数与 json 数据类型匹配。 我不得不稍微修改属性构造函数以匹配 Jackson 反序列化(使用包装器类型而不是原语)。 此外,引用类型是从 Map 构建的( Reference类已去除所有 Jackson 注释)

属性类:

import java.util.List;
import java.util.Map;

public class Property {

    private Object value;

    public Property(String value) {
        this.value = value;
    }

    public Property(Integer value) {
        this.value = value;
    }

    public Property(List<String> values) {
        this.value = values;
    }

    public Property(Boolean value) {
        this.value = value;
    }

    public Property(Ref reference) {  // not used
        this.value = reference;
    }

    // reference is received as map 
    public Property(Map<String, String> map) {
        if (map.containsKey("Ref")) {
            this.value = new Ref(map.get("Ref"));
        } else {
            throw new IllegalArgumentException("invalid reference property");
        }
    }

    @Override
    public String toString() {
        return value.toString();
    }
}

模板类

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class Template {

    @JsonIgnore
    public Map<String, Property> propertyList = new HashMap<>();

    // map of data type from json to property constructor
    private static Map<Class<?>, Constructor<Property>> propertyConstructors;

    // build propertyConstructors map
    static {
        propertyConstructors = new HashMap<>();
        @SuppressWarnings("unchecked")
        Constructor<Property>[] constructors = (Constructor<Property>[])Property.class.getConstructors();
        for (Constructor<Property> ctor : constructors) {
            if (ctor.getParameterCount() == 1) {
                propertyConstructors.put(ctor.getParameterTypes()[0], ctor);
            }
        }
    }

    @JsonProperty("Properties")
    public void setProperties(Map<String, Object> jsonProperties) {
        if (jsonProperties == null  ||  jsonProperties.isEmpty()) return;
        for (Map.Entry<String, Object> property : jsonProperties.entrySet()) {
            Optional<Constructor<Property>> optionalCtor =
                propertyConstructors.keySet().stream()
                    .filter(argType -> argType.isAssignableFrom(property.getValue().getClass()))
                    .map(argType -> propertyConstructors.get(argType))
                    .findFirst();
            if (optionalCtor.isPresent()) {
                try {
                    propertyList.put(property.getKey(), optionalCtor.get().newInstance(property.getValue()));
                } catch (ReflectiveOperationException e) {
                    throw new IllegalArgumentException("invalid property " + property.getKey());
                }
            }
        }
    }
}

反序列化很简单:

Template t = mapper.readValue(in, Template.class);

根据我的经验,Jackson 不喜欢整个 List 界面。

public class Property{

//...

  public Property(String[] list){
    this.value = list;
  }
//...

或者如果你想要一个 List 对象:

public class Property{

//...

  public Property(String[] list){
    this.value = Arrays.asList(list);
  }
//...

或者干脆使用@cassiomolin 的答案

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM