json使用過程踩過的一些坑

JSON,全稱:JavaScript Object Notation,作為一個常見的輕量級的數據交換格式,應該在一個程序員的開發生涯中是常接觸的。簡潔和清晰的層次結構使得 JSON 成為理想的數據交換語言。易於人閱讀和編寫,同時也易於機器解析和生成,並有效地提升網絡傳輸效率。

Java是面向對象的語言,所以我們更多的在項目中是以對象的形式處理業務的,但是在傳輸的時候我們卻要將對象轉換為 JSON 格式便於傳輸,而且 JSON 格式一般能解析為大多數的對象格式,而不在乎編程語言。

在工作中, 我們不可避免的要使用json字符串, json已經成為我們resultful接口最常使用的數據格式, 相信大家也都不陌生了, 工作中或多或少也是用過的.今天我就是要來說說json數據處理時, 我們使用到的一些坑, 避免自己或者大家以後也落入到這個陷阱中, 特總結出來, 分享給大家, 希望對大家有幫助.

好吧, 我們進入正題, 大家在處理json字符串的時候, 最常使用的包和方法是什麼嗯?

1. fastjson

1. 什麼是fastjson

阿里官方給的定義是, fastjson 是阿里巴巴的開源JSON解析庫,它可以解析 JSON 格式的字符串,支持將 Java Bean 序列化為 JSON 字符串,也可以從 JSON 字符串反序列化到 JavaBean。

2. fastjson的優點

  • 速度快fastjson相對其他JSON庫的特點是快,從2011年fastjson發佈1.1.x版本之後,其性能從未被其他Java實現的JSON庫超越。
  • 使用廣泛fastjson在阿里巴巴大規模使用,在數萬臺服務器上部署,fastjson在業界被廣泛接受。在2012年被開源中國評選為最受歡迎的國產開源軟件之一。
  • 測試完備fastjson有非常多的testcase,在1.2.11版本中,testcase超過3321個。每次發佈都會進行迴歸測試,保證質量穩定。
  • 使用簡單fastjson的 API 十分簡潔。
  • 功能完備支持泛型,支持流處理超大文本,支持枚舉,支持序列化和反序列化擴展。

3. 獲取fastjson

你可以通過如下地方下載fastjson:

  • maven中央倉庫: http://central.maven.org/maven2/com/alibaba/fastjson/
  • Sourceforge.net : https://sourceforge.net/projects/fastjson/files/
  • 在maven項目的pom文件中直接配置fastjson依賴

fastjson最新版本都會發布到maven中央倉庫,你可以直接依賴。

<code>dependency>
    <groupid>com.alibaba/<groupid>
    <artifactid>fastjson/<artifactid>
    <version>x.x.x/<version>
/<code>

其中x.x.x是版本號,根據需要使用特定版本,建議使用最新版本。

fastjson漏洞

漏洞描述

常用JSON組件FastJson存在遠程代碼執行漏洞,攻擊者可通過精心構建的json報文對目標服務器執行任意命令,從而獲得服務器權限。此次爆發的漏洞為以往漏洞中autoType的繞過。

影響範圍

FastJson < 1.2.48

漏洞描述

利用該0day漏洞,惡意攻擊者可以構造攻擊請求繞過FastJSON的黑名單策略。例如,攻擊者通過精心構造的請求,遠程讓服務端執行指定命令(以下示例中成功運行計算器程序)。

json使用過程踩過的一些坑

4. Fastjson主要的API

Fastjson入口類是 com.alibaba.fastjson.JSON,主要的 API 是 JSON.toJSONString 和 parseObject。

<code>package com.alibaba.fastjson;
public abstract class JSON {
     // Java對象轉換為JSON字符串
     public static final String toJSONString(Object object);
     //JSON字符串轉換為Java對象
     public static final  T parseObject(String text, Class clazz, Feature... features);
}
/<code>

序列化:

<code>String jsonString = JSON.toJSONString(obj);/<code>

反序列化

<code>VO vo = JSON.parseObject("...", VO.class);/<code>

5. Fastjson的性能

fastjson是目前java語言中最快的json庫,比自稱最快的jackson速度還要快,第三方獨立測試結果看這裡:https://github.com/eishay/jvm-serializers/wiki。

自行做性能測試時,需關閉循環引用檢測的功能。

另外,Fastjson 比 Gson 快大約6倍

所以在工作中, 自己的同事或者很多同學都在使用fastjson.


2. @JsonProperty

好的, 上面的這些操作足夠你使用fastjson了, 但是如果我們遇到一個比較複雜一些的例子時, 我們應該怎麼解析呢?

