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

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

上篇:https://kuyur.info/blog/archives/2729

上篇介绍了ObjectMapper的readValue(String, Class<T>)接口,这个接口的源虽然是字符串,但ObjectMapper实际上重载了众多的readValue,源可以是二进制数组(byte[])/文件(File)/资源定位符(URL)/输入流(InputStream)等,json源并不是关注重点。

Jackson专门为加注JAXBAnnotation标签的数据模型准备了JacksonJaxbJsonProvider。默认的构造函数支持JAXBAnnotation和JacksonAnnotation两种标签。但它的readFrom接口仅支持从InputStream读取json。

写成一个工具类的方法:

	@SuppressWarnings("unchecked")
	public static <T> T readJsonFromStream(InputStream is, Type genericType) {
		JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
		try {
			return (T)provider.readFrom(Object.class, genericType, new Annotation[0], MediaType.APPLICATION_JSON_TYPE, null, is);
		} catch (Exception e) {
			throw new RuntimeException("Deserialize from JSON failed.", e);
		}
	}

readFrom接口参数很繁杂,但对于一般转换而言,仅需要把【型】(Type genericType)和输入流(InputStream is)交给它即可。
由于Type是一切【型】的基础,因此readJsonFromStream(InputStream, Type)的转换能力要比readJsonFromString(String, Class<T>)强。

测试一下转换能力

