【问题标题】:Serializing map having value of different types to JSON using Jackson in Java使用Java中的Jackson将具有不同类型值的映射序列化为JSON
【发布时间】:2020-10-14 09:43:58
【问题描述】:

我想将给定的 hashmap 序列化为 json 并将其反序列化回原始映射。

在这里,我想将其设为通用,以便无论值的类型如何,它都能无缝运行。

我正在使用以下代码 sn-p 构建地图,然后将其序列化为 json:

Map<String, Map<String, Object>> argumentNameValueMap = new HashMap<>();

for (int i = 0; i < codeSignature.getParameterNames().length; i++) {
  argumentNameValueMap.put(codeSignature.getParameterNames()[i],
      mapper.convertValue(joinPoint.getArgs()[i],
          Map.class)); <----THIS LINE IS FAILING WHEN ARGUMENT VALUE IS OF PRIMITIVE TYPE.
}

return mapper.writeValueAsString(argumentNameValueMap);

当要放入映射的值的类型是对象但当值是任何原始类型(即字符串/整数等)时失败,这可以正常工作。

我可以通过编写检查关联的值类型是否为任何原始类型并相应地添加 if else 来处理此问题。

但我想知道是否有更好的方法来做到这一点。谢谢。

【问题讨论】:

  • 我不明白这句话“但是当它是任何原始类型时失败,即字符串/整数等”。 String 和 Integer 是对象,不是原始类型。
  • @k314159 当然。通过这句话,我的意思是如果该值是任何原始类型,那么 OP 中的代码将不起作用。
  • 什么是codeSignature和joinPoint?
  • @k314159 它们与 Spring 方面有关。在这里,我主要关心的是在映射中捕获参数名称以及参数值并将映射序列化为 json。

标签: java json jackson objectmapper


【解决方案1】:

JSON 规范中识别的值为:JSON Object - {...}JSON Array - [...]stringnumberfalsetruenull。只有JSON Object可以默认反序列化为MapMap可以序列化为JSON Object

在您的情况下,您需要手动处理其他类型并以某种方式将它们转换为 Map 实例。您可以实现自己的com.fasterxml.jackson.databind.deser.DeserializationProblemHandler,它允许在出现问题时拦截转换机制。

您可以在下面找到一个简单的实现方法:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class JsonApp {

    public static void main(String[] args) {
        Map<String, Object> source = new HashMap<>();
        source.put("pojo", new SomeClass());
        source.put("string", "String-Value");
        source.put("int", 1);
        source.put("null", null);
        source.put("char", 'A');
        source.put("long", Long.MIN_VALUE);
        source.put("list", Arrays.asList(1, 3));
        source.put("array", new int[]{12, 13});

        ObjectMapper mapper = new ObjectMapper();
        mapper.addHandler(new Convert2MapDeserializationProblemHandler());

        Map<String, Map<String, Object>> argumentNameValueMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : source.entrySet()) {
            argumentNameValueMap.put(entry.getKey(), mapper.convertValue(entry.getValue(), Map.class));
        }
        argumentNameValueMap.forEach((k, v) -> System.out.println(k + " -> " + v));
    }
}

class Convert2MapDeserializationProblemHandler extends DeserializationProblemHandler {
    @Override
    public Object handleMissingInstantiator(DeserializationContext ctxt, Class<?> instClass, ValueInstantiator valueInsta, JsonParser p, String msg) throws IOException {
        if (Map.class.isAssignableFrom(instClass)) {
            Map<String, Object> map = new LinkedHashMap<>();
            TreeNode value = p.readValueAsTree();
            map.put("value", value);
            return map;
        }
        return super.handleMissingInstantiator(ctxt, instClass, valueInsta, p, msg);
    }

    @Override
    public Object handleUnexpectedToken(DeserializationContext ctxt, JavaType targetType, JsonToken t, JsonParser p, String failureMsg) throws IOException {
        if (Map.class.isAssignableFrom(targetType.getRawClass())) {
            Map<String, Object> map = new LinkedHashMap<>();
            TreeNode value = p.readValueAsTree();
            map.put("value", value);
            return map;
        }
        return super.handleUnexpectedToken(ctxt, targetType, t, p, failureMsg);
    }
}

class SomeClass {
    String stringField = "Value!";

    public String getStringField() {
        return stringField;
    }

    public void setStringField(String stringField) {
        this.stringField = stringField;
    }

    @Override
    public String toString() {
        return "SomeClass{" +
                "stringField='" + stringField + '\'' +
                '}';
    }
}

上面的代码打印:

pojo -> {stringField=Value!}
string -> {value="String-Value"}
null -> null
array -> {value=[12,13]}
char -> {value="A"}
list -> {value=[1,3]}
int -> {value=1}
long -> {value=-9223372036854775808}

【讨论】:

  • @michael-ziober 感谢您的回答。一个小问题。我在地图中看到,您有一个硬编码的键作为“值”。是否可以在这里传递我想放入地图的密钥?
  • @Joy,在那个级别上,键名是未知的,只有值。也许您不应该将“not-POJO”值转换为Map?在转换为Map 之前检查这一点应该容易得多。如果它是一个原语,您可以只使用一个键值对创建一个 Map 而不要运行转换逻辑。
  • @michael-ziober,实际上我已经按照您的建议尝试了使用 Google-Guava 的 isPrimitive()/isPrimitiveWrapper() API 以及 Apache commons 的 ClassUtils 的方法。两者都给了类 Java.lang.String 的值 false。
  • @Joy,您可以随时检查包,如果类来自java.lang,您可以将其视为原语。我了解您正在实现某些方面,这些值是方法的参数,因此它应该是一种合理的方式。
猜你喜欢
  • 1970-01-01
  • 2018-02-28
  • 1970-01-01
  • 2017-03-27
  • 2023-03-30
  • 2017-07-28
  • 1970-01-01
  • 2015-04-10
  • 2011-10-06
相关资源
最近更新 更多