Java泛型和JSON的反序列化(上)

开发RESTful应用的客户端时,不可避免要实现JSON或xml的反序列化和序列化。对于HTML客户端,JSON和JavaScript对象之间的转换是自然而然理所当然的事,JavaScript框架不实现就算不上框架。但对于Java语言的客户端,选择却不是那么多。Jackson JSON应该是最强大的JSON和Java对象转换工具库。

Jackson官方有个五分钟入门教程,用法非常的简洁。

下文的型都是指java.lang.reflect.Type,
java1.5引入了泛型,Type正是对一切数据类型的抽象,万物皆有型,
内置数据类型有型,如int.class,boolean.class,
对象类型有型,如Integer.class,和Integer i = 1; i.getClass();等价

Type type1 = Integer.class;
Integer i = 1;
Type type2 = i.getClass();
assertEquals(type1, type2);

同时【型】还是一种Java对象。Class是Type的实现,是【对象】(Object)这一类数据类型的型。

将反序列化功能写成一个通用的工具类:(使用ObjectMapper的接口readValue(String, Class<T>))

package info.kuyur.jsondemo.utils;

import java.io.IOException;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

public class Util {
	private Util() {}

	/**
	 * Only JacksonAnnotation supported.
	 * @param jsonString JSON string
	 * @param type Class type. Generic type is not supported.
	 * @return
	 */
	public static <T> T readJsonFromString(String jsonString, Class<T> type) {
		ObjectMapper mapper = new ObjectMapper();
		try {
			return mapper.readValue(jsonString, type);
		} 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);
		}
	}

反序列化函数使用了泛型(本来Jackson的ObjectMapper就使用了泛型)

定义几个数据模型,然后再测试一下这个反序列化函数的转换能力

User.java,不加注任何标签

package info.kuyur.jsondemo.models;

public class User {

	private String name;
	private String mailAddress;

	public User() {
		super();
	}

