Java泛型和JSON的反序列化(下)
上篇:https://kuyur.net/blog/archives/2729
中篇:https://kuyur.net/blog/archives/2772
中篇介绍了如何构造自己的参数化类型(ParameterizedType),但一步步构造还是嫌麻烦。
本篇介绍Jackson提供的【型】工具。
TypeReference是一个抽象类,但在使用它时却不需要实现任何方法。
例如获取Triple<User, Archive, Comment>的型:
Type tripleGenericType = new TypeReference<Triple<User, Archive, Comment>>(){}.getType();
TypeReference的源码:
package org.codehaus.jackson.type; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; public abstract class TypeReference<T> implements Comparable<TypeReference<T>> { final Type _type; protected TypeReference() { Type superClass = getClass().getGenericSuperclass(); if (superClass instanceof Class<?>) { // sanity check, should never happen throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information"); } /* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect * it is possible to make it fail? * But let's deal with specifc * case when we know an actual use case, and thereby suitable * work arounds for valid case(s) and/or error to throw * on invalid one(s). */ _type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; } public Type getType() { return _type; } /** * The only reason we define this method (and require implementation * of <code>Comparable</code>) is to prevent constructing a * reference without type information. */ public int compareTo(TypeReference<T> o) { // just need an implementation, not a good one... hence: return 0; } }
TypeReference的构造函数用protected修饰,因此只能够由子类调用。
new TypeReference<Triple<User, Archive, Comment>>(){};
上面的代码构造了一个派生自TypeReference<Triple<User, Archive, Comment>>的匿名子类实例,
匿名子类在构造时,通过反射获取父类的参数化类型,也即TypeReference<Triple<User, Archive, Comment>>型,
再通过ParameterizedType的接口getActualTypeArguments(),获得Triple<User, Archive, Comment>型
(ParameterizedType的详细说明见中篇)
getGenericSuperclass()和getSuperclass()都是Class提供的方法,
如果匿名子类调用getSuperclass(),获得的将只是TypeReference.class,而不会包含泛型信息。
Type tripleGenericType = new TypeReference<Triple<User, Archive, Comment>>(){}.getType();实际上是下面代码的语法糖:
TypeReference<Triple<User, Archive, Comment>> object = new TypeReference<Triple<User, Archive, Comment>>(){}; Type superType = object.getClass().getGenericSuperclass(); Type tripleGenericType = ((ParameterizedType)superType).getActualTypeArguments()[0];
了解到getGenericSuperclass()可以获取参数化类型,于是我们可以继续玩一下:
@Test public void testGetGenericSuperclass2() throws UnsupportedEncodingException { class ExtTriple extends Triple<User, Archive, Comment> {} Type tripleGenericType = ExtTriple.class.getGenericSuperclass(); System.out.println(tripleGenericType); String jsonString = "{\"second\":{\"ID\":1000,\"标题\":\"18岁的那天\",\"正文\":\"18岁那天的早上...\",\"作者\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}},\"third\":{\"作者\":{\"mailAddress\":\"qian.qiu@wandai.com\",\"name\":\"铅球万袋\"},\"回复\":\"骚年よ、大志を抱け\"},\"first\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}}"; ByteArrayInputStream bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8")); Triple<User, Archive, Comment> triple = Util.readJsonFromStream(bais, tripleGenericType); System.out.println(triple.getFirst()); System.out.println(triple.getSecond()); System.out.println(triple.getThird()); }
到这里Java泛型与JSON反序列化的冒险之旅已经接近尾声。
ObjectMapper重载了n+个readValue接口,其中有一组接口支持TypeReference。(但没有readValue接口直接支持Type。)
为Util添加一个静态转换函数:
public class Util { ... public static <T> T readJsonFromString(String jsonString, TypeReference<T> typeRef) { ObjectMapper mapper = new ObjectMapper(); try { return mapper.readValue(jsonString, typeRef); } catch (JsonParseException e) { throw new RuntimeException("Deserialize from JSON failed.", e); } catch (JsonMappingException e) { throw new RuntimeException("Deserialize from JSON failed.", e); } catch (IOException e) { throw new RuntimeException("Deserialize from JSON failed.", e); } } |
使用很简单,构造一个TypeReference的匿名子类的对象作为参数传入:
@Test public void testReadJsonFromString11() { String jsonString = "{\"1\" : {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"2\" : {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}}"; Map<Integer, User> users = Util.readJsonFromString(jsonString, new TypeReference<Map<Integer, User>>(){}); assertEquals(users.size(), 2); User user1 = users.get(Integer.valueOf(1)); System.out.println(user1); User user2 = users.get(Integer.valueOf(2)); System.out.println(user2); }
Jackson还定义了自己的【型】数据结构:JavaType,readValue接口同样支持这种【型】数据结构。
public <T> T readValue(String content, JavaType valueType)
如果阅读ObjectMapper的代码,就会发现【型】或者TypeReference最后都会转换为JavaType。
工厂类TypeFactory提供了静态转换方法。
于是继续为Util添加一个静态转换函数:
public class Util { ... public static <T> T readJsonFromString(String jsonString, Type genericType) { ObjectMapper mapper = new ObjectMapper(); try { return mapper.readValue(jsonString, TypeFactory.type(genericType)); } catch (JsonParseException e) { throw new RuntimeException("Deserialize from JSON failed.", e); } catch (JsonMappingException e) { throw new RuntimeException("Deserialize from JSON failed.", e); } catch (IOException e) { throw new RuntimeException("Deserialize from JSON failed.", e); } } |
使用:
(注意使用默认构造函数创建的ObjectMapper对象不支持JAXBAnnotation式的标签,必须为Archive添加JacksonAnnotation式的标签)
@Test public void testReadJsonFromString18() throws UnsupportedEncodingException { class ExtTriple extends Triple<User, Archive, Comment> {} Type tripleGenericType = ExtTriple.class.getGenericSuperclass(); String jsonString = "{\"second\":{\"ID\":1000,\"标题\":\"18岁的那天\",\"正文\":\"18岁那天的早上...\",\"作者\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}},\"third\":{\"作者\":{\"mailAddress\":\"qian.qiu@wandai.com\",\"name\":\"铅球万袋\"},\"回复\":\"骚年よ、大志を抱け\"},\"first\":{\"mailAddress\":\"yi.tong@jianghu.com\",\"name\":\"一桶浆糊\"}}"; Triple<User, Archive, Comment> triple = Util.readJsonFromString(jsonString, tripleGenericType); System.out.println(triple.getFirst()); System.out.println(triple.getSecond()); System.out.println(triple.getThird()); }
最后提供源码下载,内含一个不支持多线程的HttpClient Demo:jsondemo
最后用的@JsonTypeInfo @JsonSubTypes @JsonTypeName 解决的问题
之前之所以有问题是因为objectmapper有个地方写错了….
Anyway, thank you! Merry Xmas!
今天遇到一个问题:
前台javascript已经构造好一个json object 然后发送到后台(java),使用jackson进行deserilization. 使用了readvalueas(xx, abc.class)
但是由于abc.class本身就是i个wrapper class,其中包含了一个父类field,而实际json中使用的是子类(子类相较父类多了一个field),不知道是否能有方法将这个json deserilization并且mapping得到子类?
派生类嵌套父类的情况,Jackson只能做到有限支持。
考虑父类:
package info.kuyur.jsondemo.models;
public class Parent {
private String name;
public Parent() {
super();
}
public Parent(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Parent [name=" + name + "]";
}
}
子类:
package info.kuyur.jsondemo.models;
public class Child extends Parent {
private Integer age;
private Parent parent;
public Child() {
super();
}
public Child(Integer age, Parent parent) {
super();
this.age = age;
this.parent = parent;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
@Override
public String toString() {
return "Child [age=" + age + ", parent=" + parent + ", name="
+ getName() + "]";
}
}
对于json:
{"age":15,"parent":{"name":"parent-name"},"name":"child-name"}
,能反序列化如果构建这么一个对象:
@Test
public void testToJsonString3() {
Parent grandFather = new Parent("grand-father-name");
Child father = new Child(35, grandFather);
father.setName("father-name");
Child child = new Child(15, father);
child.setName("grand-child-name");
String jsonString = Util.toJsonString(child, Child.class);
System.out.println(jsonString);
}
得到的json:
{"age":15,"parent":{"age":35,"parent":{"name":"grand-father-name"},"name":"father-name"},"name":"grand-child-name"}
,则无法反序列化,因为Jackson并不知道parent实际上应该用Child的构造函数来构建,Jackson仍然会用Parent的构造函数来构建parent,由于字段”age”:35无法忽略,于是抛出异常。非继承的自包含,比如单向链表的节点,反序列化没有问题。
package info.kuyur.jsondemo.models;
public class LinkedNode {
private String nodeName;
private LinkedNode nextNode;
public LinkedNode() {
super();
}
public String getNodeName() {
return nodeName;
}
public void setNodeName(String nodeName) {
this.nodeName = nodeName;
}
public LinkedNode getNextNode() {
return nextNode;
}
public void setNextNode(LinkedNode nextNode) {
this.nextNode = nextNode;
}
@Override
public String toString() {
return "LinkedNode [nodeName=" + nodeName + ", nextNode=" + nextNode
+ "]";
}
}
json:
{"nodeName":"node1","nextNode":{"nodeName":"node2","nextNode":{"nodeName":"node3","nextNode":null}}}
能成功反序列化。归结到一点,还是要为Jackson提供足够的型信息,派生类嵌套父类会导致歧义性,不是值得推荐的数据模型组织方法。