我有一个对象,其中一个属性是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",
}
}
}
},
在反序列化时,杰克逊失败并出现以下异常
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)
我非常清楚:杰克逊并不理解我班级中的Map<MyEnum, Object>
声明,虽然MyEnum
是最后一堂课,但却希望添加一些类型元数据(嘿,也许吧等)# 39; sa bug?!)。
我能做些什么来使代码正常工作?
我使用Jackson 1.5.2
答案 0 :(得分:0)
好的,所以,问题正确地说明了:不可能使用其中键不是字符串的JSON映射。因此,要在javascript中模拟Java Map,必须走更长的路径,这通常会涉及将地图转换为......其他内容。
我选择的是非常常见的数组:
地图,例如
{
a:b,
c:d,
}
然后将被翻译成数组
[
[a,b],
[c,d],
]
获得该结果所需的详细步骤
这是通过将序列化工厂设置为对象映射器而获得的,Jackson doc clearly explains:
/**
* 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 also states,反序列化需要添加显式类映射,这些映射不以任何面向对象的方式使用(继承是不支持那里)
/**
* 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;
}
现在我们已经正确定义了(de)序列化是如何定制的,还是我们有?事实上,不,MapAsArrayDeserializerFactory
值得自己解释。
经过一些调试后,我发现DeserializerProvider
代表DeserializerFactory
,当没有类的反序列化器时,这很酷。但是,DeserializerFactory
根据&#34; kind&#34;创建了解串器。 obejct:如果它是一个集合,那么将使用CollectionDeserializer(将数组读入Collection)。如果它是Map,那么将使用MapDeserializer。
不幸的是,这个解决方案使用了JSON流中给出的java类(特别是在使用polymorphic deserialization时,这是我的情况)。因此,配置自定义反序列化无效,除非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必须有一个与地图等效的对象才能读取这些数据。