json-diff簡單使用

一狠鸳、摘要

今天推薦的是一款java中险胰,對比兩個json-diff對象是否一致的工具包 json-diff` 。他可以對比任何結(jié)構(gòu)的兩個json數(shù)據(jù),并且將其中的不一致信息反饋給用戶。工具還內(nèi)置了很多配置可以來控制對比過程中的行為。目前已經(jīng)補充大量單測别伏,穩(wěn)定性還是比較好的。

二忧额、背景

公司最近在重構(gòu)一個核心系統(tǒng)厘肮,至于為什么重構(gòu)原因很多,就不說明了睦番。但是這個核心系統(tǒng)承載較多的線上業(yè)務(wù)类茂。為了不影響依賴依賴該服務(wù)的應(yīng)用,所以我們重構(gòu)的最核心就是完全兼容老系統(tǒng)接口托嚣。

為了保證平滑上線巩检,并且測試新系統(tǒng)與老系統(tǒng)是否一致,我們決定系統(tǒng)并行一段時間示启,并且在這段時間之中驗證新接口對老接口的兼容性兢哭。我們新起一個代理服務(wù),他會將我們的用戶流量分別轉(zhuǎn)發(fā)到新老接口夫嗓,然后拿到兩個結(jié)果迟螺,將老接口結(jié)果直接返回冲秽;異步去比較新老結(jié)果是否符合預(yù)期,進(jìn)行記錄或者報警煮仇。

這樣系統(tǒng)在經(jīng)過一段時間的測試劳跃,穩(wěn)定性更高,出錯的概率更小浙垫。

圖片

因為系統(tǒng)都是采用http接口對外提供服務(wù),且返回數(shù)據(jù)格式統(tǒng)一的是json格式郑诺。所以我們急需一款強大的Java語言的Json對比工具來幫助我們發(fā)現(xiàn)新老系統(tǒng)的不兼容之處夹姥。

三、工具介紹

1. 介紹

json-diff 是一款功能強大的json差異發(fā)現(xiàn)工具辙诞,支持任何結(jié)構(gòu)的json對比辙售,并且可以將對比結(jié)果返給用戶。目前該工具更新到了 3.0.0-RC1-RELEASE 版本飞涂。最新版可以查看 版本列表 旦部。建議使用最新版,舊版可能存在缺陷较店。

優(yōu)點:

  • 輕量級:工具只依賴 fastjson2
  • 精準(zhǔn)定位:可以返回最精準(zhǔn)且詳細(xì)的信息
  • 功能全面:幾乎覆蓋任何json結(jié)構(gòu)
  • 高性能

2. 使用教程

2.1 快速開始

  • 引入依賴
<dependency>
    <groupId>cn.xiaoandcai</groupId>
    <artifactId>json-diff</artifactId>
    <!-- 舊版本可能存在某些缺陷士八。版本請以maven倉庫最版為準(zhǔn)。 -->
    <version>${version}</version>
</dependency>

版本查看 2022-03-04 最新版本:3.0.0-RC1-RELEASE

  • 開始使用
/**
 * @author: codeleep
 * @createTime: 2022/11/22 16:57
 * @description: 使用示例
 */
public class UseExample {

    public static void main(String[] args) {
        String array1 = "[1, 2, 3, 4, 5]";
        String array2 = "[1, 3, 9, 4, 5]";

        JsonComparedOption jsonComparedOption = new JsonComparedOption().setIgnoreOrder(true);
        JsonCompareResult jsonCompareResult = new DefaultJsonDifference()
                .option(jsonComparedOption)
                .detectDiff(JSON.parseArray(array1), JSON.parseArray(array2));
        System.out.println(JSON.toJSONString(jsonCompareResult));
    }
}

結(jié)果展示:

{
    "defectsList": [
        {
            "actual": 9,
            "expect": 2,
            "illustrate": "The expect('2') data is inconsistent with the actual('9') data",
            "travelPath": {
                "abstractTravelPath": "root[]",
                "actualTravelPath": "root[2]",
                "expectTravelPath": "root[1]"
            }
        }
    ],
    "match": false
}

工具會返回 match 表示是否通過比對梁呈。defectsList 則是對比信息婚度。

2.2 更多配置

配置 類型 備注
ignoreOrder boolean 是否比較過程中忽略數(shù)組順序
mapping Map<String, String> 將真實字段映射到期望字段,key是真實字段name官卡,value是期望的字段name
ignorePath Set<String> 當(dāng)對比的路徑完全匹配時會被跳過蝗茁。遇到數(shù)組使用 [] 即可。無需填入下標(biāo)
ignoreKey Set<String> 對比object時寻咒∠蹋或忽略該key。對整個json生效
customComparator Map<String, Class<JsonNeat>> 用戶自定義比較器毛秘。具體說明見下文

2.0.1-RC1-RELEASE 之后版本中移除了 keyFunction 配置參數(shù)饭寺。可以使用 ignorePath 來代替達(dá)到同樣的效果熔脂。

工具提供了四個配置佩研,來之對比過程中一些其他的要求。工具還在積極開發(fā)中霞揉,如果有新的需求旬薯,可以給作者提一個issuse。

在開發(fā)中适秩。很多時候?qū)Ρ扰渲靡恢掳硇颉硕舆?梢允褂?JsonDiffOption 進(jìn)行開啟唯一配置

3. 進(jìn)階

3.1. 全局使用固定配置

由于在設(shè)計中考慮到各線程比較配置相互獨立。所以默認(rèn)將配置防止在 ThreadLocal 中進(jìn)行存儲骤公。但在大多數(shù)情況下抚官,我們在全局比較時,配置并不會發(fā)生變化阶捆。

工具提供了全局配置方式凌节。采用的方式是靜態(tài)類屬性。這樣也會獲得更好的性能洒试。

// 開啟并設(shè)置全局配置
JsonDiffOption.openUniqueOption();
JsonDiffOption.setGloballyUniqueOption(new JsonComparedOption());
// 不想使用時可以調(diào)用調(diào)整回線程獨有模式
 JsonDiffOption.closeUniqueOption();

3.2. 數(shù)組元素為對象關(guān)聯(lián)

當(dāng)我們在遇到數(shù)組元素是一個對象時倍奢。如下:

[
    {
        "date": "23日星期五",
        "sunrise": "06:16",
        "high": "高溫 18.0℃"
    },
    {
        "date": "24日星期六",
        "sunrise": "06:14",
        "high": "高溫 21.0℃"
    }
]

在比較時, 如果希望 date 字段一致,則認(rèn)為兩個對象一致垒棋。那么可以將 sunrise, high 字段都配置到 ignorePath 中卒煞。如:

HashSet<String> ignorePath = new HashSet<>();
ignorePath.add("root[].sunrise");
ignorePath.add("root[].high");

如果只是不想關(guān)注某個字段。即是 ignorePath 正常用法叼架。配置如上畔裕。

3.3. 字段映射

在比較兩個對象時。也許由于字段名變更乖订。導(dǎo)致校驗不通過扮饶。這時可以使用 mapping 配置。將 真實字段名稱映射至期望字段名稱垢粮。在比較過程中會將

actual.mappingKey 與 expect.mappingValue 認(rèn)為是應(yīng)該比較的對象贴届。具體配置如下

// mapping key 是 actual 鍵名
// mapping value 是 expect 鍵名
HashMap<String, String> mapping = new HashMap<>();
mapping.put("date", "sunrise");

3.4. 字段忽略

如果有一些字段是想在整個json都進(jìn)行忽略的,可以使用 ignoreKey 進(jìn)行全局忽略蜡吧。當(dāng)然如果不想全局忽略毫蚓,但是配置了該項,還是會被忽略掉昔善。

HashSet<String> ignoreKey = new HashSet<>();
ignoreKey.add("sunrise");
ignoreKey.add("high");

3.5 自定義比較器

在我們一個大json文件下元潘。可能遇到某些節(jié)點希望實現(xiàn)自定義比較君仆◆娓牛可以通過 customComparator 來進(jìn)行實現(xiàn)。

它配置的key是一個 travelPath 返咱。具體格式參照 ignorePath 钥庇。value 則是一個自定義比較器。對于自定義比較器需要繼承對應(yīng)的抽象類咖摹。并且實現(xiàn)具體的抽象接口评姨。具體如下:

對象比較:

需要繼承 me.codeleep.jsondiff.core.handle.array.AbstractArrayJsonNeat 并且重寫以下方法。

/**
* 比較對象
* @param expect 期望的json對象
* @param actual 實際的json對象
* @return 返回比較結(jié)果
* @throws IllegalAccessException 發(fā)生異常直接拋出
*/
JsonCompareResult detectDiff(JSONObject expect, JSONObject actual);

數(shù)組比較:

需要繼承 me.codeleep.jsondiff.core.handle.object.AbstractObjectJsonNeat 并且重寫以下方法萤晴。

  /**
 * 比較數(shù)組.調(diào)用入口吐句。需要自己去分別調(diào)用 ignoreOrder 和  keepOrder胁后。
 * @param expect 期望的json對象
 * @param actual 實際的json對象
 * @return 返回比較結(jié)果
 */
JsonCompareResult detectDiff(JSONArray expect, JSONArray actual);

// 忽略順序的比較
JsonCompareResult ignoreOrder(JSONArray expect, JSONArray actual);

// 保持順序比較
JsonCompareResult keepOrder(JSONArray expect, JSONArray actual);

基本類型比較:

基本類型指的是java基礎(chǔ)類型的包裝類型以及Number的實現(xiàn)類型。

需要繼承 me.codeleep.jsondiff.core.handle.primitive.AbstractPrimitiveJsonNeat 并且重寫以下方法嗦枢。

   /**
     * 比較數(shù)組
     * @param expect 基礎(chǔ)類型對象
     * @param actual 基礎(chǔ)類型對象
     * @return 返回比較結(jié)果
     */
    JsonCompareResult detectDiff(Object expect, Object actual);

用戶可以自己根據(jù) travelPath 來決定使用何種自定義比較攀芯。三種比較器都返回 JsonCompareResult 對象作為當(dāng)前節(jié)點的比較結(jié)果。對于JsonCompareResult對象文虏。需要填入以下信息:

// 示例
JsonCompareResult result = new JsonCompareResult();
Defects defects = new Defects()
                  .setActual(actualDiffJson)
                  .setExpect(expectDiffJson)
                  .setTravelPath(nextTravelPath)
                  .setIllustrateTemplate(DATA_TYPE_INCONSISTENT, expectDiffJson.getClass().getName(), actualDiffJson.getClass().getName());
result.addDefects(defects);

如果遇到在自定義節(jié)點中侣诺,還需要使用系統(tǒng)自帶的比較器時。

// 該值可以在上述三個抽象類中獲得择葡。但需要經(jīng)自行處理
String abstractTravelPath = "root";
// 下一級是對象
TravelPath nextTravelPath = new TravelPath(abstractTravelPath, mappingKey);
// 下一級是數(shù)組
TravelPath nextTravelPath = new TravelPath(abstractTravelPath, expectIndex, actualIndex);
// 獲得比較器
JsonDiffUtil.getJsonNeat(expectDiffJson, actualDiffJson, nextTravelPath);
// 執(zhí)行比較獲得結(jié)果
JsonCompareResult diff = jsonNeat.diff(expectDiffJson, actualDiffJson, nextTravelPath);
// 本級創(chuàng)建的 JsonCompareResult result 將下一級結(jié)果合并
this.result.mergeDefects(diff.getDefectsList());

可以使用上述代碼獲取系統(tǒng)自帶的比較器紧武。

自定義比較器值得注意的是: 從匹配到 travelPath 之后,根據(jù)不再接管比較操作敏储。一切行為由用戶自行定義。但工具依然預(yù)留默認(rèn)的比較器給用戶處理后續(xù)字段朋鞍。這需要用戶自行進(jìn)行組合調(diào)用已添。

4.其他說明

前面提到工具幾乎可以支持所有json結(jié)果的對比校驗,并且發(fā)現(xiàn)差異滥酥。那它到底可以支持哪些呢更舞,不知道是否符合你的需求呢?

  • 對象 ?

    這是最簡單的數(shù)據(jù)結(jié)構(gòu)了坎吻,其中元素都以key-value構(gòu)成缆蝉,key是字符串,value可以是任何數(shù)據(jù)結(jié)構(gòu)瘦真。

  • 數(shù)組 ?

    支持嚴(yán)格順序?qū)Ρ群秃雎皂樞驅(qū)Ρ瓤罚梢约?xì)化數(shù)組元素的類型

    • 基本類型 ?

    • 對象類型 ?

      該類型在對比時,可以通過ignorePath參數(shù)進(jìn)行元素是否進(jìn)行比較诸尽,將不關(guān)心的元素忽略掉原杂。當(dāng)然ignoreKey也可以,但其是全局生效

    • 數(shù)組類型 ?

      元素也是數(shù)組您机,這樣就形成了多維數(shù)組穿肄,工具理論上來說支持n維數(shù)組的對比

    • 元素類型不統(tǒng)一 ?

      數(shù)組中,類型可能包含前面三種類型际看,這時工具會按照類型分類進(jìn)行匹配咸产,最后找不到的元素再反饋給用戶。

由于json結(jié)構(gòu)在單個看來仲闽,就只有對象和數(shù)組兩種類型脑溢,該工具完美支持了所有類型。

四蔼囊、其他

1. 交流群

交流群: 710435809

2. 測試用例

目前工具測試覆蓋率已經(jīng)達(dá)到80%焚志。剩余測試用例正在補全中衣迷,完全可用于生產(chǎn)環(huán)境栋操。

本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布纠脾!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末靡挥,一起剝皮案震驚了整個濱河市简烤,隨后出現(xiàn)的幾起案子拢切,更是在濱河造成了極大的恐慌窒盐,老刑警劉巖添诉,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摧莽,死亡現(xiàn)場離奇詭異挑社,居然都是意外死亡陨界,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門痛阻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菌瘪,“玉大人,你說我怎么就攤上這事阱当∏卫” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵弊添,是天一觀的道長录淡。 經(jīng)常有香客問我,道長油坝,這世上最難降的妖魔是什么嫉戚? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮澈圈,結(jié)果婚禮上彬檀,老公的妹妹穿的比我還像新娘。我一直安慰自己极舔,他們只是感情好凤覆,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拆魏,像睡著了一般盯桦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渤刃,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天拥峦,我揣著相機與錄音,去河邊找鬼卖子。 笑死略号,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播玄柠,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼突梦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了羽利?” 一聲冷哼從身側(cè)響起宫患,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎这弧,沒想到半個月后娃闲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡匾浪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年皇帮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛋辈。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡属拾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冷溶,到底是詐尸還是另有隱情捌年,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布挂洛,位于F島的核電站,受9級特大地震影響眠砾,放射性物質(zhì)發(fā)生泄漏虏劲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一褒颈、第九天 我趴在偏房一處隱蔽的房頂上張望柒巫。 院中可真熱鬧,春花似錦谷丸、人聲如沸堡掏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泉唁。三九已至,卻和暖如春揩慕,著一層夾襖步出監(jiān)牢的瞬間亭畜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工迎卤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拴鸵,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像劲藐,于是被迫代替她去往敵國和親八堡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容