阻止Jackson ObjectMapper自动toString格式的复合映射键

问题描述

我正在使用com.fasterxml.jackson.databind.ObjectMapper.writeValueAsstring()从Java POJO生成JSON。

我的一个对象-称为ResultSheet-包含属性Map<ResultId,Integer> resultsResultId代表(两个FK的)复合PK。

似乎ObjectMapper在遇到ResultId时意识到这不能直接用JSON表示,因为JSON映射键必须是字符串。因此,它改为输出toString()中的ResultId。 (在这种特殊情况下,它会以"42:43"的形式生成一个字符串,但这仅仅是因为这恰好是toString()的当前实现方式。)

如何阻止它执行此操作?当前的行为掩盖了以下事实:被编组的结构不能正确表示为JSON结构。还将当前的toString()实现泄漏到我的Web服务的API中。

当用作映射键的值不是String时,或者至少在转换为String时,我会抛出异常告诉我。 (例如,我不太担心Long版的toString()键。)

我已经研究了SerializationFeature设置,但似乎没有一个解决这个问题。 https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/SerializationFeature.html

有没有办法防止这种情况?

是否可以以一般方式完成操作,即不仅针对特定类,而且在将来可能出现的任何情况下,都会添加新类,其中一些可能用作映射键?

请注意,在这种情况下,将toString()引发异常将是不可接受的。

解决方法

要解决此问题,您可以实现并注册自己的object S; type S = S.type object K; type K = K.type object I; type I = I.type // or,heck type S = 0 type K = 1 type I = 2 类。并覆盖modifyKeySerializer方法。实现总是可以返回引发异常的序列化程序。参见以下示例:

com.fasterxml.jackson.databind.ser.BeanSerializerModifier

上面的代码打印:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.IOException;
import java.util.Collections;
import java.util.Map;

public class JsonKeySerializerApp {
    public static void main(String[] args) throws IOException {
        SimpleModule keyExceptionSerializerModule = new SimpleModule();
        keyExceptionSerializerModule.setSerializerModifier(new BeanSerializerModifier() {
            @Override
            public JsonSerializer<?> modifyKeySerializer(SerializationConfig config,JavaType valueType,BeanDescription beanDesc,JsonSerializer<?> serializer) {
                // specify classes you want to allow to be serialized as keys
                if (valueType.getRawClass().getPackage().getName().startsWith("java.")) {
                    return serializer;
                }
                return new JsonSerializer<Object>() {
                    @Override
                    public void serialize(Object value,JsonGenerator gen,SerializerProvider serializers) {
                        throw new UnsupportedOperationException("You can not serialize POJO as keys!");
                    }
                };
            }
        });
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(keyExceptionSerializerModule);

        Map<ResultId,Long> map = Collections.singletonMap(new ResultId(1,2),1L);

        mapper.writeValue(System.out,map);
    }
}

@AllArgsConstructor
@Data
class ResultId {

    private int id1;
    private int id2;
}