考虑以下示例:
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";
}
}
}
Run Code Online (Sandbox Code Playgroud)
反序列化在运行时失败,但出现以下异常:
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<Object> {
private final Map<String, Class<?>> typesByName;
private final String typeProperty;
private JacksonDeserializerOfNamedTypes(final Map<String, Class<?>> 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> T parseOrNull(final JsonNode root, final Class<T> 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<String, Class<?>> 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<String, Class<?>> scanForNamedTypes() {
//in reality, i'd be using a framework (e.g. Reflections) to scan the classpath
//for classes tagged with @JsonTypeName to avoid maintaining manual mappings
final Map<String, Class<?>> typesByName = new HashMap<>();
typesByName.put("Balloon", Balloon.class);
typesByName.put("Car", Car.class);
return Collections.unmodifiableMap(typesByName);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
19752 次 |
| 最近记录: |