使用Jackson反序列化非字符串映射键

30

我有一个类似这样的地图:

public class VerbResult {
    @JsonProperty("similarVerbs")
    private Map<Verb, List<Verb>> similarVerbs;
}

我的动词类看起来是这样的:

public class Verb extends Word {
    @JsonCreator
    public Verb(@JsonProperty("start") int start, @JsonProperty("length") int length,
            @JsonProperty("type") String type, @JsonProperty("value") VerbInfo value) {
        super(length, length, type, value);
    }
    //...
}

我想对我的VerbResult类的实例进行序列化和反序列化,但这样做时会出现以下错误:Can not find a (Map) Key deserializer for type [simple type, class my.package.Verb]

我在网上看到需要告诉Jackson如何反序列化映射键,但没有找到任何关于如何做到这一点的信息。Verb类还需要在映射之外进行序列化和反序列化,因此任何解决方案都应该保留这个功能。

谢谢您的帮助。


1
你读过 https://dev59.com/MGw15IYBdhLWcg3w1vOB 吗?乍一看,他的情况与你的相似。 - fvu
1
是的,我已经阅读了那篇文章,但实际上我没有找到其中的答案。我该如何使用一个模块来解决这个问题? - Max
Verb是一个POJO吗? - Kalle Richter
5个回答

43

经过一天的搜索,我发现了一个更简单的方法,它基于这个问题。解决方案是在映射中添加@JsonDeserialize(keyUsing = YourCustomDeserializer.class)注释。然后通过扩展KeyDeserializer并覆盖deserializeKey方法来实现自定义反序列化器。该方法将使用字符串键调用,您可以使用该字符串来构建真正的对象,甚至从数据库中获取现有对象。

因此,在映射声明中首先:

@JsonDeserialize(keyUsing = MyCustomDeserializer.class)
private Map<Verb, List<Verb>> similarVerbs;

然后创建反序列化器,该反序列化器将使用字符串键进行调用。

public class MyCustomDeserializer extends KeyDeserializer {
    @Override
    public MyMapKey deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        //Use the string key here to return a real map key object
        return mapKey;
    }
}

适用于Jersey和Jackson 2.x


我一直在尝试将注释放在关键类本身上(在您的情况下是 Verb),而不是在地图声明上。你帮我省去了更多的头疼,谢谢! - snappieT
1
有关示例反序列化程序,请参见此处 https://www.baeldung.com/jackson-map - best wishes
1
但是在这里如何使用字符串键来返回真正的映射键对象呢? - Jason Lim Ji Chen

13
如上所述,关键是您需要一个密钥反序列化程序(这也让我感到困惑)。 在我的情况下,我的类上配置了非字符串映射密钥,但在我解析的JSON中并没有它,因此对我来说一个极其简单的解决方案就起作用了(在密钥反序列化程序中返回null)。
public class ExampleClassKeyDeserializer extends KeyDeserializer
{
    @Override
    public Object deserializeKey( final String key,
                                  final DeserializationContext ctxt )
       throws IOException, JsonProcessingException
    {
        return null;
    }
}

public class ExampleJacksonModule extends SimpleModule
{
    public ExampleJacksonModule()
    {
        addKeyDeserializer(
            ExampleClass.class,
            new ExampleClassKeyDeserializer() );
    }
}

final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule( new ExampleJacksonModule() );

6

这里给出的答案的基础上建议实现一个带有反序列化器的模块。JodaTime模块是一个易于理解的包含序列化器和反序列化器的完整示例。

请注意,模块功能是在Jackson 1.7版本中引入的,因此您可能需要升级。

所以,步骤如下:

  1. 创建一个包含您的类的(反)序列化器的模块,基于Joda示例
  2. 使用mapper.registerModule(module);注册该模块

然后您就可以开始了


4
还有一个额外的注意事项:你需要添加“键(反)序列化器”:普通的(反)序列化器无法直接使用,因为 Map 键有额外的限制。但肯定可以通过模块进行注册。 - StaxMan
5
@Max,你可以提供一下你如何实现反序列化的代码吗?如果可以的话,我想看看 =) - Ted

2
假设我们有一个Map属性,如下所示:
class MyDTO{
    @JsonSerialize(keyUsing = MyObjectKeySerializer.class)
    @JsonDeserialize(keyUsing = MyObjectKeyDeserilazer.class)
    private Map<MyObjectKey , List<?>> map;
}

我们将MyObjectKey序列化为json字符串,在调用objectMapper.writeAsString时; 然后从json字符串反序列化到MyObjectKey。
    public class MyObjectKeySerializer extends StdSerializer<MyObjectKey> {

        public Serializer() {
            super(MyObjectKey.class);
        }

        @Override
        public void serialize(MyObjectKey value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            gen.writeFieldName(JsonUtil.toJSONString(value));
        }
    }

    public class MyObjectKeyDeserializer extends KeyDeserializer {
        @Override
        public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
            return JsonUtil.toObject(key, MyObjectKey.class);
        }
    }

0
在搜索了许多网页之后,我认为我找到了一个不错的解决方案来处理POJO风格的键(尽管通常最好不要使用完整对象作为映射键)。
序列化器(注册为Jackson模块,在Spring Boot中):
@Bean
fun addKeySerializer(): Module =
    SimpleModule().addKeySerializer(YourClass::class.java, YourClassSerializer())

class YourClassSerializer() : JsonSerializer<YourClass>() {
    override fun serialize(value: DataElement, gen: JsonGenerator, serializers: SerializerProvider) {
        gen.writeFieldName(jacksonObjectMapper().writeValueAsString(value))
    }
}

(请注意,在标准的Java环境中,您需要在此处实例化自己的objectMapper实例)

反序列化器:

@Bean
fun addKeyDeserializer(): Module =
    SimpleModule().addKeyDeserializer(YourClass::class.java, YourClassDeserializer())

class YourClassDeserializer() : KeyDeserializer() {
    override fun deserializeKey(key: String, ctxt: DeserializationContext): YourClass? {
        return ctxt.parser.readValueAs(YourClass::class.java)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接