實(shí)例解析Json反序列化之ObjectMapper(自定義實(shí)現(xiàn)反序列化方法)
對(duì)于服務(wù)器端開發(fā)人員而言,調(diào)用第三方接口獲取數(shù)據(jù),將其“代理”轉(zhuǎn)化并返給客戶端幾乎是家常便飯的事兒。 一般情況下,第三方接口返回的數(shù)據(jù)類型是json格式,而服務(wù)器開發(fā)人員則需將json格式的數(shù)據(jù)轉(zhuǎn)換成對(duì)象,繼而對(duì)其進(jìn)行處理并封裝,以返回給客戶端。
在不是特別考慮效率的情況下(對(duì)于搜索、緩存等情形可以考慮使用thrift和protobuffer),通常我們會(huì)選取jackson包中的ObjectMapper類對(duì)json串反序列化以得到相應(yīng)對(duì)象。通常會(huì)選取readValue(String content, Class<T>valueType)方法進(jìn)行反序列化。
ObjectMapper的readValue方法將json串反序列化為對(duì)象的過程大致為: 依據(jù)傳入的json串和目標(biāo)對(duì)象類型分別創(chuàng)建JsonParse和JavaType,隨后生成DeserializationConfig、DeserializationContext、JsonDeserializer,其中JsonDeserializer的實(shí)現(xiàn)類決定將要執(zhí)行哪一種類型解析(Bean、Map、String等),JsonParse中存儲(chǔ)了待解析字符串及其它信息,在解析的過程中通過token來(lái)判斷當(dāng)前匹配的類型(例如:如果遇到{,將其判斷為對(duì)象類型的起始位置;遇到[,將其判斷為集合類型的起始位置),一旦確定了類型,則跳入與之對(duì)應(yīng)的反序列化類中進(jìn)行處理,得到結(jié)果,然后token往后移動(dòng),接著解析下一個(gè)串??梢钥醋鲱愃七f歸的方式進(jìn)行解析,當(dāng)通過token判斷為一個(gè)對(duì)象時(shí),則會(huì)跳入BeanDeserializer中進(jìn)行解析,隨后遍歷該對(duì)象的所有字段,如果字段是字符串,則跳到StringDeserializer中進(jìn)行解析,如果字段是數(shù)組,則跳到CollectionDeserializer中進(jìn)行解析,直到解析完整個(gè)字符串為止。也可以看做類似而樹的深度遍歷,理解起來(lái)還是挺容易的。
下面將簡(jiǎn)單介紹ObjectMapper的readValue方法進(jìn)行反序列化的過程:
a:通過json串和對(duì)象類型得到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);
}
//將待解析的類型轉(zhuǎn)化為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、獲取反序列化配置對(duì)象和上下文對(duì)象,進(jìn)行第一步的序列化操作。
protected Object _readMapAndClose(JsonParser jp, JavaType valueType)
throws IOException, JsonParseException, JsonMappingException
{
try {
Object result;
DeserializationConfig cfg = getDeserializationConfig();
DeserializationContext ctxt = createDeserializationContext(jp, cfg);
//依據(jù)valueType得到反序列化的解析器
// 對(duì)象對(duì)應(yīng)的是beanDeserializer map對(duì)應(yīng)的是MapDeserializer 。。。。
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
if (cfg.useRootWrapping()) {
result = _unwrapAndDeserialize(jp, ctxt, cfg, valueType, deser);
} else {
//如果是對(duì)象,則調(diào)到BeanDeserializer類中進(jìn)行解析
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為例進(jìn)行講解:
@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 {
//將得到的結(jié)果放入反序列化對(duì)應(yīng)的對(duì)象中
_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方法,則會(huì)跳轉(zhuǎn)到對(duì)應(yīng)注入的類中進(jìn)行處理
//不出意外的話最后都會(huì)調(diào)用 DeserializationContext的readValue(JsonParser p, Class<T> type)方法,然后會(huì)根據(jù)type的類型跳轉(zhuǎn)到對(duì)應(yīng)的反序列化類中進(jìn)行處理。
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);
}
//例如這里如果是一個(gè)map,則會(huì)調(diào)用MapDeserializer的deserizlize方法得到最后的返回結(jié)果。
//對(duì)于集合類,會(huì)通過token按照順序解析生成一個(gè)個(gè)的集合對(duì)象并放入集合中。
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;
在不同的業(yè)務(wù)場(chǎng)景下,第三方接口返回的數(shù)據(jù)類型可能會(huì)發(fā)生變化,比如最初第三方業(yè)務(wù)代碼是使用php實(shí)現(xiàn)的,而與之對(duì)接的服務(wù)器端也是用php實(shí)現(xiàn)的。后來(lái),又成立了以Java為開發(fā)語(yǔ)言的服務(wù)器端開發(fā)小組,此時(shí),對(duì)接第三方可能會(huì)出現(xiàn)問題。第三方返回?cái)?shù)據(jù)類型的不唯一性,可能會(huì)使Java開發(fā)人員無(wú)法“正?!狈葱蛄谢谌浇涌诜祷氐膉son串。例如:第三方接口返回的字段中,當(dāng)字段為空時(shí),返回的是數(shù)組;而字段不為空時(shí),返回的卻是對(duì)象。這樣,那么通過ObjectMapper進(jìn)行解析時(shí),就會(huì)拋出異常,導(dǎo)致服務(wù)器端無(wú)法正常將數(shù)據(jù)返回給客戶端。面對(duì)這樣的問題,可能有 以下兩種解決方法:
第一種解決方法是對(duì)bean中每個(gè)字段set方法內(nèi)進(jìn)行判斷,當(dāng)解析字符串是一個(gè)數(shù)組時(shí),則返回空對(duì)象;
當(dāng)解析的字符串不為空時(shí),就會(huì)特別的麻煩,默認(rèn)情況下,會(huì)將Json串解析成一個(gè)map,其中key為bean中字段的名稱,value為bean的值。這樣,就需要?jiǎng)?chuàng)建一個(gè)新的bean,隨后依次從map中取出對(duì)應(yīng)字段的值,然后再set到bean中。顯然,這種方式很麻煩,一旦第三方字段發(fā)生變化時(shí),需要不停地維護(hù)這段代碼。
第二種解決方法是繼承JsonDeserialize,并重寫反序列化方法。通過源碼可知,JsonDeserializer抽象類是處理反序列化的類,只需在Bean類中的字段上加入注解@JsonDeserialize(using=xxx.class),并且xxx類要繼承JsonDeserializer類,且重新對(duì)應(yīng)的deserialize方法,在該方法中進(jìn)行相應(yīng)處理即可。在該方法中處理待反序列化字段可能出現(xiàn)的多種不同情況,詳情見源碼。
這里需要注意的是:當(dāng)反序列化字段是一個(gè)對(duì)象,而第三方返回的數(shù)據(jù)為一個(gè)數(shù)組時(shí),在重寫deserialize方法時(shí),如果判斷出當(dāng)前token指向的是一個(gè)數(shù)組,而此時(shí)需得到空對(duì)象。此時(shí),不能直接返回空對(duì)象,必須調(diào)用readValue方法,目的是將token移動(dòng)到正確的位置,否則,將創(chuàng)建一些奇怪的對(duì)象。
對(duì)于第二種解決方法,下面舉例說(shuō)明:
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;
}
}
總結(jié)
以上就是本文關(guān)于實(shí)例解析Json反序列化之ObjectMapper(自定義實(shí)現(xiàn)反序列化方法)的全部?jī)?nèi)容,希望對(duì)大家有所幫助。歡迎大家參閱本站其他專題,有什么問題可以留言,小編會(huì)及時(shí)回復(fù)大家的。
相關(guān)文章
SpringBoot+docker環(huán)境變量配置詳解
這篇文章主要介紹了SpringBoot+docker環(huán)境變量配置詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
jstack報(bào)錯(cuò)Unable to open socket file解決
這篇文章主要為大家介紹了jstack報(bào)錯(cuò)Unable to open socket file的解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-02-02
idea快捷鍵生成getter和setter,有構(gòu)造參數(shù),無(wú)構(gòu)造參數(shù),重寫toString方式
這篇文章主要介紹了java之idea快捷鍵生成getter和setter,有構(gòu)造參數(shù),無(wú)構(gòu)造參數(shù),重寫toString方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
怎樣將一個(gè)JAR包添加到Java應(yīng)用程序的Boot?Classpath中
本文文章給大家介紹如何將一個(gè)JAR包添加到Java應(yīng)用程序的Boot?Classpath中,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的的朋友參考下吧2023-11-11
Java實(shí)現(xiàn)簡(jiǎn)單文件過濾器功能
下面小編就為大家分享一篇Java實(shí)現(xiàn)簡(jiǎn)單文件過濾器功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2018-01-01
Java開發(fā)環(huán)境不再需要配置classpath問題
這篇文章主要介紹了Java開發(fā)環(huán)境不再需要配置classpath問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12

