在 Java 中使用 Jackson 进行地图反序列化

Posted

技术标签:

【中文标题】在 Java 中使用 Jackson 进行地图反序列化【英文标题】:Map deserializing with Jackson in Java 【发布时间】:2015-02-01 17:02:31 【问题描述】:

我有以下课程

public class BetWrapper 

    private String description;
    private Calendar startTime;
    private HashMap<String, SimpleEntry<Integer, Double>> map;


    public BetWrapper() 
        map = new HashMap<>();
    

    public Calendar getStartTime() 
        return startTime;
    

    public void setStartTime(Calendar startTime) 
        this.startTime = startTime;
    

    public String getDescription() 
        return description;
    

    public void setDescription(String description) 
        this.description = description;
    

    public HashMap<String, SimpleEntry<Integer, Double>> getMap() 
        return map;
    

    public void setMap(HashMap<String, SimpleEntry<Integer, Double>> map) 
        this.map = map;
    


我正在使用 JSONUtil 类

public class JSONUtil 

    private JSONUtil() 

    public static <T> T fromJSON(String content, Class<T> clazz) throws TechnicalException 
        try 
            return new ObjectMapper().readValue(content, clazz);
         catch (IOException e) 
            throw new TechnicalException(e);
        
    

    public static String toJSON(Object obj) throws TechnicalException 
        try 
            return new ObjectMapper().writeValueAsString(obj);
         catch (JsonProcessingException ex) 
            throw new TechnicalException(ex);
        
    


我想将 JSON 反序列化为 BetWrapper 对象。但是下面的代码会产生一些异常。

BetWrapper betWrapper = new BetWrapper();
betWrapper.setDescription("Stoke City - Arsenal");
betWrapper.setStartTime(Calendar.getInstance());
HashMap<String, AbstractMap.SimpleEntry<Integer, Double>> map = new HashMap<>();
map.put("home_team", new AbstractMap.SimpleEntry<>(1, 2.85));
betWrapper.setMap(map);
String json = JSONUtil.toJSON(betWrapper);
JSONUtil.fromJSON(json, BetWrapper.class);

例外情况是:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class java.util.AbstractMap$SimpleEntry<java.lang.Integer,java.lang.Double>]: can not instantiate from JSON object (need to add/enable type information?)
at [Source: "description":"Stoke City - Arsenal","startTime":1417648132139,"map":"home_team":"key":1,"value":2.85; line: 1, column: 85] (through reference chain: by.bsu.kolodyuk.bettingapp.model.entity.BetWrapper["map"])

如何正确反序列化?似乎问题在于 SimpleEntry 中的类型 K,V 应该以某种方式为 Jackson 指定。

有什么想法吗?

【问题讨论】:

我们来看看SimpleEntry SimpleEntry 是 AbstractMap 的静态内部类 ***.com/questions/7625783/… 【参考方案1】:

SimpleEntry 类型有如下构造函数

public SimpleEntry(K key, V value) 
    this.key   = key;
    this.value = value;

默认情况下,Jackson 需要一个无参数的构造函数。如果这样的构造函数不存在,它会查找带有@JsonProperty 注释的构造函数。 (我可能有这个倒退,但永远不要这样编码。)因为你正在使用 JDK 类型,它显然不会有那些注释。

您可以使用 Mixin。 Jackson 使用这些作为模板来反序列化您的目标类型。

abstract class Mixin<K, V> 
    public Mixin(@JsonProperty("key") K key, @JsonProperty("value") V value)         
 
...
public static <T> T fromJSON(String content, Class<T> clazz) throws Exception 
    ObjectMapper mapper = new ObjectMapper();
    mapper.addMixInAnnotations(SimpleEntry.class, Mixin.class);
    return mapper.readValue(content, clazz);

Jackson 将使用元类型 Mixin,反序列化为 SimpleEntry 对象。

(注意,Mixin 的类型参数和构造函数参数类型并不重要。重要的是构造函数参数有两个,构造函数参数有注释。)

【讨论】:

正确,有一个小的改动:type 的构造函数参数确实很重要(名称不重要)。 @StaxMan 我昨天试过了,它没有抱怨。也许我没有做我认为的那样。让我再试一次。 @StaxMan 所以,是的。我也是那么想的。但是我只是为 mixin 构造函数参数设置了一些完全随机的类型,它仍然有效。 @SotiriosDelimanolis 很有趣。这实际上听起来像一个错误。谢谢!【参考方案2】:

在 Jackson 中,您可以定义自定义反序列化器。因此,对于您的情况,它可能如下所示:

import java.io.IOException;
import java.util.AbstractMap;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;


public class SomeDeserializer extends StdDeserializer<AbstractMap.SimpleEntry>

    public SomeDeserializer()
    
        super( AbstractMap.SimpleEntry.class );
    

    @Override
    public AbstractMap.SimpleEntry deserialize( JsonParser jp, DeserializationContext ctxt ) throws IOException, JsonProcessingException
    
        Integer key = null;
        Double value = null;

        JsonToken token;
        while ( ( token = jp.nextValue() ) != null )
        
            if ( token.isNumeric() )
            
                String propertyName = jp.getCurrentName();
                if ( "key".equalsIgnoreCase( propertyName ) )
                
                    key = jp.getIntValue();
                
                else if ( "value".equalsIgnoreCase( propertyName ) )
                
                    value = jp.getDoubleValue();
                
            
        

        if ( key != null && value != null )
        
            return new AbstractMap.SimpleEntry( key, value );
        
        return null;
    

应使用ObjectMapper.registerModule(Module m) 注册反序列化程序。在您的情况下,您可以在 JSONUtil 实用程序类中执行此操作:

SimpleModule module = new SimpleModule();
module.addDeserializer( AbstractMap.SimpleEntry.class, new SomeDeserializer() );

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule( module );

请注意,反序列化器是使用ObjectMapper 实例注册的。因此,您最好将实例存储为实用程序类的字段。

上面的反序列化器类不全面!这只是为了演示手头的案例。可以根据需要应用进一步的优化和重构。

【讨论】:

以上是关于在 Java 中使用 Jackson 进行地图反序列化的主要内容,如果未能解决你的问题,请参考以下文章

Java中使用Jackson进行JSON解析和序列化

Java下利用Jackson进行JSON解析和序列化

Java下利用Jackson进行JSON解析和序列化

Java下利用Jackson进行JSON解析和序列化1

Java下用Jackson进行JSON序列化和反序列化(转)

JPA / Jackson - 反序列化时排除字段并在序列化时包含它们