关于json传输的过程中字段不对应的问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/singgel/article/details/81507105

https://github.com/singgel?tab=repositories 

前情摘要

先来一点故事补充,话说小明的领导给小明安排了一个任务,很简单就是调用别人的API,我们作为Client接收数据并进行相应的处理,领导说由于各种原因,目前不知道对方接口的返回数据格式,所以你先做数据解析吧,先写XML格式的,于是小明开始着手工作了,经过编码,调试,并且领导也review通过了。但是,领导接到消息说数据格式好像是JSON格式的,小明只好重新开始工作了。解析XML格式的代码请点击访问以下链接:

解析XML格式代码

代码编写之JSONObject解决一切

小明一听解析JSON格式,那不是手到擒来么,以前经常这么干,如以下代码所示

JSONObject jsonObject = new JSONObject(assetJson);

JSONArray jsonArray = jsonObject.getJSONArray("value");

for(int i=0;i<jsonArray.length();i++) {

            JSONObject jsonObj = jsonArray.getJSONObject(i);

    if(jsonObj.has("AssetNumber") && !jsonObj.isNull("AssetNumber")) {

asset.setAssetNumber(jsonObj.getLong("AssetNumber"));

}

这段代码应该都能读懂,就是传进来一个Json字符串,然后用一个实体类接收,为了防止异常保证代码健壮性,很多人都会像小明一样加上jsonObject.has(key)和jsonObject.isNull(key)。这时候小明正在为自己的小聪明得意,但是殊不知,这个json对象中有60多个key,意思就是得为这个小聪明多写120行重复的代码。然后再仔细一看,对方的API返回的数据,字段都是超级多,很显然,这么多重复代码无论你是放在Service层处理还是直接裸在Controller里都不是很友好。然后你是不是以为我要提高代码的可重用性,哈哈,这样的代码如果按这个思路来的话是没有办法简化的。那怎么办,换一个思路咯。(有点小啰嗦,请见谅)

代码编写之Json反序列化

小明想了想,现在整体项目使用的是spring boot,spring boot里集成了restTemplate,客户端根据自己的需要可以重写这里关于restTemplate的就不在赘述了。看到一篇关于HttpMessageConverter的文章,希望在你重写restTemplate的时候能帮助你完成关于消息类型转换的工作。如下:

https://segmentfault.com/a/1190000012659486

长话短说,封装好自己的restTemplate。小明想着使用实体类直接去接收服务端传过来的Json数据,代码如下:

public ResponseEntity<JsonResultForMaterial> getMaterials() {

return this.restTemplate.exchange( getNlyteServiceEndpoint()+ GetMaterialsURL, HttpMethod.GET,

getDefaultEntity(), JsonResultForMaterial.class);

}

json数据例子如下:

{"@odata.context": "/$metadata#Materials",

"value": [{

"@odata.type": "#Nlyte.Model.StandardNetworkMaterial",

"MaterialID": 1

},

{

"@odata.type": "#Nlyte.Model.StandardServerMaterial",

"MaterialID": 96

},

{

"@odata.type": "#Nlyte.Model.BladeServerMaterial",

"MaterialID": 101

}],

"@odata.nextLink": "Materials?$skip=200"

}

由于服务端传过来的json数据格式问题,小明写了如下实体类

import java.util.List;


import com.fasterxml.jackson.annotation.JsonProperty;


public class JsonResultForMaterial {


@JsonProperty(value="@odata.context")

private String odatacontext;


private List<Material> value;


@JsonProperty(value="@odata.nextLink")

private String odatanextLink;


public String getOdatacontext() {

return odatacontext;

}


public void setOdatacontext(String odatacontext) {

this.odatacontext = odatacontext;

}


public String getOdatanextLink() {

return odatanextLink;

}


public void setOdatanextLink(String odatanextLink) {

this.odatanextLink = odatanextLink;

}


public List<Material> getValue() {

return value;

}


public void setValue(List<Material> value) {

this.value = value;

}


}


package com.vmware.wormhole.nlyteworker.model;

import com.fasterxml.jackson.annotation.JsonProperty;


public class Material {


@JsonProperty(value = "@odata.type")

private String odataType;

@JsonProperty(value = "MaterialID")

private int materialID;


public String getOdataType() {

return odataType;

}

public void setOdataType(String odataType) {

this.odataType = odataType;

}

public int getMaterialID() {

return materialID;

}

public void setMaterialID(int materialID) {

this.materialID = materialID;

}

}
在Json反序列化过程中,key需要和你定义的实体类的属性对应,但是大家都知道,实体类属性的命名规范,关于首字母和特殊字符的问题是很严格的。以上json数据中出现了,这样的key如:
@odata.context 、MaterialID

这样是无法直接用实体类接收的。这时候需要用到@JsonProperty(),来修饰具体属性,这样就可以解决无法映射的问题。

扫描二维码关注公众号,回复: 3379051 查看本文章

这样我们就获得了一个json结果对象,里面有我们需要操作的属性。这里不用考虑,开篇的那个问题,比如key是否存在,key的值是否为空。然后代码可以改成如下:

public List<Asset> getAssetsFromNlyte(List<NlyteAsset> nlyteAssets) {

List<Asset> assetsFromNlyte = new ArrayList<Asset>();

Asset asset;

for(NlyteAsset nlyteAsset:nlyteAssets) {

asset = new Asset();

asset.setAssetNumber(nlyteAsset.getAssetNumber());

asset.setTag(nlyteAsset.getTag());

assetsFromNlyte.add(asset);

}

return assetsFromNlyte;

}

这里其实是和开篇的代码做的事情是一样的,不同的是,开篇的代码接收到的是一个json字符串,需要使用JSONObject和JSONArray等进行处理,还需要判断key等等,最后再进行数据的封装。这里的代码直接接收到一个list,然后直接进行遍历,封装数据。这里就解决了,要写很多has(key)和isNull(key)判断的问题。

@JsonProperty,@NotNull,@JsonIgnore的具体实例使用,和其中发现的一些问题。

场景分析一

小明做了一个web表单,用来填写并保存数据,后台写restful接口接收数据并保存。写完之后让老大review的时候,自信满满的小明,又收到了很多comment。小明看到了这些comment发现确实有不足之处,比如表单里的有些数据是必须不为空的,虽然在页面上加上了强校验(Js校验),但是后台接口是对外开放的restful接口,别人不走页面直接访问接口存储数据,这时候页面的校验就显得很尴尬了,小明又想这好办啊,直接拿接收到的参数进行非空判断不就行了么,其实也是可以的,但是小明在研究@JsonProperty的时候发现@NotNull正好解决这个问题。代码展示如下:

Student类

public class Student {


@JsonProperty(value="real_name")

private String name ;


@NotNull(message="idcard is not null")

private String idCard;



public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getIdCard() {

return idCard;

}

public void setIdCard(String idCard) {

this.idCard = idCard;

}


}

StudentController.java(注:这里为了测试方便未按照标准的restApi书写,如需学习标准的restful接口风格请移步百度。请见谅)

public class Student {


@JsonProperty(value="real_name")

private String name ;


@NotNull(message="idcard is not null")

private String idCard;



public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getIdCard() {

return idCard;

}

public void setIdCard(String idCard) {

this.idCard = idCard;

}



}

Postman测试如下:(条件是:正常输入real_name和idCard,返回结果正常)

Postman测试结果如下(条件:只写real_name,不填写idCard。报错)

注意接口书写时,用@RequestBody接收输入参数时,这时候也需要匹配你预先定义的@JsonProperty的值。参考real_name.并且,在参数前需要加上@Valid,你定义的@NotNull校验才会生效。

@JsonProperty(value="real_name")
private String name ;

场景分析二

idCard为用户的敏感信息,在接口返回数据中不能展示出来,以免用户敏感信息直接暴露在外。这时候,小明想到了另外一个注解,@JsonIgnore,在Student对象序列化为json数据的返回的时候,忽略该属性。代码及测试如下:

@JsonProperty(value="real_name")

private String name ;


@JsonIgnore

private String idCard;

Postman测试如下(条件:正常输入real_name和idCard,观察返回数据,只包含real_name)

场景分析三

在场景二中提到使用@JsonIgnore可以让接口在返回数据的时候不序列化一些属性。但是小明又想了,若场景一和场景二结合使用,及在用户输入表单保存数据的时候,某个字段不能为空,并且返回数据的时候又不能包含该属性,是不是可以使用组合注解@JsonIngore和@NotNull呢,代码和测试结果如下:

@JsonProperty(value="real_name")

private String name ;


@JsonIgnore

@NotNull(message="idcard is not null")

private String idCard;

Postman测试结果如下(条件:idCard及为上述特殊字段,结果报错)

经过尝试,小明想到了如下解决方案,代码及测试结果如下:

@JsonProperty(value="real_name")

private String name ;


@JsonProperty(access=Access.WRITE_ONLY)

@NotNull(message="idcard is not null")

private String idCard;

Postman测试结果如下(条件:用JsonProperty代替JsonIgnore)

成功实现需求。

场景分析四

由于小明公司秉承尽最大努力少使用第三方的资源的原因,项目中关于JsonObject相关的jar都是使用的org.json,并未使用阿里的fastjson,如果项目使用的是fastjson,再使用上述的注解就不起作用了,它有自己的一套注解来解决上述问题,如:@JSONField,具体可参考com.alibaba.fastjson.annotation包。感兴趣的可以查一下。如有问题欢迎交流和分享。

猜你喜欢

转载自blog.csdn.net/singgel/article/details/81507105