	public User(String name, String mailAddress) {
		super();
		this.name = name;
		this.mailAddress = mailAddress;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getMailAddress() {
		return mailAddress;
	}

	public void setMailAddress(String mailAddress) {
		this.mailAddress = mailAddress;
	}

	@Override
	public String toString() {
		return "User [name=" + name + ", mailAddress=" + mailAddress + "]";
	}
}

Archive.java,加注JAXBAnnotation类型的标签

package info.kuyur.jsondemo.models;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Archive {

	@XmlElement(name="ID")
	private int blogId;

	@XmlElement(name="标题")
	private String title;

	@XmlElement(name="正文")
	private String text;

	@XmlElement(name="作者")
	private User author;

	public Archive() {
		super();
	}

	public Archive(int blogId, String title, String text, User author) {
		super();
		this.blogId = blogId;
		this.title = title;
		this.text = text;
		this.author = author;
	}

	public int getBlogId() {
		return blogId;
	}

	public void setBlogId(int blogId) {
		this.blogId = blogId;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	public User getAuthor() {
		return author;
	}

	public void setAuthor(User author) {
		this.author = author;
	}

	@Override
	public String toString() {
		return "Archive [blogId=" + blogId + ", title=" + title + ", text="
				+ text + ", author=" + author + "]";
	}
}

Comment.java,加注JacksonAnnotation类型的标签

package info.kuyur.jsondemo.models;

import org.codehaus.jackson.annotate.JsonProperty;

public class Comment {

	@JsonProperty("作者")
	private User author;

	@JsonProperty("回复")
	private String content;

	public User getAuthor() {
		return author;
	}

	public void setAuthor(User author) {
		this.author = author;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	@Override
	public String toString() {
		return "Comment [content=" + content + ", author=" + author + "]";
	}
}

User的一条json字符串,成员变量名作为key,反序列化通过。
{"name": "一桶浆糊", "mailAddress": "yi.tong@jianghu.com"}

	@Test
	public void testReadJsonFromString1() {
		String jsonString = "{\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}";
		User user = Util.readJsonFromString(jsonString, User.class);
		System.out.println(user);
		assertEquals(user.getName(), "一桶浆糊");
		assertEquals(user.getMailAddress(), "yi.tong@jianghu.com");
	}

Jackson能从User的型(Class<User>)中,以json字符串中的key(name和mailAddress)来搜索成员变量名,再通过规范化的setter设置成员变量值。
含有参数的public构造子对Jackson是没意义的,因为对java而言参数变量名没有意义,有意义的只是参数类型。
以参数类型和函数名为检索条件通过反射可以确定唯一的函数,以参数类型为检索条件可以确定唯一的构造子,但Jackson不会去使用这种带参数的构造子

	public User(String name, String mailAddress) {
		super();
		this.name = name;
		this.mailAddress = mailAddress;
	}
	public User(String mingzi, String youxiang) {
		super();
		this.name = mingzi;
		this.mailAddress = youxiang;
	}

上面两个构造子完全一样,它们的函数原型相同,都是public User(String, String),你不能在一个类里同时定义两个原型相同的函数。
再次强调,参数名没有意义,如果调用这种构造子,Jackson不会懂得name和mailAddress那个先,那个后。
因此,为了让Jackson能自动反序列化,数据模型的public无参构造子以及规范化setter必须实现

Archive的一条json字符串,key在JAXBAnnotation类型的标注中定义
{"ID": "1000", "标题": "18岁的那天", "正文": "18岁那天的早上..."}

	@Test
	public void testReadJsonFromStringButFail() {
		String jsonString = "{\"ID\": \"1000\", \"标题\": \"18岁的那天\", \"正文\": \"18岁那天的早上...\"}";
		Archive archive = Util.readJsonFromString(jsonString, Archive.class);
		System.out.println(archive);
	}

很可惜,反序列化失败,ObjectMapper不支持JAXBAnnotation类型标签。

Comment的一条json字符串,key在JacksonAnnotation类型的标注中定义
{"作者": {"name": "铅球万袋", "mailAddress": "qian.qiu@wandai.com"}, "回复": "骚年よ、大志を抱け"}

	@Test
	public void testReadJsonFromString4() {
		String jsonString = "{\"作者\": {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"回复\": \"骚年よ、大志を抱け\"}";
		Comment comment = Util.readJsonFromString(jsonString, Comment.class);
		System.out.println(comment);
		assertEquals(comment.getContent(), "骚年よ、大志を抱け");
		assertEquals(comment.getAuthor().getName(), "铅球万袋");
		assertEquals(comment.getAuthor().getMailAddress(), "qian.qiu@wandai.com");
	}

反序列化通过。同时Comment的成员变量author也反序列化成功。因此Jackson反序列化复杂的数据模型没有问题。

再来测试泛型

数组类型的一条json
[{"name": "铅球万袋", "mailAddress": "qian.qiu@wandai.com"}, {"name": "一桶浆糊", "mailAddress": "yi.tong@jianghu.com"}]

	@Test
	public void testReadJsonFromString5() {
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		User[] users = Util.readJsonFromString(jsonString, User[].class);
		for (User user : users) {
			System.out.println(user);
		}
	}

转换成功,ObjectMapper支持转换到特定类型的数组。
User[] users的型也即User[].class是GenericArrayType,通过getGenericComponentType()接口,可以获得User的型,这样Jackson就可以成功构造User类型的数组。

但如果我们想转换成泛型容器呢?

	@Test
	public void testReadJsonFromString5ButFail() {
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		List<User> users = Util.readJsonFromString(jsonString, List.class);
		for (User user : users) {
			System.out.println(user);
		}
	}

这种转换不会成功,List.class提供给Jackson的信息并不足够,Jackson并不懂得List里面要装的是User类型的数据。

简单的字符串数组/整型数组/布尔数组是能转换成List<String>,List<Integer>,List<Boolean>的。
例如,下面的json反序列化通过

	@Test
	public void testReadJsonFromString12() {
		String jsonString = "[1, 2]";
		List<Integer> integers = Util.readJsonFromString(jsonString, List.class);
		for (Integer i : integers) {
			System.out.println(i);
		}
	}

Jackson能从json中推导出List中所要装的东西的类型。
(注意[1, 2]是整型数组,["1", "2"]是字符串数组,json自身能携带少量的型信息)

testReadJsonFromString5ButFail用例中,事实上Jackson将(希望是User类型的)json对象转换成了LinkedHashMap类型的对象,LinkedHashMap当然不能转换到User类型,于是就抛出了ClassCastException

将容器修改为List<Object>类型:

	@Test
	public void testReadJsonFromString13() {
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		List<Object> users = Util.readJsonFromString(jsonString, List.class);
		for (Object user : users) {
			System.out.println(user);
		}
	}

或者更直接的List<LinkedHashMap>类型:

	@Test
	public void testReadJsonFromString14() {
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		List<LinkedHashMap> users = Util.readJsonFromString(jsonString, List.class);
		for (LinkedHashMap user : users) {
			System.out.println(user);
		}
	}

可以反序列化成功,但对象已经面目全非,不是我们想要的了。

但我们很想很想要List<User>类型的数据啊,怎么办?那是下一篇的故事了。

总结一下:Jackson的ObjectMapper的使用有两个限制:
一是标签类型只能是JacksonAnnotation或者不加标签,数据模型不加标签的话,json就只能迁就数据模型,但如果json的字段名(key)不是我们所能控制的话,比如来一个"城市": "广州",麻烦可就大了;
二是对泛型容器的有限支持(这句话并不准确,事实上ObjectMapper的另外一个接口支持更加强大的【型】——ParameterizedType,可以实现我们的需求)。

发表评论

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: :-? :?: :!: