如何配置Jackson以默认键入反序列化命名类型?

请考虑以下示例:

package com.example; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; public class JacksonDeserializationOfNamedTypes { public static void main(String[] args) throws Exception { ObjectMapper jackson = new ObjectMapper(); jackson.enableDefaultTypingAsProperty(DefaultTyping.JAVA_LANG_OBJECT, "@type"); Balloon redBalloon = new Balloon("red"); String json = jackson.writeValueAsString(redBalloon); //{"@type":"Balloon","color":"red"} //assume the JSON could be anything Object deserialized = jackson.readValue(json, Object.class); assert deserialized instanceof Balloon; assert redBalloon.equals(deserialized); } @JsonTypeName("Balloon") @JsonTypeInfo(use = Id.NAME) public static final class Balloon { private final String color; //for deserialization private Balloon() { this.color = null; } public Balloon(final String color) { this.color = color; } public String getColor() { return color; } @Override public boolean equals(final Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; final Balloon other = (Balloon) obj; return this.color.equals(other.color); } @Override public int hashCode() { int result = color.hashCode(); result = 31 * result + color.hashCode(); return result; } @Override public String toString() { return color + " balloon"; } } } 

反序列化在运行时失败,出现以下exception: Exception in thread "main" java.lang.IllegalArgumentException: Invalid type id 'Balloon' (for id type 'Id.class'): no such class found

生成的JSON肯定具有Jackson正确确定类型所需的所有信息,因此如何配置ObjectMapper以将"Balloon"正确映射到com.example.JacksonDeserializationOfNamedTypes$Balloon

我当前的解决方案涉及自定义反序列化器和手动形成的类型名称映射到Java类型的组合:

 package com.example.jackson; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; public class JacksonDeserializerOfNamedTypes extends StdDeserializer { private final Map> typesByName; private final String typeProperty; private JacksonDeserializerOfNamedTypes(final Map> typesByName, final String typeProperty) { super(Object.class); this.typesByName = typesByName; this.typeProperty = typeProperty; } @Override public Object deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { final ObjectCodec codec = parser.getCodec(); final JsonNode root = parser.readValueAsTree(); final JsonNode typeNameNodeOrNull = root.get(typeProperty); if (typeNameNodeOrNull == null) { throw new JsonMappingException(parser, "Unable to determine Java type of JSON: " + root); } else { final String typeName = typeNameNodeOrNull.asText(); return Optional .ofNullable(typesByName.get(typeName)) .map(type -> parseOrNull(root, type, codec)) .orElseThrow(() -> new JsonMappingException(parser, String.format( "Unsupported type name '%s' in JSON: %s", typeName, root))); } } private  T parseOrNull(final JsonNode root, final Class type, final ObjectCodec codec) { try { return root.traverse(codec).readValueAs(type); } catch (IOException e) { return null; } } public static void main(String[] args) throws Exception { final Map> typesByName = scanForNamedTypes(); final SimpleModule namedTypesModule = new SimpleModule("my-named-types-module"); namedTypesModule.addDeserializer(Object.class, new JacksonDeserializerOfNamedTypes(typesByName, JsonTypeInfo.Id.NAME.getDefaultPropertyName())); final Car pinto = new Car("Ford", "Pinto", 1971); final Balloon sharik = new Balloon("blue"); final ObjectMapper mapper = new ObjectMapper().registerModule(namedTypesModule); System.out.println(mapper.readValue(mapper.writeValueAsString(pinto), Object.class).getClass()); System.out.println(mapper.readValue(mapper.writeValueAsString(sharik), Object.class).getClass()); } @JsonTypeName("Balloon") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) public static final class Balloon { public String color; private Balloon() {} public Balloon(final String color) { this.color = color; } } @JsonTypeName("Car") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) public static final class Car { public String make; public String model; public int year; private Car() {} public Car(final String make, final String model, final int year) { this.make = make; this.model = model; this.year = year; } } static Map> scanForNamedTypes() { //in reality, i'd be using a framework (eg Reflections) to scan the classpath //for classes tagged with @JsonTypeName to avoid maintaining manual mappings final Map> typesByName = new HashMap<>(); typesByName.put("Balloon", Balloon.class); typesByName.put("Car", Car.class); return Collections.unmodifiableMap(typesByName); } } 

生成的JSON肯定具有Jackson正确确定类型所需的所有信息

您已向jackson提供以下内容

 Object deserialized = jackson.readValue(json, Object.class); 

Object是所有Java引用类型的超类型。 这是众所周知的。 对jackson不是很有用。 您的JSON还包含

 {"@type":"Balloon","color":"red"} 

鉴于此并感谢

 jackson.enableDefaultTypingAsProperty(DefaultTyping.JAVA_LANG_OBJECT, "@type"); 

jackson可以推断它可以使用@type元素的值。 但是, Balloon这个名字怎么办呢? jackson不知道类路径上的所有类型。 您是否拥有完全限定名称Balloon的类型? 你有一个名为com.example.Balloon的类型或一个名为org.company.toys.Balloon的类型吗? jackson应该如何选择?

@JsonTypeInfo和注释族通常用于inheritance层次结构的反序列化。 例如

 @JsonTypeInfo(use = Id.NAME) @JsonSubTypes(value = @Type(value = Balloon.class)) public abstract static class Toy { } @JsonTypeName("Balloon") public static final class Balloon extends Toy { 

 Object deserialized = jackson.readValue(json, Toy.class); 

现在Jackson可以查找Toy类及其元数据,这些元数据将其子类标识为Balloon ,它也可以检查名称Balloon

如果您没有尝试为inheritance层次结构建模,那么最简单的解决方案是在@JsonTypeName注释中使用Balloon类的完全限定名称。

 @JsonTypeName("com.example.JacksonDeserializationOfNamedTypes$Balloon") @JsonTypeInfo(use = Id.NAME) public static final class Balloon { 

然后该名称将显示在JSON中,Jackson将使用它来确定目标类。

总之(太晚了:))

你应该:

  • 提供完全限定的className,以便jackson可以找到要反序列化的类,例如:

     @JsonTypeName("com.example.JacksonDeserializationOfNamedTypes$Balloon") 
  • 或添加自定义反序列化器来处理您的Balloon类型