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

  1. legendary
    2012年12月25日 04:27 | #1

    最后用的@JsonTypeInfo @JsonSubTypes @JsonTypeName 解决的问题
    之前之所以有问题是因为objectmapper有个地方写错了….
    Anyway, thank you! Merry Xmas!

  2. legendary
    2012年12月19日 13:13 | #2

    今天遇到一个问题:
    前台javascript已经构造好一个json object 然后发送到后台(java),使用jackson进行deserilization. 使用了readvalueas(xx, abc.class)
    但是由于abc.class本身就是i个wrapper class,其中包含了一个父类field,而实际json中使用的是子类(子类相较父类多了一个field),不知道是否能有方法将这个json deserilization并且mapping得到子类?

    • 2012年12月19日 15:12 | #3

      派生类嵌套父类的情况,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提供足够的型信息,派生类嵌套父类会导致歧义性,不是值得推荐的数据模型组织方法。

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: