如何使用Jackson和默认类型(反)序列化EnumMap
How to (de)serialize an EnumMap using Jackson and default typing?
我有一个对象,其中一个属性是Map<MyEnum, Object>
。
由于我的应用程序很大,我启用了默认的键入方式:
ObjectMapper jsonMapper = new ObjectMapper()
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_OBJECT)
.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
总的来说,这是相当不错的。
但是,由于Javascript在使用对象作为散列时不支持对象键,所以当我从Javascript端将一些数据放入映射中时,对象会转换为字符串。
因此,我收到的JSON包含
"MyClass": {
"contextElements": {
"userCredentials": {
"UserCredentials": {
"login": "admin",
"password": "admin",
}
}
}
},
当反序列化时,Jackson失败,出现以下异常
java.lang.IllegalArgumentException: Invalid type id 'userCredentials' (for id type 'Id.class'): no such class found
at org.codehaus.jackson.map.jsontype.impl.ClassNameIdResolver.typeFromId(ClassNameIdResolver.java:72)
at org.codehaus.jackson.map.jsontype.impl.TypeDeserializerBase._findDeserializer(TypeDeserializerBase.java:61)
at org.codehaus.jackson.map.jsontype.impl.AsWrapperTypeDeserializer._deserialize(AsWrapperTypeDeserializer.java:87)
at org.codehaus.jackson.map.jsontype.impl.AsWrapperTypeDeserializer.deserializeTypedFromObject(AsWrapperTypeDeserializer.java:39)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:133)
at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:221)
我非常理解:Jackson不理解我的类中的Map<MyEnum, Object>
声明,尽管MyEnum
是最后一个类,但他希望添加一些类型元数据(嘿,也许这是个bug?!)。
我该怎么做才能让代码正常工作?
我使用的是Jackson 1.5.2
好的,所以,问题正确地说明了这一点:不可能使用键不是字符串的JSON映射。因此,要在javascript中模拟Java Map,必须走更长的路,这通常需要将映射转换为。。。其他的东西。
我选择的是非常常见的阵列阵列:
像这样的地图
{
a:b,
c:d,
}
然后将转换为阵列
[
[a,b],
[c,d],
]
获得结果所需的详细步骤是什么
配置自定义(反)序列化
这是通过在对象映射器中设置序列化工厂来获得的,正如Jackson doc明确解释的那样:
/**
* Associates all maps with our custom serialization mechanism, which will transform them into arrays of arrays
* @see MapAsArraySerializer
* @return
*/
@Produces
public SerializerFactory createSerializerFactory() {
CustomSerializerFactory customized = new CustomSerializerFactory();
customized.addGenericMapping(Map.class, new MapAsArraySerializer());
return customized;
}
public @Produces ObjectMapper createMapper() {
ObjectMapper jsonMapper = new ObjectMapper();
// ....
// now configure serializer
jsonMapper.setSerializerFactory(createSerializerFactory());
// ....
return jsonMapper;
}
这个过程看起来很简单,主要是因为序列化在序列化中提供了非常正确的多态性特性,而这些特性对反序列化来说并不是很好。事实上,正如doc所说,反序列化需要添加显式的类映射,这些映射不以任何面向对象的方式使用(继承性不支持)
/**
* Defines a deserializer for each and any used map class, as there is no inheritence support ind eserialization
* @return
*/
@Produces
public DeserializerProvider createDeserializationProvider() {
// Yeah it's not even a standard Jackson class, it'll be explained why later
CustomDeserializerFactory factory = new MapAsArrayDeserializerFactory();
List<Class<? extends Map>> classesToHandle = new LinkedList<>();
classesToHandle.add(HashMap.class);
classesToHandle.add(LinkedHashMap.class);
classesToHandle.add(TreeMap.class);
for(Class<? extends Map> c : classesToHandle) {
addClassMappingFor(c, c, factory);
}
// and don't forget interfaces !
addClassMappingFor(Map.class, HashMap.class, factory);
addClassMappingFor(SortedMap.class, TreeMap.class, factory);
return new StdDeserializerProvider(factory);
}
private void addClassMappingFor(final Class<? extends Map> detected, final Class<? extends Map> created, CustomDeserializerFactory factory) {
factory.addSpecificMapping(detected, new MapAsArrayDeserializer() {
@Override
protected Map createNewMap() throws Exception {
return created.newInstance();
}
});
}
// It's the same createMapper() method that was described upper
public @Produces ObjectMapper createMapper() {
ObjectMapper jsonMapper = new ObjectMapper();
// ....
// and deserializer
jsonMapper.setDeserializerProvider(createDeserializationProvider());
return jsonMapper;
}
现在我们已经正确地定义了如何自定义(反)序列化,或者我们已经定义了吗?事实上,不:MapAsArrayDeserializerFactory
应该有自己的解释。
经过一些调试,我发现当类不存在反序列化程序时,DeserializerProvider
会委托给DeserializerFactory
,这很酷。但是,DeserializerFactory
根据obejct的"种类"创建反序列化器:如果它是一个集合,则将使用CollectionDeserializer(将数组读取到集合中)。如果是地图,则将使用MapDeserializer。
不幸的是,这个解决方案使用了JSON流中给定的java类(尤其是在使用多态反序列化时,我就是这样)。因此,配置自定义反序列化没有任何效果,除非CustomDeserializerFactory
是自定义的。。。像这样:
public class MapAsArrayDeserializerFactory extends CustomDeserializerFactory {
@Override
public JsonDeserializer<?> createMapDeserializer(DeserializationConfig config, MapType type, DeserializerProvider p) throws JsonMappingException {
return createBeanDeserializer(config, type, p);
}
}
是的,我将所有映射反序列化为bean。但现在,我所有的反序列化程序都被正确调用了。
序列化
现在,序列化是一项相当简单的任务:
public class MapAsArraySerializer extends JsonSerializer<Map> {
@SuppressWarnings("unchecked")
private Set asListOfLists(Map<?, ?> value) {
Set returned = new HashSet<>();
for(Map.Entry e : value.entrySet()) {
returned.add(Arrays.asList(e.getKey(), e.getValue()));
}
return returned;
}
@Override
public void serialize(Map value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
Collection entries = asListOfLists(value);
jgen.writeObjectField("entries", entries);
}
@Override
public void serializeWithType(Map value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException,
JsonProcessingException {
Collection entries = asListOfLists(value);
typeSer.writeTypePrefixForObject(value, jgen);
jgen.writeObjectField("entries", entries);
typeSer.writeTypeSuffixForObject(value, jgen);
}
}
反序列化
反序列化并不复杂:
public abstract class MapAsArrayDeserializer<Type extends Map> extends JsonDeserializer<Type> {
protected Type newMap(Collection c, Type returned) {
for(Object o : c) {
if (o instanceof List) {
List l = (List) o;
if(l.size()==2) {
Iterator i = l.iterator();
returned.put(i.next(), i.next());
}
}
}
return returned;
}
protected abstract Type createNewMap() throws Exception;
@Override
public Type deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
if(jp.getCurrentToken().equals(JsonToken.START_OBJECT)) {
JsonToken nameToken = jp.nextToken();
String name = jp.getCurrentName();
if(name.equals("entries")) {
jp.nextToken();
Collection entries = jp.readValueAs(Collection.class);
JsonToken endMap = jp.nextToken();
try {
return newMap(entries, createNewMap());
} catch(Exception e) {
throw new IOException("unable to create receiver map", e);
}
} else {
throw new IOException("expected '"entries'", but field name was '""+name+"'"");
}
} else {
throw new IOException("not startying an object ? Not possible");
}
}
@Override
public Type deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException,
JsonProcessingException {
Object value = typeDeserializer.deserializeTypedFromObject(jp, ctxt);
return (Type) value;
}
}
好吧,预期类是左抽象的,让每个声明的子类型创建右映射实例。
现在
现在它在Java端无缝工作(因为Javascript必须有一个映射等效对象来读取这些数据。
- Ajax发布表单序列化,发布引号'
- 序列化数据属性中对象的最可靠方法
- YUI3 IO实用程序是否可以根据给定的内容类型标头值自动序列化数据
- 为什么JSON.stringify没有序列化原型值
- 有没有一个Nodejs库可以序列化和反序列化命名组件的路径(比如URL路径名)
- 是否可以在javascript中反序列化java对象
- jQuery Ajax数组序列化错误
- 对象序列化,JAVA,Javascript
- 在jquery中以序列化的形式传递额外的paparameter
- 如何使用angularjs序列化对象
- 使用System从C#集合创建JSON数组.网状物剧本序列化
- javascript中是否有更标准化的方法来转换(序列化)非表单数据以与ajax一起使用
- 在html标记中序列化javascript代码
- 如何在C#中反序列化json对象
- jQueryAJAX-将额外的键/值对推送到序列化的$_POST数组中
- 使用JSON序列化图论树的解决方法
- PHP未从AJAX接收序列化数据
- 如何序列化包括影子 DOM 在内的 HTML DOM
- 序列化表单时进行编码
- 如何使用Jackson和默认类型(反)序列化EnumMap