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,可以实现我们的需求)。
评论