英文:
Prevent Jackson ObjectMapper automatically toString()-formatting composite map keys
问题
我正在使用 com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString()
来从Java POJOs生成JSON。
我的一个对象 - 我们称其为 ResultSheet
- 包含一个属性 Map<ResultId,Integer> results
。ResultId
表示复合主键(由两个外键组成)。
似乎 ObjectMapper
在遇到 ResultId
时意识到它不能直接在JSON中表示,因为JSON映射键必须是字符串。因此它输出了 ResultId
的 toString()
。 (在这种特定情况下,这会产生一个形如 "42:43"
的字符串,但这仅是因为目前实现了这种方式。)
我该如何阻止它这样做呢?当前的行为隐藏了正在进行的结构不能正确地表示为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()
抛出异常是不可接受的hack方法。
英文:
I'm using com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString()
to generate JSON from Java POJOs.
One of my objects - lets call it ResultSheet
- contains a property Map<ResultId,Integer> results
. ResultId
represents a composite PK (of two FKs).
It appears that ObjectMapper
upon encountering ResultId
realizes that this can't be represented in JSON directly because JSON map keys have to be strings. It is therefore outputing the toString()
of ResultId
instead. (In this particular case this produces a string in the form "42:43"
but only because this happens to be how toString()
is currently implemented.)
How can I stop it doing this? The current behavour is hiding the fact that the structure being marshalled is not properly representable as a JSON structure. It is also leaking the current toString()
implementation into the API of my web service.
I would rather have an exception thrown telling me when a value used as a map key is not a String
- or, at least, when it is not trivial to convert to a String
. (I'm not so concerned about Long
keys being toString()
-ed, for example.)
I've looked into the SerializationFeature
settings but none of them seem to address this issue. https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/SerializationFeature.html
Is there are way to prevent this? Can it be done in a general way i.e. not just for this specific class but in any case where this might arise on future, as new classes, some of which might be used as map keys, are added?
Note, making toString()
throw an exception would not be an acceptable hack in this instance.
答案1
得分: 3
为解决这个问题,您可以实现并注册自己的com.fasterxml.jackson.databind.ser.BeanSerializerModifier
类。并且覆盖modifyKeySerializer方法。实现可以始终返回一个抛出异常的序列化器。以下是示例:
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) throws IOException {
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;
}
以上代码输出:
{Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: You can not serialize POJO as keys! (through reference chain: java.util.Collections$SingletonMap["ResultId(id1=1, id2=2)"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:353)
at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:316)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:725)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:643)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:33)
英文:
To solve this problem you can implement and register your own com.fasterxml.jackson.databind.ser.BeanSerializerModifier
class. And override modifyKeySerializer method. Implementation could always return a serialiser which throws an exception. See below example:
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;
}
Above code prints:
{Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: You can not serialize POJO as keys! (through reference chain: java.util.Collections$SingletonMap["ResultId(id1=1, id2=2)"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:353)
at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:316)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:725)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:643)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:33)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论