Comment的一条json,JacksonAnnotation标签,反序列化毫无压力
{"作者": {"name": "铅球万袋", "mailAddress": "qian.qiu@wandai.com"}, "回复": "骚年よ、大志を抱け"}

	@Test
	public void testReadJsonFromString15() throws UnsupportedEncodingException {
		String jsonString = "{\"作者\": {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"回复\": \"骚年よ、大志を抱け\"}";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			Comment comment = Util.readJsonFromStream(bais, Comment.class);
			System.out.println(comment);
			assertEquals(comment.getContent(), "骚年よ、大志を抱け");
			assertEquals(comment.getAuthor().getName(), "铅球万袋");
			assertEquals(comment.getAuthor().getMailAddress(), "qian.qiu@wandai.com");
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

Stream遵循谁创建谁关闭的原则。ByteArrayInputStream事实上不需要关闭。

Archive的一条json,JAXBAnnotation标签,反序列化通过
{"ID": "1000", "标题": "18岁的那天", "正文": "18岁那天的早上...", "作者": {"name": "一桶浆糊", "mailAddress": "yi.tong@jianghu.com"}}

	@Test
	public void testReadJsonFromString3() throws UnsupportedEncodingException {
		String jsonString = "{\"ID\": \"1000\", \"标题\": \"18岁的那天\", \"正文\": \"18岁那天的早上...\", \"作者\": {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}}";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			Archive archive = Util.readJsonFromStream(bais, Archive.class);
			System.out.println(archive);
			assertEquals(archive.getBlogId(), 1000);
			assertEquals(archive.getTitle(), "18岁的那天");
			assertEquals(archive.getText(), "18岁那天的早上...");
			assertNotNull(archive.getAuthor());
			User author = archive.getAuthor();
			assertEquals(author.getName(), "一桶浆糊");
			assertEquals(author.getMailAddress(), "yi.tong@jianghu.com");
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

再来测试泛型容器

User数组,我们希望转换为List<User>
[{"name": "铅球万袋", "mailAddress": "qian.qiu@wandai.com"}, {"name": "一桶浆糊", "mailAddress": "yi.tong@jianghu.com"}]
List的型是List.class,用反射从List.class获取构造函数构造的List,能存储Object,但Jackson并不懂得应该用User.class来构造元素,List.class包含的型信息是不足够的。
但List<User>的型是什么?List<User>.class这种语法并不存在,怎样才能获取List<User>的型?

装载了具体类型的元素的泛型容器的【型】是ParameterizedType类型,ParameterizedType是一个接口
(官方将ParameterizedType翻译成参数化类型)

	@SuppressWarnings("unused")
	private List<User> forGetType;

	@Test
	public void testParameterizedType() {
		try {
			Field userListField = getClass().getDeclaredField("forGetType");
			assertTrue(userListField.getGenericType() instanceof ParameterizedType);
			ParameterizedType userListType = (ParameterizedType)userListField.getGenericType();
			System.out.println(userListType.getClass());
			System.out.println(userListType.getRawType());
			Type[] typeArguments = userListType.getActualTypeArguments();
			for (Type type : typeArguments) {
				assertEquals(type, User.class);
				System.out.println(type);
			}
		} catch (SecurityException e) {
			e.printStackTrace();
			fail();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
			fail();
		}
	}

输出结果
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
interface java.util.List
class info.kuyur.jsondemo.models.User

forGetType只是为了获取List<User>的型而存在,不需要实例化。
第八行表明,List<User>的【型】确确实实是ParameterizedType
第十行透露了,Java内置的ParameterizedType实现是sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl

ParameterizedType的Type getRawType()接口,返回泛型容器自身的【型】。
ParameterizedType的Type[] getActualTypeArguments()接口,返回参与构造容器的型别参数们的【型】。
List<User>实际上是以User.class为型别参数,结合母容器List.class,构造出一种新的具体型:List<User>型,getActualTypeArguments()返回[User.class]。
同理,Map<Integer, User>实际上是以[Integer.class, User.class]为型别参数,结合母容器Map.class,构造出具体型:Map<Integer, User>型,getActualTypeArguments()返回[Integer.class, User.class]。
ParameterizedType可以反复递归,也即复杂似List<Map<Integer, User>>型同样能够用ParameterizedType表达出来。
ParameterizedType含有足够的【型】信息,让Jackson能够构造泛型容器中的元素。

(在这里说句题外话,学习Haskell对泛型的深入理解大有好处)

反序列化为List<User>,终于成功

	@Test
	public void testReadJsonFromString6() throws Exception {
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			Field userListField = UtilTest.class.getDeclaredField("forGetType");
			assertTrue(userListField.getGenericType() instanceof ParameterizedType);
			ParameterizedType userListType = (ParameterizedType)userListField.getGenericType();
			List<User> users  = Util.readJsonFromStream(bais, userListType);
			for (User user : users) {
				System.out.println(user);
				if (user.getName().equals("铅球万袋")) {
					assertEquals(user.getMailAddress(), "qian.qiu@wandai.com");
				}
				if (user.getName().equals("一桶浆糊")) {
					assertEquals(user.getMailAddress(), "yi.tong@jianghu.com");
				}
			}
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

为了获得List<User>型信息,定义了一个从来不会使用fotGetType,代码太丑陋,还有别的方法吗?

反射为我们提供了从函数的返回类型获取参数化类型信息的另外一个方法

public class Util {
	...
	public static ParameterizedType getParameterizedReturnType(Class<?> clazz, String methodName, Class<?>... param) {
		try {
			Method method = clazz.getDeclaredMethod(methodName, param);
			Type type = method.getGenericReturnType();
			if (type instanceof ParameterizedType) {
				return (ParameterizedType) type;
			} else {
				throw new RuntimeException(type.getClass() + "is not ParameterizedType");
			}
		} catch (SecurityException e) {
			throw new RuntimeException(e);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}
}

假如有那么一个客户端需要从json反序列化,可以通过方法自身的返回值去获取参数化类型信息

package info.kuyur.jsondemo.client;

import info.kuyur.jsondemo.models.User;
import info.kuyur.jsondemo.utils.Util;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.List;

public class UserClient {

	public List<User> getUserList() {
		Type type = Util.getParameterizedReturnType(getClass(), "getUserList");
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			return Util.readJsonFromStream(bais, type);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}
}

测试一下,转换无压力:

	@Test
	public void testGetUserList() {
		UserClient userClient = new UserClient();
		List<User> users = userClient.getUserList();
		for (User user : users) {
			System.out.println(user);
		}
	}

但动态从方法获取【型】还是太丑了,而且还必须有个方法才能用,这和先定义个”forGetType”没什么区别啊

于是再玩一下,实现自己的ParameterizedType
实际上sun已经给我们实现了一个,就在jaxb包中,将它拷贝出来,改改可见性

package info.kuyur.jsondemo.utils;

import java.lang.reflect.MalformedParameterizedTypeException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;

/**
 * {@link ParameterizedType} implementation.
 */
public class MoeParameterizedTypeImpl implements ParameterizedType {
	private Type[] actualTypeArguments;
	private Class<?> rawType;
	private Type ownerType;

	public MoeParameterizedTypeImpl(Class<?> rawType, Type[] actualTypeArguments, Type ownerType) {
		this.actualTypeArguments = actualTypeArguments;
		this.rawType = rawType;
		if (ownerType != null) {
			this.ownerType = ownerType;
		} else {
			this.ownerType = rawType.getDeclaringClass();
		}
		validateConstructorArguments();
	}

	private void validateConstructorArguments() {
		TypeVariable<?>[] formals = rawType.getTypeParameters();
		// check correct arity of actual type args
		if (formals.length != actualTypeArguments.length) {
			throw new MalformedParameterizedTypeException();
		}
		for (int i = 0; i < actualTypeArguments.length; i++) {
			// check actuals against formals' bounds
		}
	}

	public Type[] getActualTypeArguments() {
		return actualTypeArguments.clone();
	}

	public Class<?> getRawType() {
		return rawType;
	}

	public Type getOwnerType() {
		return ownerType;
	}

	/*
	 * From the JavaDoc for java.lang.reflect.ParameterizedType
	 * "Instances of classes that implement this interface must
	 * implement an equals() method that equates any two instances
	 * that share the same generic type declaration and have equal
	 * type parameters."
	 */
	@Override
	public boolean equals(Object o) {
		if (o instanceof ParameterizedType) {
			// Check that information is equivalent
			ParameterizedType that = (ParameterizedType) o;

			if (this == that)
				return true;

			Type thatOwner = that.getOwnerType();
			Type thatRawType = that.getRawType();

			return (ownerType == null ? thatOwner == null :	ownerType.equals(thatOwner)) &&
				(rawType == null ? thatRawType == null : rawType.equals(thatRawType)) &&
				Arrays.equals(actualTypeArguments, that.getActualTypeArguments());
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		return Arrays.hashCode(actualTypeArguments) ^
				(ownerType == null ? 0 : ownerType.hashCode()) ^
				(rawType == null ? 0 : rawType.hashCode());
	}

	@SuppressWarnings("rawtypes")
	public String toString() {
		StringBuilder sb = new StringBuilder();

		if (ownerType != null) {
			if (ownerType instanceof Class)
				sb.append(((Class) ownerType).getName());
			else
				sb.append(ownerType.toString());

			sb.append(".");

			if (ownerType instanceof MoeParameterizedTypeImpl) {
				// Find simple name of nested type by removing the
				// shared prefix with owner.
				sb.append(rawType.getName().replace(((MoeParameterizedTypeImpl) ownerType).rawType.getName() + "$",""));
			} else
				sb.append(rawType.getName());
		} else
			sb.append(rawType.getName());

		if (actualTypeArguments != null && actualTypeArguments.length > 0) {
			sb.append("<");
			boolean first = true;
			for (Type t : actualTypeArguments) {
				if (!first)
					sb.append(", ");
				if (t instanceof Class)
					sb.append(((Class) t).getName());
				else
					sb.append(t.toString());
				first = false;
			}
			sb.append(">");
		}

		return sb.toString();
	}
}

下面构造自己的萌萌的参数化类型(ownerType实际上可以完全无视)
List<User>型的构造:

Type[] typeArguments = new MoeParameterizedTypeImpl[1];
typeArguments[0] = new MoeParameterizedTypeImpl(User.class, new MoeParameterizedTypeImpl[0], null);
MoeParameterizedTypeImpl userListType = new MoeParameterizedTypeImpl(List.class, typeArguments, null);

User不是泛型,但用ParameterizedType仍然可以表达,第二行的等号右边即是User的ParameterizedType形式的型,可以认为User是一种退化的泛型

用萌萌的参数化类型反序列化,无压力

	@Test
	public void testReadJsonFromString7() throws UnsupportedEncodingException {
		Type[] typeArguments = new MoeParameterizedTypeImpl[1];
		typeArguments[0] = new MoeParameterizedTypeImpl(User.class, new MoeParameterizedTypeImpl[0], null);
		System.out.println(typeArguments[0]);
		MoeParameterizedTypeImpl userListType = new MoeParameterizedTypeImpl(List.class, typeArguments, null);
		System.out.println(userListType);
		String jsonString = "[{\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}]";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			List<User> users  = Util.readJsonFromStream(bais, userListType);
			for (User user : users) {
				System.out.println(user);
				if (user.getName().equals("铅球万袋")) {
					assertEquals(user.getMailAddress(), "qian.qiu@wandai.com");
				}
				if (user.getName().equals("一桶浆糊")) {
					assertEquals(user.getMailAddress(), "yi.tong@jianghu.com");
				}
			}
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

Map<Integer, User>型,同样毫无压力:

	@Test
	public void testReadJsonFromString10() throws UnsupportedEncodingException {
		Type[] typeArguments = new MoeParameterizedTypeImpl[2];
		typeArguments[0] = new MoeParameterizedTypeImpl(Integer.class, new MoeParameterizedTypeImpl[0], null);
		typeArguments[1] = new MoeParameterizedTypeImpl(User.class, new MoeParameterizedTypeImpl[0], null);
		MoeParameterizedTypeImpl userMapType = new MoeParameterizedTypeImpl(Map.class, typeArguments, null);
		System.out.println(userMapType);
		String jsonString = "{\"1\" : {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"2\" : {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}}";
		ByteArrayInputStream bais = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			Map<Integer, User> users = Util.readJsonFromStream(bais, userMapType);
			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);
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

如果拿ObjectMapper的readValue(String, Class<T>)来转换,因为型信息不足,转换结果将会变得面目全非

	@SuppressWarnings("unchecked")
	@Test
	public void testReadJsonFromString8() {
		String jsonString = "{\"1\" : {\"name\": \"铅球万袋\", \"mailAddress\": \"qian.qiu@wandai.com\"}, \"2\" : {\"name\": \"一桶浆糊\", \"mailAddress\": \"yi.tong@jianghu.com\"}}";
		Map<Integer, User> users = Util.readJsonFromString(jsonString, Map.class);
		assertEquals(users.size(), 2);

		User user1 = users.get(Integer.valueOf(1));
		System.out.println(user1); // null
		User user2 = users.get(Integer.valueOf(2));
		System.out.println(user2); // null

		Iterator<Integer> iter = users.keySet().iterator();
		while(iter.hasNext()) {
			Object val = users.get(iter.next());
			System.out.println(val);
			assertFalse(val instanceof User);
			assertTrue(val instanceof Map);
		}

		Object userMap1 = users.get("1"); // actually key is String type
		assertNotNull(userMap1);
		System.out.println(userMap1); // actually value is LinkedHashMap type

		Object userMap2 = users.get("2");
		assertNotNull(userMap2);
		System.out.println(userMap2);
	}

再来玩一下自定义的泛型类型

定义一个三元组:

package info.kuyur.jsondemo.models;

public class Triple<F, S, T> {

	private F first;
	private S second;
	private T third;

	public Triple() {
	}

	public Triple(F first, S second, T third) {
		this.first = first;
		this.second = second;
		this.third = third;
	}

	public F getFirst() {
		return first;
	}

	public void setFirst(F first) {
		this.first = first;
	}

	public S getSecond() {
		return second;
	}

	public void setSecond(S second) {
		this.second = second;
	}

	public T getThird() {
		return third;
	}

	public void setThird(T third) {
		this.third = third;
	}

	@Override
	public String toString() {
		return "Triple [first=" + first + ", second=" + second + ", third="
				+ third + "]";
	}
}

从下面的json反序列化出Triple<User, Archive,Comment>,注意json中的字段顺序不应该对转换有影响
{"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":"一桶浆糊"}}

	@Test
	public void testReadJsonFromString16() throws UnsupportedEncodingException {
		Type[] typeArguments = new MoeParameterizedTypeImpl[3];
		typeArguments[0] = new MoeParameterizedTypeImpl(User.class, new MoeParameterizedTypeImpl[0], null);
		typeArguments[1] = new MoeParameterizedTypeImpl(Archive.class, new MoeParameterizedTypeImpl[0], null);
		typeArguments[2] = new MoeParameterizedTypeImpl(Comment.class, new MoeParameterizedTypeImpl[0], null);
		MoeParameterizedTypeImpl tripleType = new MoeParameterizedTypeImpl(Triple.class, typeArguments, null);
		System.out.println(tripleType);
		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 = null;
		try {
			bais = new ByteArrayInputStream(jsonString.getBytes("UTF-8"));
			Triple<User, Archive, Comment> triple = Util.readJsonFromStream(bais, tripleType);
			System.out.println(triple.getFirst());
			System.out.println(triple.getSecond());
			System.out.println(triple.getThird());
		} finally {
			if (bais != null) {
				try {
					bais.close();
				} catch (Exception e) {}
			}
		}
	}

输出:

info.kuyur.jsondemo.models.Triple<info.kuyur.jsondemo.models.User, info.kuyur.jsondemo.models.Archive, info.kuyur.jsondemo.models.Comment>
User [name=一桶浆糊, mailAddress=yi.tong@jianghu.com]
Archive [blogId=1000, title=18岁的那天, text=18岁那天的早上..., author=User [name=一桶浆糊, mailAddress=yi.tong@jianghu.com]]
Comment [content=骚年よ、大志を抱け, author=User [name=铅球万袋, mailAddress=qian.qiu@wandai.com]]

完美转换!

User/Archive/Comment是退化的参数化类型,因此Triple<User, Archive, Comment>的参数化类型的构造过程可以简化为:

Type[] typeArguments = {User.class, Archive.class, Comment.class};
MoeParameterizedTypeImpl tripleType = new MoeParameterizedTypeImpl(Triple.class, typeArguments, null);

下篇介绍最后的杀器,Jackson提供的TypeReference。

本文目前尚无任何评论.

发表评论

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