实例解析Json反序列化之ObjectMapper(自定义实现反序列化方法)
作者:皮斯特劳沃 发布时间:2023-11-23 18:07:25
对于服务器端开发人员而言,调用第三方接口获取数据,将其“代理”转化并返给客户端几乎是家常便饭的事儿。 一般情况下,第三方接口返回的数据类型是json格式,而服务器开发人员则需将json格式的数据转换成对象,继而对其进行处理并封装,以返回给客户端。
在不是特别考虑效率的情况下(对于搜索、缓存等情形可以考虑使用thrift和protobuffer),通常我们会选取jackson包中的ObjectMapper类对json串反序列化以得到相应对象。通常会选取readValue(String content, Class<T>valueType)方法进行反序列化。
ObjectMapper的readValue方法将json串反序列化为对象的过程大致为: 依据传入的json串和目标对象类型分别创建JsonParse和JavaType,随后生成DeserializationConfig、DeserializationContext、JsonDeserializer,其中JsonDeserializer的实现类决定将要执行哪一种类型解析(Bean、Map、String等),JsonParse中存储了待解析字符串及其它信息,在解析的过程中通过token来判断当前匹配的类型(例如:如果遇到{,将其判断为对象类型的起始位置;遇到[,将其判断为集合类型的起始位置),一旦确定了类型,则跳入与之对应的反序列化类中进行处理,得到结果,然后token往后移动,接着解析下一个串。可以看做类似递归的方式进行解析,当通过token判断为一个对象时,则会跳入BeanDeserializer中进行解析,随后遍历该对象的所有字段,如果字段是字符串,则跳到StringDeserializer中进行解析,如果字段是数组,则跳到CollectionDeserializer中进行解析,直到解析完整个字符串为止。也可以看做类似而树的深度遍历,理解起来还是挺容易的。
下面将简单介绍ObjectMapper的readValue方法进行反序列化的过程:
a:通过json串和对象类型得到JsonParser和JavaType。
public <T> T readValue(String content, Class<T> valueType)
throws IOException, JsonParseException, JsonMappingException
{
return (T) _readMapAndClose(_jsonFactory.createParser(content), _typeFactory.constructType(valueType));
}
//获取json解析器,其中包含带解析的串
public JsonParser createParser(String content) throws IOException, JsonParseException {
final int strLen = content.length();
// Actually, let's use this for medium-sized content, up to 64kB chunk (32kb char)
if (_inputDecorator != null || strLen > 0x8000 || !canUseCharArrays()) {
// easier to just wrap in a Reader than extend InputDecorator; or, if content
// is too long for us to copy it over
return createParser(new StringReader(content));
}
IOContext ctxt = _createContext(content, true);
char[] buf = ctxt.allocTokenBuffer(strLen);
content.getChars(0, strLen, buf, 0);
return _createParser(buf, 0, strLen, ctxt, true);
}
//将待解析的类型转化为JavaType类型
public JavaType constructType(Type type) {
return _constructType(type, null);
}
protected JavaType _constructType(Type type, TypeBindings context)
{
JavaType resultType;
// simple class?
if (type instanceof Class<?>) {
resultType = _fromClass((Class<?>) type, context);
}
// But if not, need to start resolving.
else if (type instanceof ParameterizedType) {
resultType = _fromParamType((ParameterizedType) type, context);
}
else if (type instanceof JavaType) { // [Issue#116]
return (JavaType) type;
}
else if (type instanceof GenericArrayType) {
resultType = _fromArrayType((GenericArrayType) type, context);
}
else if (type instanceof TypeVariable<?>) {
resultType = _fromVariable((TypeVariable<?>) type, context);
}
else if (type instanceof WildcardType) {
resultType = _fromWildcard((WildcardType) type, context);
} else {
// sanity check
throw new IllegalArgumentException("Unrecognized Type: "+((type == null) ? "[null]" : type.toString()));
}
if (_modifiers != null && !resultType.isContainerType()) {
for (TypeModifier mod : _modifiers) {
resultType = mod.modifyType(resultType, type, context, this);
}
}
return resultType;
}
b、获取反序列化配置对象和上下文对象,进行第一步的序列化操作。
protected Object _readMapAndClose(JsonParser jp, JavaType valueType)
throws IOException, JsonParseException, JsonMappingException
{
try {
Object result;
DeserializationConfig cfg = getDeserializationConfig();
DeserializationContext ctxt = createDeserializationContext(jp, cfg);
//依据valueType得到反序列化的解析器
// 对象对应的是beanDeserializer map对应的是MapDeserializer 。。。。
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
if (cfg.useRootWrapping()) {
result = _unwrapAndDeserialize(jp, ctxt, cfg, valueType, deser);
} else {
//如果是对象,则调到BeanDeserializer类中进行解析
result = deser.deserialize(jp, ctxt);
}
ctxt.checkUnresolvedObjectId();
}
// Need to consume the token too
jp.clearCurrentToken();
return result;
} finally {
try {
jp.close();
} catch (IOException ioe) { }
}
}
c、跳入到BeanDeserializer类中。
下面以BeanDeserializer为例进行讲解:
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
JsonToken t = p.getCurrentToken();
// common case first
if (t == JsonToken.START_OBJECT) { // TODO: in 2.6, use 'p.hasTokenId()'
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt, p.nextToken());
}
p.nextToken();
if (_objectIdReader != null) {
return deserializeWithObjectId(p, ctxt);
}
return deserializeFromObject(p, ctxt);
}
return _deserializeOther(p, ctxt, t);
}
/**
* Streamlined version that is only used when no "special"
* features are enabled.
*/
private final Object vanillaDeserialize(JsonParser p,
DeserializationContext ctxt, JsonToken t)
throws IOException
{
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken();
if (!_beanProperties.findDeserializeAndSet(p, ctxt, bean, propName)) {
handleUnknownVanilla(p, ctxt, bean, propName);
}
}
return bean;
}
/**
* Convenience method that tries to find property with given name, and
* if it is found, call {@link SettableBeanProperty#deserializeAndSet}
* on it, and return true; or, if not found, return false.
* Note, too, that if deserialization is attempted, possible exceptions
* are wrapped if and as necessary, so caller need not handle those.
*
* @since 2.5
*/
public boolean findDeserializeAndSet(JsonParser p, DeserializationContext ctxt,
Object bean, String key) throws IOException
{
if (_caseInsensitive) {
key = key.toLowerCase();
}
int index = key.hashCode() & _hashMask;
Bucket bucket = _buckets[index];
// Let's unroll first lookup since that is null or match in 90+% cases
if (bucket == null) {
return false;
}
// Primarily we do just identity comparison as keys should be interned
if (bucket.key == key) {
try {
bucket.value.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, key, ctxt);
}
return true;
}
return _findDeserializeAndSet2(p, ctxt, bean, key, index);
}
MethodProperty
@Override
public void deserializeAndSet(JsonParser jp, DeserializationContext ctxt,
Object instance) throws IOException
{
Object value = deserialize(jp, ctxt);
try {
//将得到的结果放入反序列化对应的对象中
_setter.invoke(instance, value);
} catch (Exception e) {
_throwAsIOE(e, value);
}
}
public final Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
JsonToken t = p.getCurrentToken();
if (t == JsonToken.VALUE_NULL) {
return (_nullProvider == null) ? null : _nullProvider.nullValue(ctxt);
}
if (_valueTypeDeserializer != null) {
return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
}
return _valueDeserializer.deserialize(p, ctxt);
}
//如果继承了JsonDeserializer类重写了deseriakize方法,则会跳转到对应注入的类中进行处理
//不出意外的话最后都会调用 DeserializationContext的readValue(JsonParser p, Class<T> type)方法,然后会根据type的类型跳转到对应的反序列化类中进行处理。
public <T> T readValue(JsonParser p, Class<T> type) throws IOException {
return readValue(p, getTypeFactory().constructType(type));
}
@SuppressWarnings("unchecked")
public <T> T readValue(JsonParser p, JavaType type) throws IOException {
//得到最终解析的类型,Map list string。。。。
JsonDeserializer<Object> deser = findRootValueDeserializer(type);
if (deser == null) {
}
return (T) deser.deserialize(p, this);
}
//例如这里如果是一个map,则会调用MapDeserializer的deserizlize方法得到最后的返回结果。
//对于集合类,会通过token按照顺序解析生成一个个的集合对象并放入集合中。
JsonToken t;
while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
try {
Object value;
if (t == JsonToken.VALUE_NULL) {
value = valueDes.getNullValue();
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
}
if (referringAccumulator != null) {
referringAccumulator.add(value);
} else {
result.add(value);
}
} catch (UnresolvedForwardReference reference) {
if (referringAccumulator == null) {
throw JsonMappingException
.from(p, "Unresolved forward reference but no identity info", reference);
}
Referring ref = referringAccumulator.handleUnresolvedReference(reference);
reference.getRoid().appendReferring(ref);
} catch (Exception e) {
throw JsonMappingException.wrapWithPath(e, result, result.size());
}
}
return result;
在不同的业务场景下,第三方接口返回的数据类型可能会发生变化,比如最初第三方业务代码是使用php实现的,而与之对接的服务器端也是用php实现的。后来,又成立了以Java为开发语言的服务器端开发小组,此时,对接第三方可能会出现问题。第三方返回数据类型的不唯一性,可能会使Java开发人员无法“正常”反序列化第三方接口返回的json串。例如:第三方接口返回的字段中,当字段为空时,返回的是数组;而字段不为空时,返回的却是对象。这样,那么通过ObjectMapper进行解析时,就会抛出异常,导致服务器端无法正常将数据返回给客户端。面对这样的问题,可能有 以下两种解决方法:
第一种解决方法是对bean中每个字段set方法内进行判断,当解析字符串是一个数组时,则返回空对象;
当解析的字符串不为空时,就会特别的麻烦,默认情况下,会将Json串解析成一个map,其中key为bean中字段的名称,value为bean的值。这样,就需要创建一个新的bean,随后依次从map中取出对应字段的值,然后再set到bean中。显然,这种方式很麻烦,一旦第三方字段发生变化时,需要不停地维护这段代码。
第二种解决方法是继承JsonDeserialize,并重写反序列化方法。通过源码可知,JsonDeserializer抽象类是处理反序列化的类,只需在Bean类中的字段上加入注解@JsonDeserialize(using=xxx.class),并且xxx类要继承JsonDeserializer类,且重新对应的deserialize方法,在该方法中进行相应处理即可。在该方法中处理待反序列化字段可能出现的多种不同情况,详情见源码。
这里需要注意的是:当反序列化字段是一个对象,而第三方返回的数据为一个数组时,在重写deserialize方法时,如果判断出当前token指向的是一个数组,而此时需得到空对象。此时,不能直接返回空对象,必须调用readValue方法,目的是将token移动到正确的位置,否则,将创建一些奇怪的对象。
对于第二种解决方法,下面举例说明:
package com.string;
import java.util.Map;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
public class Comment {
public String id;
@JsonDeserialize(using = ImgPackSerializer.class)
public Map<String, String> imgPack;
@JsonDeserialize(using = CoopSerializer.class)
public Coop coop;
public Coop getCoop() {
return coop;
}
public void setCoop(Coop coop) {
this.coop = coop;
}
public Map<String, String> getImgPack() {
return imgPack;
}
public void setImgPack(Map<String, String> imgPack) {
this.imgPack = imgPack;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
class Coop {
public Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
package com.string;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestJson {
static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static void main(String[] args) {
String s = "{\"code\":\"1\",\"comm\":[{\"imgPack\":{\"abc\":\"abc\"},\"coop\":[]}],\"name\":\"car\"}";
try {
Response readValue = OBJECT_MAPPER.readValue(s, Response.class);
System.err.println(readValue.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Response {
public String code;
public List<Comment> comm;
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public List<Comment> getComm() {
return comm;
}
public void setComm(List<Comment> comm) {
this.comm = comm;
}
}
class CoopSerializer extends JsonDeserializer<Coop> {
@Override
public Coop deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonToken currentToken = jp.getCurrentToken();
if (currentToken == JsonToken.START_ARRAY) {
// return null; //error may create more object
// jp.nextToken(); //error
return ctxt.readValue(jp, Object.class) == null ? null : null;
} else if (currentToken == JsonToken.START_OBJECT) {
return (Coop) ctxt.readValue(jp, Coop.class);
}
return null;
}
}
class ImgPackSerializer extends JsonDeserializer<Map<String, String>> {
@Override
public Map<String, String> deserialize(JsonParser jp,
DeserializationContext ctxt) throws IOException,
JsonProcessingException {
JsonToken currentToken = jp.getCurrentToken();
if (currentToken == JsonToken.START_ARRAY) {
return ctxt.readValue(jp, Object.class) == null ? null : null;
} else if (currentToken == JsonToken.START_OBJECT) {
return (Map<String, String>) ctxt.readValue(jp, Map.class);
}
return null;
}
}
来源:http://blog.csdn.net/pistolove/article/details/50868105


猜你喜欢
- 今天,我们接着讲微信支付的系列教程,前面,我们讲了这个微信红包和扫码支付。现在,我们讲讲这个公众号支付。公众号支付的应用环境常见的用户通过公
- 大家好,欢迎来到老胡的博客,今天我们继续了解设计模式中的职责链模式,这是一个比较简单的模式。跟往常一样,我们还是从一个真实世界的例子入手,这
- hibernate一级缓存和二级缓存的区别缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用
- 本文所述实例主要实现WPF项目中C#改变DataGrid某一行和单元格颜色的功能。分享给大家供大家参考。具体方法如下:如果要改变DataGr
- Kotlin 面向对象这几天一直在准备考试,实在没有时间,已经过去了这么久,终于要到面向对象了!先看看Kotlin中的类长什么样吧.可以看到
- 在说struts2的线程安全之前,先说一下,什么是线程安全?这是一个网友讲的。如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会
- 解决My eclipse 工程发布时端口占用问题如果运行后如图的错,需要进行如下操作来解决:a:打开cmd,输入netstat -ano 找
- 题目:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。经典题,不多说,直接上代码import java.
- 本文实例为大家分享了Android实现背景图滑动变大松开回弹的具体代码,供大家参考,具体内容如下原图放大后1、自定义view继承Scroll
- 1. Spring Boot Condition功能与作用@Conditional是基于条件的自动化配置注解, 由Spring 4框架推出的
- 前言最近写了一篇博客是关于使用Jenkins来构建SVN+Maven项目 ,这里使用的的代码版本工具是SVN,但是事实上也有很多公司使用GI
- 本文实例讲述了C#通过WIN32 API实现嵌入程序窗体的方法,分享给大家供大家参考。具体如下:这是一个不使用COM,而是通过WIN32 A
- 使用对象访问类中的成员:对象名.成员变量;对象名.成员方法();成员变量的默认值:具体实例代码:public class StudentTe
- 流程引擎对象和其配置对象都是activiti的核心对象一、activiti的简单使用流程activiti在工作时,一般有以下几个步骤:创建一
- 本文实例讲述了Android4.0平板开发之隐藏底部任务栏的方法。分享给大家供大家参考,具体如下:getWindow().getDecorV
- 本文以实例代码实现了C#根据数字序号输出星期几,用户可通过输入数字0~6,输出星期各天的英语单词,程序中主要是演示if语句和switch语句
- 尽管我们通常认为通过JAVA的反射机制来访问其它类的私有字段和私有方法是可行的,其实并没有那么困难。 注释:只有在单独的JAVA程序中运行该
- JetBrains正在开发一种被称为Qodana的代码质量检测工具。它将JetBrains IDE具有的智能代码检查带入了项目CI/CD管道
- 开发语言:C#3.0 IDE:Visual Studio 2008 一、C#线程概述 在操作系统中一个进程至少要包含一个线程,然后,在某些时
- 快速排序快速排序是一种比较高效的排序算法,采用“分而治之”的思想,通过多次比较和交换来实现排序,在一