80337960 2020-03-26
Java是面向对象的语言,所以我们更多的在项目中是以对象的形式处理业务的,但是在传输的时候我们却要将对象转换为 JSON 格式便于传输,而且 JSON 格式一般能解析为大多数的对象格式,而不在乎编程语言。
在工作中, 我们不可避免的要使用json字符串, json已经成为我们resultful接口最常使用的数据格式, 相信大家也都不陌生了, 工作中或多或少也是用过的.今天我就是要来说说json数据处理时, 我们使用到的一些坑, 避免自己或者大家以后也落入到这线陷阱中, 特总结出来, 分享给大家, 希望对大家有帮助.
好吧, 我们进入正题, 大家在处理json字符串的时候, 最常使用的包和方法时什么嗯?
阿里官方给的定义是, fastjson 是阿里巴巴的开源JSON解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。
你可以通过如下地方下载fastjson:
fastjson最新版本都会发布到maven中央仓库,你可以直接依赖。
dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>x.x.x</version> </dependency>
其中x.x.x是版本号,根据需要使用特定版本,建议使用最新版本。
常用JSON组件FastJson存在远程代码执行漏洞,***者可通过精心构建的json报文对目标服务器执行任意命令,从而获得服务器权限。此次爆发的漏洞为以往漏洞中autoType的绕过。
FastJson < 1.2.48
利用该0day漏洞,恶意者可以构造请求绕过FastJSON的黑名单策略。例如,***者通过精心构造的请求,远程让服务端执行指定命令(以下示例中成功运行计算器程序)。
Fastjson入口类是 com.alibaba.fastjson.JSON,主要的 API 是 JSON.toJSONString 和 parseObject。
package com.alibaba.fastjson; public abstract class JSON { // Java对象转换为JSON字符串 public static final String toJSONString(Object object); //JSON字符串转换为Java对象 public static final <T> T parseObject(String text, Class<T> clazz, Feature... features); }
序列化:
String jsonString = JSON.toJSONString(obj);
反序列化
VO vo = JSON.parseObject("...", VO.class);
fastjson是目前java语言中最快的json库,比自称最快的jackson速度还要快,第三方独立测试结果看这里:https://github.com/eishay/jvm-serializers/wiki。
自行做性能测试时,需关闭循环引用检测的功能。
另外,Fastjson 比 Gson 快大约6倍
所以在工作中, 自己的同事或者很多同学都在使用fastjson.
好的, 上面的这些操作足够你使用fastjson了, 但是如果我们遇到一个比较复杂一些的例子时, 我们应该怎么解析呢?
比如我给一个字符串, 大家来看看如何解析呢?
{ "properties": { "sonar.analysis.buildNumber": "1555" } }
如果你遇到这个例子, 应该怎么做呢? 因为sonar.analysis.buildNumber并不符合我们java属性的命名方式, 所以我们没法直接使用属性对他进行赋值, 那我们应该怎么办嗯?
在网上找了很多, 终于找到了答案, 就是我们可以通过@JsonProperty来进行注解的属性的赋值和设置
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.ArrayList; @Data @JsonIgnoreProperties(ignoreUnknown = true) @ApiModel(value = "sonarQube webhook的提交条件, 只适用目前的版本") public class ProviderSonarWebhookCondition { @Data @JsonIgnoreProperties(ignoreUnknown = true) public static class SonarProperties { @ApiModelProperty(value = "jenkins发起sonar扫描, 需要接受的build_id") @JsonProperty(value = "sonar.analysis.buildNumber") private String buildNumber; } @ApiModelProperty(value = "属性") private SonarProperties properties; }
我们先来看一下这几个注解的作用吧:
好吧, 上面的注解就顺利的帮我解决了这个问题, 把sonar.analysis.buildNumber给我自动转换成了buildNumber, 好吧, 很好的解决了我的问题.
当我进行编写单侧实例的时候, 突然发现了问题, 发现buildNumber就是怎么都转换不成功
其实我们发现: @JsonIgnoreProperties 是使用的jackson的注解, 但是我们在使用字符串转换的时候, 却使用的Fastjson, 所以不太能兼容, 特别是在单元测试中,
jackson的maven依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.3</version> </dependency>
@JsonProperty 此注解用于属性上,作用是把该属性的名称序列化为另外一个名称,如把trueName属性序列化为name,@JsonProperty(value="name")。
import com.fasterxml.jackson.annotation.JsonProperty; public class Student { @JsonProperty(value = "real_name") private String realName; public String getRealName() { return realName; } public void setRealName(String realName) { this.realName = realName; } @Override public String toString() { return "Student{" + "realName=‘" + realName + ‘\‘‘ + ‘}‘; } }
测试
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class Main { public static void main(String[] args) throws JsonProcessingException { Student student = new Student(); student.setRealName("zhangsan"); System.out.println(new ObjectMapper().writeValueAsString(student)); } }
结果:
{"real_name":"zhangsan"}
*这里需要注意的是将对象转换成json字符串使用的方法是fasterxml.jackson提供的!!*
如果使用fastjson呢?
import com.alibaba.fastjson.JSON; public class Main { public static void main(String[] args) { Student student = new Student(); student.setRealName("zhangsan"); System.out.println(JSON.toJSONString(student)); } }
结果:
{"realName":"zhangsan"}
可以看到,@JsonProperty(value = "real_name")没有生效,为啥?
*因为fastjson不认识@JsonProperty注解呀!所以要使用jackson自己的序列化工具方法!*
@JsonProperty不仅仅是在序列化的时候有用,反序列化的时候也有用,比如有些接口返回的是json字符串,命名又不是标准的驼峰形式,在映射成对象的时候,将类的属性上加上@JsonProperty注解,里面写上返回的json串对应的名字
import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { String jsonStr = "{\"real_name\":\"zhangsan\"}"; Student student = new ObjectMapper().readValue(jsonStr.getBytes(), Student.class); System.out.println(student); } }
结果:
Student{realName=‘zhangsan‘}
既然fastjson号称最牛逼的json解析工具, 那不可能不考虑到这个特殊字符串的问题啊, 如果fastjson是自己开发的, 自己也应该考虑到这些情况啊, 大佬开的不会比自己开大的更差吧. 所以可能是自己还没有更好的掌握好fastjson的使用方法, 在升入学习一下fastjson, 果然还是支持这种操作的, 快来优惠我们的代码吧
fastjson支持多种方式的定制序列化
package com.alibaba.fastjson.annotation; public @interface JSONField { // 配置序列化和反序列化的顺序,1.1.42版本之后才支持 int ordinal() default 0; // 指定字段的名称 String name() default ""; // 指定字段的格式,对日期格式有用 String format() default ""; // 是否序列化 boolean serialize() default true; // 是否反序列化 boolean deserialize() default true; }
可把@JSONField配置在字段或者getter/setter方法上, 例如:
配置在字段上
public class VO { @JSONField(name="ID") private int id; @JSONField(name="birthday",format="yyyy-MM-dd") public Date date; }
配置在Getter/Setter上**
public class VO { private int id; @JSONField(name="ID") public int getId() { return id;} @JSONField(name="ID") public void setId(int id) {this.id = id;} }
可以定制化配置各个日期字段的格式化
public class A { // 配置date序列化和反序列使用yyyyMMdd日期格式 @JSONField(format="yyyyMMdd") public Date date; }
public class A { @JSONField(serialize=false) public Date date; } public class A { @JSONField(deserialize=false) public Date date; }
缺省Fastjson序列化一个java bean,是根据fieldName的字母序进行序列化的,你可以通过ordinal指定字段的顺序。这个特性需要1.1.42以上版本。
public static class VO { @JSONField(ordinal = 3) private int f0; @JSONField(ordinal = 2) private int f1; @JSONField(ordinal = 1) private int f2; }
在fa
stjson 1.2.16版本之后,JSONField支持新的定制化配置serializeUsing,可以单独对某一个类的某个属性定制序列化,比如:
public static class Model { @JSONField(serializeUsing = ModelValueSerializer.class) public int value; } public static class ModelValueSerializer implements ObjectSerializer { @Override public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { Integer value = (Integer) object; String text = value + "元"; serializer.write(text); } }
测试代码
Model model = new Model(); model.value = 100; String json = JSON.toJSONString(model); Assert.assertEquals("{\"value\":\"100元\"}", json);
和JSONField类似,但JSONType配置在类上,而不是field或者getter/setter方法上。
SerializeFilter是通过编程扩展的方式定制序列化. fastjson支持6种SerializeFilter, 用于不用场景的定制序列化
public interface PropertyFilter extends SerializeFilter { boolean apply(Object object, String propertyName, Object propertyValue); }
可以通过扩展实现根据object或者属性名称或者属性值进行判断是否需要序列化。例如:
PropertyFilter filter = new PropertyFilter() { public boolean apply(Object source, String name, Object value) { if ("id".equals(name)) { int id = ((Integer) value).intValue(); return id >= 100; } return false; } }; JSON.toJSONString(obj, filter); // 序列化的时候传入filter
和PropertyFilter不同只根据object和name进行判断,在调用getter之前,这样避免了getter调用可能存在的异常。
public interface PropertyPreFilter extends SerializeFilter { boolean apply(JSONSerializer serializer, Object object, String name); }
如果需要修改Key,process返回值则可
public interface NameFilter extends SerializeFilter { String process(Object object, String propertyName, Object propertyValue); }
fastjson内置一个PascalNameFilter,用于输出将首字符大写的Pascal风格。 例如:
import com.alibaba.fastjson.serializer.PascalNameFilter; Object obj = ...; String jsonStr = JSON.toJSONString(obj, new PascalNameFilter());
public interface ValueFilter extends SerializeFilter { Object process(Object object, String propertyName, Object propertyValue); }
在序列化对象的所有属性之前执行某些操作,例如调用 writeKeyValue 添加内容
public abstract class BeforeFilter implements SerializeFilter { protected final void writeKeyValue(String key, Object value) { ... } // 需要实现的抽象方法,在实现中调用writeKeyValue添加内容 public abstract void writeBefore(Object object); }
在序列化对象的所有属性之后执行某些操作,例如调用 writeKeyValue 添加内容
public abstract class AfterFilter implements SerializeFilter { protected final void writeKeyValue(String key, Object value) { ... } // 需要实现的抽象方法,在实现中调用writeKeyValue添加内容 public abstract void writeAfter(Object object); }
ParseProcess是编程扩展定制反序列化的接口。fastjson支持如下ParseProcess:
public static class VO { private int id; private Map<String, Object> attributes = new HashMap<String, Object>(); public int getId() { return id; } public void setId(int id) { this.id = id;} public Map<String, Object> getAttributes() { return attributes;} } ExtraProcessor processor = new ExtraProcessor() { public void processExtra(Object object, String key, Object value) { VO vo = (VO) object; vo.getAttributes().put(key, value); } }; VO vo = JSON.parseObject("{\"id\":123,\"name\":\"abc\"}", VO.class, processor); Assert.assertEquals(123, vo.getId()); Assert.assertEquals("abc", vo.getAttributes().get("name"));
public static class VO { private int id; private Map<String, Object> attributes = new HashMap<String, Object>(); public int getId() { return id; } public void setId(int id) { this.id = id;} public Map<String, Object> getAttributes() { return attributes;} } class MyExtraProcessor implements ExtraProcessor, ExtraTypeProvider { public void processExtra(Object object, String key, Object value) { VO vo = (VO) object; vo.getAttributes().put(key, value); } public Type getExtraType(Object object, String key) { if ("value".equals(key)) { return int.class; } return null; } }; ExtraProcessor processor = new MyExtraProcessor(); VO vo = JSON.parseObject("{\"id\":123,\"value\":\"123456\"}", VO.class, processor); Assert.assertEquals(123, vo.getId()); Assert.assertEquals(123456, vo.getAttributes().get("value")); // value本应该是字符串类型的,通过getExtraType的处理变成Integer类型了。
我相信上面这些例子方法足够你在工作中运用了吧!
作者:故事凌
简介:生活中的段子手,目前就职于一家地产公司做 DevOPS 相关工作, 曾在大型互联网公司做高级运维工程师,熟悉 Linux 运维,Python 运维开发,Java 开发,DevOPS 常用开发组件等,个人公众号:stromling,欢迎来撩我哦!