实例解析Json反序列化之ObjectMapper(自定义实现反序列化方法)
对于服务器端开发人员而言,调用第三方接口获取数据,将其“代理”转化并返给客户端几乎是家常便饭的事儿。 一般情况下,第三方接口返回的数据类型是json格式,而服务器开发人员则需将json格式的数据转换成对象,继而对其进行处理并封装,以返回给客户端。
在不是特别考虑效率的情况下(对于搜索、缓存等情形可以考虑使用thrift和protobuffer),通常我们会选取jackson包中的objectmapper类对串反序列化以得到相应对象。通常会选取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; } }
总结
以上就是本文关于实例解析json反序列化之objectmapper(自定义实现反序列化方法)的全部内容,希望对大家有所帮助。欢迎大家参阅本站其他专题,有什么问题可以留言,小编会及时回复大家的。