比如我給一個字符串, 大家來看看如何解析呢?

<code>{
 "properties": {
   "sonar.analysis.buildNumber": "1555"
}
}/<code>

如果你遇到這個例子, 應該怎麼做呢? 因為sonar.analysis.buildNumber並不符合我們java屬性的命名方式, 所以我們沒法直接使用屬性對他進行賦值, 那我們應該怎麼辦嗯?在網上找了很多, 終於找到了答案, 就是我們可以通過@JsonProperty來進行註解的屬性的賦值和設置

<code>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;
 
}/<code>

我們先來看一下這幾個註解的作用吧:

  • @JsonIgnore註解用來忽略某些字段,可以用在變量或者Getter方法上,用在Setter方法時,和變量效果一樣。這個註解一般用在我們要忽略的字段上。
  • @JsonIgnoreProperties(ignoreUnknown = true),將這個註解寫在類上之後,就會忽略類中不存在的字段。這個註解還可以指定要忽略的字段,例如@JsonIgnoreProperties({ “password”, “secretKey” }), password和secretKey就是我們的java成員變量

好吧, 上面的註解就順利的幫我解決了這個問題, 把sonar.analysis.buildNumber給我自動轉換成了buildNumber, 好吧, 很好的解決了我的問題.

3. 問題來了

當我進行編寫單側實例的時候, 突然發現了問題, 發現buildNumber就是怎麼都轉換不成功

其實我們發現: @JsonIgnoreProperties 是使用的jackson的註解, 但是我們在使用字符串轉換的時候, 卻使用的Fastjson, 所以不太能兼容, 特別是在單元測試中,

jackson的maven依賴

<code><dependency>
  <groupid>com.fasterxml.jackson.core/<groupid>
  <artifactid>jackson-databind/<artifactid>
  <version>2.5.3/<version>
/<dependency>/<code>

@JsonProperty 此註解用於屬性上,作用是把該屬性的名稱序列化為另外一個名稱,如把trueName屬性序列化為name,@JsonProperty(value="name")。

<code>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 + '\\'' +
               '}';
  }
}/<code>

測試

<code>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));
  }
}/<code>

結果:

<code>{"real_name":"zhangsan"}/<code>

*這裡需要注意的是將對象轉換成json字符串使用的方法是fasterxml.jackson提供的!!*

如果使用fastjson呢?

<code>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));
  }
}/<code>

結果:

<code>{"realName":"zhangsan"}/<code>

可以看到,@JsonProperty(value = "real_name")沒有生效,為啥?

*因為fastjson不認識@JsonProperty註解呀!所以要使用jackson自己的序列化工具方法!*

@JsonProperty不僅僅是在序列化的時候有用,反序列化的時候也有用,比如有些接口返回的是json字符串,命名又不是標準的駝峰形式,在映射成對象的時候,將類的屬性上加上@JsonProperty註解,裡面寫上返回的json串對應的名字

<code>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);
  }
}/<code>

結果:

<code>Student{realName='zhangsan'}/<code>

4. 思考在學習

既然fastjson號稱最牛逼的json解析工具, 那不可能不考慮到這個特殊字符串的問題啊, 如果fastjson是自己開發的, 自己也應該考慮到這些情況啊, 大佬開的不會比自己開大的更差吧. 所以可能是自己還沒有更好的掌握好fastjson的使用方法, 在升入學習一下fastjson, 果然還是支持這種操作的, 快來優惠我們的代碼吧

6. fastjson定製序列化

1. 簡介

fastjson支持多種方式的定製序列化

  • 通過@JSONField定製序列化
  • 通過@JSONType定製序列化
  • 通過@SerializeFilter定製序列化
  • 通過ParseProcess定製序列化

2. 使用@JSONField配置

1. JSONField註解介紹

<code>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;
}/<code>

2. JSONField配置方式

可把@JSONField配置在字段或者getter/setter方法上, 例如:

配置在字段上

<code>public class VO {
    @JSONField(name="ID")
    private int id;

    @JSONField(name="birthday",format="yyyy-MM-dd")
    public Date date;
}/<code>

配置在Getter/Setter上**

<code>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;}
}/<code>

3. 使用format配置日期格式化

可以定製化配置各個日期字段的格式化

<code>public class A {
     // 配置date序列化和反序列使用yyyyMMdd日期格式
     @JSONField(format="yyyyMMdd")
     public Date date;
}/<code>

4. 使用serialize/deserialize指定字段不序列化

<code>public class A {
     @JSONField(serialize=false)
     public Date date;
}

public class A {
     @JSONField(deserialize=false)
     public Date date;
}/<code>

5. 使用ordinal指定字段的順序

