Jackson 中的通用元组反序列化
Generic tuple de-serialization in Jackson
碰巧我需要在 Java JSON 中支持来自外部数据源的数据。有一种常见的模式。它是一个数组,包含固定数量的某些不同类型的元素。我们称之为元组。这是我使用 FasterXML Jackson 对具有特定预期元素类型的 3 元素元组进行反序列化的示例:
public class TupleTest {
public static void main(String[] args) throws Exception {
String person = "{\"name\":\"qqq\",\"age\":35,\"address\":\"nowhere\",\"phone\":\"(555)555-5555\",\"email\":\"super@server.com\"}";
String jsonText = "[[" + person + ",[" + person + "," + person + "],{\"index1\":" + person + ",\"index2\":" + person + "}]]";
ObjectMapper om = new ObjectMapper().registerModule(new TupleModule());
List<FixedTuple3> data = om.readValue(jsonText, new TypeReference<List<FixedTuple3>>() {});
System.out.println("Deserialization result: " + data);
System.out.println("Serialization result: " + om.writeValueAsString(data));
}
}
class Person {
public String name;
public Integer age;
public String address;
public String phone;
public String email;
@Override
public String toString() {
return "Person{name=" + name + ", age=" + age + ", address=" + address
+ ", phone=" + phone + ", email=" + email + "}";
}
}
class FixedTuple3 {
public Person e1;
public List<Person> e2;
public Map<String, Person> e3;
@Override
public String toString() {
return "Tuple[" + e1 + ", " + e2 + ", " + e3 + "]";
}
}
class TupleModule extends SimpleModule {
public TupleModule() {
super(TupleModule.class.getSimpleName(), new Version(1, 0, 0, null, null, null));
setSerializers(new SimpleSerializers() {
@Override
public JsonSerializer<?> findSerializer(SerializationConfig config,
JavaType type, BeanDescription beanDesc) {
if (isTuple(type.getRawClass()))
return new TupleSerializer();
return super.findSerializer(config, type, beanDesc);
}
});
setDeserializers(new SimpleDeserializers() {
@Override
public JsonDeserializer<?> findBeanDeserializer(JavaType type,
DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {
Class<?> rawClass = type.getRawClass();
if (isTuple(rawClass))
return new TupleDeserializer(rawClass);
return super.findBeanDeserializer(type, config, beanDesc);
}
});
}
private boolean isTuple(Class<?> rawClass) {
return rawClass.equals(FixedTuple3.class);
}
public static class TupleSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
try {
jgen.writeStartArray();
for (int i = 0; i < 3; i++) {
Field f = value.getClass().getField("e" + (i + 1));
Object res = f.get(value);
jgen.getCodec().writeValue(jgen, res);
}
jgen.writeEndArray();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
public static class TupleDeserializer extends JsonDeserializer<Object> {
private Class<?> retClass;
public TupleDeserializer(Class<?> retClass) {
this.retClass = retClass;
}
public Object deserialize(JsonParser p, DeserializationContext ctx) throws IOException, JsonProcessingException {
try {
Object res = retClass.newInstance();
if (!p.isExpectedStartArrayToken()) {
throw new JsonMappingException("Tuple array is expected but found " + p.getCurrentToken());
}
JsonToken t = p.nextToken();
for (int i = 0; i < 3; i++) {
final Field f = res.getClass().getField("e" + (i + 1));
TypeReference<?> tr = new TypeReference<Object>() {
@Override
public Type getType() {
return f.getGenericType();
}
};
Object val = p.getCodec().readValue(p, tr);
f.set(res, val);
}
t = p.nextToken();
if (t != JsonToken.END_ARRAY)
throw new IOException("Unexpected ending token in tuple deserializer: " + t.name());
return res;
} catch (IOException ex) {
throw ex;
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
}
但是这种方法意味着我每次面对特定大小的元组中的新类型配置时都必须创建新的class。所以我想知道是否有任何方法可以定义处理泛型类型的反序列化器。这样每个元组大小有一个元组 class 就足够了。例如,我的大小为 3 的通用元组可以定义为:
class Tuple3 <T1, T2, T3> {
public T1 e1;
public T2 e2;
public T3 e3;
@Override
public String toString() {
return "Tuple[" + e1 + ", " + e2 + ", " + e3 + "]";
}
}
它的用法如下:
List<Tuple3<Person, List<Person>, Map<String, Person>>> data =
om.readValue(jsonText,
new TypeReference<List<Tuple3<Person, List<Person>, Map<String, Person>>>>() {});
是否可行?
是的,您必须使用反射来获取所有字段,而不是通过名称获取已知字段。
好的。所以......可能有一种更简单的方法来做 "tuple" 风格。您实际上可以将 POJO 注释为序列化为数组:
@JsonFormat(shape=JsonFormat.Shape.ARRAY)
@JsonPropertyOrder({ "name", "age" }) // or use "alphabetic"
public class POJO {
public String name;
public int age;
}
如果是这样,它们将被写入数组,从数组中读取。
但是如果您要处理自定义泛型类型,您可能需要解析类型参数。这可以使用 TypeFactory
、方法 findTypeParameters(...)
来完成。虽然这似乎是多余的,但如果您使用子类型(如果不是,JavaType
实际上有 direct 类型参数的访问器),一般情况下需要它。
碰巧我需要在 Java JSON 中支持来自外部数据源的数据。有一种常见的模式。它是一个数组,包含固定数量的某些不同类型的元素。我们称之为元组。这是我使用 FasterXML Jackson 对具有特定预期元素类型的 3 元素元组进行反序列化的示例:
public class TupleTest {
public static void main(String[] args) throws Exception {
String person = "{\"name\":\"qqq\",\"age\":35,\"address\":\"nowhere\",\"phone\":\"(555)555-5555\",\"email\":\"super@server.com\"}";
String jsonText = "[[" + person + ",[" + person + "," + person + "],{\"index1\":" + person + ",\"index2\":" + person + "}]]";
ObjectMapper om = new ObjectMapper().registerModule(new TupleModule());
List<FixedTuple3> data = om.readValue(jsonText, new TypeReference<List<FixedTuple3>>() {});
System.out.println("Deserialization result: " + data);
System.out.println("Serialization result: " + om.writeValueAsString(data));
}
}
class Person {
public String name;
public Integer age;
public String address;
public String phone;
public String email;
@Override
public String toString() {
return "Person{name=" + name + ", age=" + age + ", address=" + address
+ ", phone=" + phone + ", email=" + email + "}";
}
}
class FixedTuple3 {
public Person e1;
public List<Person> e2;
public Map<String, Person> e3;
@Override
public String toString() {
return "Tuple[" + e1 + ", " + e2 + ", " + e3 + "]";
}
}
class TupleModule extends SimpleModule {
public TupleModule() {
super(TupleModule.class.getSimpleName(), new Version(1, 0, 0, null, null, null));
setSerializers(new SimpleSerializers() {
@Override
public JsonSerializer<?> findSerializer(SerializationConfig config,
JavaType type, BeanDescription beanDesc) {
if (isTuple(type.getRawClass()))
return new TupleSerializer();
return super.findSerializer(config, type, beanDesc);
}
});
setDeserializers(new SimpleDeserializers() {
@Override
public JsonDeserializer<?> findBeanDeserializer(JavaType type,
DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {
Class<?> rawClass = type.getRawClass();
if (isTuple(rawClass))
return new TupleDeserializer(rawClass);
return super.findBeanDeserializer(type, config, beanDesc);
}
});
}
private boolean isTuple(Class<?> rawClass) {
return rawClass.equals(FixedTuple3.class);
}
public static class TupleSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
try {
jgen.writeStartArray();
for (int i = 0; i < 3; i++) {
Field f = value.getClass().getField("e" + (i + 1));
Object res = f.get(value);
jgen.getCodec().writeValue(jgen, res);
}
jgen.writeEndArray();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
public static class TupleDeserializer extends JsonDeserializer<Object> {
private Class<?> retClass;
public TupleDeserializer(Class<?> retClass) {
this.retClass = retClass;
}
public Object deserialize(JsonParser p, DeserializationContext ctx) throws IOException, JsonProcessingException {
try {
Object res = retClass.newInstance();
if (!p.isExpectedStartArrayToken()) {
throw new JsonMappingException("Tuple array is expected but found " + p.getCurrentToken());
}
JsonToken t = p.nextToken();
for (int i = 0; i < 3; i++) {
final Field f = res.getClass().getField("e" + (i + 1));
TypeReference<?> tr = new TypeReference<Object>() {
@Override
public Type getType() {
return f.getGenericType();
}
};
Object val = p.getCodec().readValue(p, tr);
f.set(res, val);
}
t = p.nextToken();
if (t != JsonToken.END_ARRAY)
throw new IOException("Unexpected ending token in tuple deserializer: " + t.name());
return res;
} catch (IOException ex) {
throw ex;
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
}
但是这种方法意味着我每次面对特定大小的元组中的新类型配置时都必须创建新的class。所以我想知道是否有任何方法可以定义处理泛型类型的反序列化器。这样每个元组大小有一个元组 class 就足够了。例如,我的大小为 3 的通用元组可以定义为:
class Tuple3 <T1, T2, T3> {
public T1 e1;
public T2 e2;
public T3 e3;
@Override
public String toString() {
return "Tuple[" + e1 + ", " + e2 + ", " + e3 + "]";
}
}
它的用法如下:
List<Tuple3<Person, List<Person>, Map<String, Person>>> data =
om.readValue(jsonText,
new TypeReference<List<Tuple3<Person, List<Person>, Map<String, Person>>>>() {});
是否可行?
是的,您必须使用反射来获取所有字段,而不是通过名称获取已知字段。
好的。所以......可能有一种更简单的方法来做 "tuple" 风格。您实际上可以将 POJO 注释为序列化为数组:
@JsonFormat(shape=JsonFormat.Shape.ARRAY)
@JsonPropertyOrder({ "name", "age" }) // or use "alphabetic"
public class POJO {
public String name;
public int age;
}
如果是这样,它们将被写入数组,从数组中读取。
但是如果您要处理自定义泛型类型,您可能需要解析类型参数。这可以使用 TypeFactory
、方法 findTypeParameters(...)
来完成。虽然这似乎是多余的,但如果您使用子类型(如果不是,JavaType
实际上有 direct 类型参数的访问器),一般情况下需要它。