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

时间:2020-02-19 04:06:55

标签: java json jackson

我有一个像这样的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的实例(尽管至少存在一个Creator):无法从Object值反序列化(没有委托或基于资源的创作者)

我已经上传了所有源代码here

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

3 个答案:

答案 0 :(得分:3)

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

@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");
    }
}

答案 1 :(得分:1)

这是我的解决方案,它使用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);

答案 2 :(得分:0)

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

public class Property{

//...

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

或者如果您想使用列表对象:

public class Property{

//...

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

或简单地使用@cassiomolin的答案