缺省Fastjson序列化一個java bean,是根據fieldName的字母序進行序列化的,你可以通過ordinal指定字段的順序。這個特性需要1.1.42以上版本。

<code>public static class VO {
   @JSONField(ordinal = 3)
   private int f0;


   @JSONField(ordinal = 2)
   private int f1;

   @JSONField(ordinal = 1)
   private int f2;
}/<code>

6. 使用serializeUsing指定屬性的序列化類

在fa

stjson 1.2.16版本之後,JSONField支持新的定製化配置serializeUsing,可以單獨對某一個類的某個屬性定製序列化,比如:

<code>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);
  }
}/<code>

測試代碼

<code>Model model = new Model();
model.value = 100;
String json = JSON.toJSONString(model);
Assert.assertEquals("{\"value\":\"100元\"}", json);/<code>

3. 使用@JSONType配置

和JSONField類似,但JSONType配置在類上,而不是field或者getter/setter方法上。

4. 通過SerializeFilter定製序列化

1. 簡介

SerializeFilter是通過編程擴展的方式定製序列化. fastjson支持6種SerializeFilter, 用於不用場景的定製序列化

  • PropertyPreFilter根據PropertyName判斷是否序列化
  • PropertyFilter根據PropertyName和PropertyValue來判斷是否序列化
  • NameFilter 修改Key,如果需要修改Key,process返回值則可
  • ValueFilter 修改Value
  • BeforeFilter 序列化時在最前添加內容
  • AfterFilter 序列化時在最後添加內容

2. PropertyFilter 根據PropertyName和PropertyValue來判斷是否序列化

<code>public interface PropertyFilter extends SerializeFilter {
   boolean apply(Object object, String propertyName, Object propertyValue);
}/<code>

可以通過擴展實現根據object或者屬性名稱或者屬性值進行判斷是否需要序列化。例如:

<code>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/<code>

3. PropertyPreFilter 根據PropertyName判斷是否序列化

和PropertyFilter不同只根據object和name進行判斷,在調用getter之前,這樣避免了getter調用可能存在的異常。

<code>public interface PropertyPreFilter extends SerializeFilter {
     boolean apply(JSONSerializer serializer, Object object, String name);
}/<code>

4. NameFilter 序列化時修改Key

如果需要修改Key,process返回值則可

<code>public interface NameFilter extends SerializeFilter {
   String process(Object object, String propertyName, Object propertyValue);
}/<code>

fastjson內置一個PascalNameFilter,用於輸出將首字符大寫的Pascal風格。例如:

<code>import com.alibaba.fastjson.serializer.PascalNameFilter;

Object obj = ...;
String jsonStr = JSON.toJSONString(obj, new PascalNameFilter());/<code>

5. ValueFilter 序列化時修改Value

<code>public interface ValueFilter extends SerializeFilter {
 Object process(Object object, String propertyName, Object propertyValue);
}/<code>

6. BeforeFilter 序列化時在最前添加內容

在序列化對象的所有屬性之前執行某些操作,例如調用 writeKeyValue 添加內容

<code>public abstract class BeforeFilter implements SerializeFilter {
  protected final void writeKeyValue(String key, Object value) { ... }

   // 需要實現的抽象方法,在實現中調用writeKeyValue添加內容
   public abstract void writeBefore(Object object);
}/<code>

7. AfterFilter 序列化時在最後添加內容

在序列化對象的所有屬性之後執行某些操作,例如調用 writeKeyValue 添加內容

<code>public abstract class AfterFilter implements SerializeFilter {
 protected final void writeKeyValue(String key, Object value) { ... }
   // 需要實現的抽象方法,在實現中調用writeKeyValue添加內容
   public abstract void writeAfter(Object object);
}/<code>

5. 通過ParseProcess定製反序列化

1. 簡介

  • ExtraProcessor 用於處理多餘的字段


  • ExtraTypeProvider 用於處理多餘字段時提供類型信息


2. 使用ExtraProcessor 處理多餘字段

<code>public static class VO {
   private int id;

   private Map<string> attributes = new HashMap<string>();
   public int getId() { return id; }
   public void setId(int id) { this.id = id;}
   public Map<string> 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"));/<string>/<string>/<string>/<code>


3. 使用ExtraTypeProvider 為多餘的字段提供類型

<code>public static class VO {
   private int id;
   private Map<string> attributes = new HashMap<string>();
   public int getId() { return id; }
   public void setId(int id) { this.id = id;}
   public Map<string> 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類型了。/<string>/<string>/<string>/<code>

我相信上面這些例子方法足夠你在工作中運用了吧!


分享到:


相關文章: