RapidJSON v1.1.0 發佈簡介

来源:http://www.cnblogs.com/miloyip/archive/2016/08/28/rapidjson_v1_1_0.html
-Advertisement-
Play Games

時隔 15.6 個月,終於發佈了一個新版本 v1.1.0。 新版本除了包含了這些日子收集到的無數的小改進及 bug fixes,也有一些新功能。本文嘗試從使用者的角度,簡單介紹一下這些功能和沿由。 ...


時隔 15.6 個月,終於發佈了一個新版本 v1.1.0

新版本除了包含了這些日子收集到的無數的小改進及 bug fixes,也有一些新功能。本文嘗試從使用者的角度,簡單介紹一下這些功能和沿由。

Photo by Ian Schneider

JSON Pointer

也許 RapidJSON 一直最為人垢病的地方,是它奇怪的 API 設計。例如,對 DOM 加添數據要給於 allocator 參數:

#include "rapidjson/document.h"

using namespace rapidjson;

// ...

Document d(kObjectType);

Value a(kArrayType);
for (int i = 1; i <= 4; i++)
    a.PushBack(i, d.GetAllocator());

d.AddMember("a", a, d.GetAllocator());

// { a : [1, 2, 3, 4] }

這是由於 RapidJSON 的 DOM 使用局部的分配器,以避免全局分配器的問題。而為了節省記憶體,每個 Value 不會存儲分配器的指針,所以必須從外部提供。

此設計也導致另一種問題。我們看看一個例子,使用 DOM API 訪問以下這個 JSON:

{
    "widget": {
        "window": {
            "title": "Sample Konfabulator Widget"
        }
    }
}

要訪問 title,最直覺想到的應該是這樣:

Document d;
d.Parse(json);
std::cout << d["widget"]["window"]["title"].GetString();

如果 widgetwindowtitle 不存在呢?以標準庫 std::map::operator[] 的做法來說,當找不到鍵,它會自動加入一個鍵值對,並返回該值(所以 map::operator[] 必須是 non-const 函數)。然而,RapidJSON 創建值的時候需要 allocator,所以不可能自動加入鍵值對。因此,RapidJSON 規定以 operator[] 訪問時,必須確保鍵存在(找不到時直接斷言失敗)。若不能確保,應先用 HasMember() 判斷,或更好的是使用 FindMember(),因為它可以告之鍵是否存在的同時,能通過迭代器取得該值。可是,使用 FindMember() 去訪問多層對象,代碼非常冗長:

Value::ConstMemberIterator itr1 = d.FindMember("widget");
if (itr1 != d.MemberEnd()) {
    const Value& widget = itr1->value;
    if (widget.IsObject()) {
        Value::ConstMemberIterator itr2 = widget.FindMember("window");
        if (itr2 != widget.MemberEnd()) {
            const Value& window = itr2->value;
            if (window.IsObject()) {
                Value::ConstMemberIterator itr3 = window.FindMember("title");
                if (itr3 != window.MemberEnd()) {
                    const Value& title = itr3->value;
                    if (title.IsString()) {
                        std::cout << title.GetString();
                    }
                }
            }
        }
    }
}

這坨代碼也許是最快最直接的方式。但一般業務代碼寫成這樣,可讀性太低,也容易出錯。

大家都可以寫一些輔助函數來解決這個問題。而我選擇了實現 RFC6901 ── JSON Pointer。先看看使用後的結果:

#include "rapidjson/pointer.h"

// ...

if (const Value* title = GetValueByPointer(d, "/widget/window/title")) {
    if (title->IsString()) {
        std::cout << title->GetString();
    }
}

這個版本簡單得多吧,"/widget/window/title" 是一個 JSON Pointer 的字元串形式,然後 GetValueByPointer()d 上解引用,如果失敗就返回空指針。

在邏輯上是和上面的冗長版本是一模一樣的,只是增加了一些解析 JSON Pointer 的運行時間及空間成本。對大多數人來說,應該更會接受這個版本。

有時候,業務邏輯還會是這樣的:「如果鍵不存在,就使用預設值。」RapidJSON 的 JSON Pointer 也提供此功能:

Value& title = GetValueByPointerWithDefault(
    d, "/widget/window/title", "untitled");

當解引用失敗時,它會創建整個路徑,並把預設值複製成新值,並返回該值。由於它總能返回一個值,此函數的返回類型為引用而不是指針。

在此也簡單介紹一下 JSON Pointer 的語法。它以 '/' 分隔多個 token,而每個 token 可以是 JSON object 的鍵,也可以是 JSON array 的下標。還有一種特殊 token 是負號 -,它可以指 JSON array 最後元素的下一個元素。使用這種特性能實現 PushBack() 的效果:

Document d;
CreateValueByPointer(d, "/a").SetArray();

for (int i = 1; i <= 4; i++)
    SetValueByPointer(d, "/a/-", i);

// { a : [1, 2, 3, 4] }

使用 JSON Pointer 的另一優點在於,它本身也是一個字元串,可以放置在 JSON 或其他文本格式之中。那麼,我們便有一個標準方式去引用 JSON 中的值。

希望 JSON Pointer 能減輕使用者的負擔,同時也提供一種數據驅動的彈性。新功能 JSON Pointer 簡單介紹至此,更多信息可參考 RapidJSON 使用手冊:Pointer

JSON Schema

上面我們也談到一個問題,JSON 里的組織方式、類型可能和預期的不同,我們可能要寫很多代碼去校驗一個 JSON 的格式是否乎合預期。特別是後臺伺服器可能接收到不正常的JSON,甚至是惡意編寫的 JSON 以圖攻擊。

在 XML 的世界中,可使用 XML DTD 或 XML Schema 去描述 XML 的結構。在 JSON 的世界中,已經有相關草案,稱為 JSON Schema

RapidJSON 實現了 JSON Schema v4 draft,並正式納入了 v1.1.0。先看看用法:

#include "rapidjson/schema.h"
// ...
Document sd;
if (!sd.Parse(schemaJson).HasParseError()) {
    // 此 schema 不是合法的 JSON
}
SchemaDocument schema(sd); // 把一個 Document 編譯至 SchemaDocument
// 之後不再需要 sd
Document d;
if (!d.Parse(inputJson).HasParseError()) {
    // 輸入不是一個合法的 JSON
}
SchemaValidator validator(schema);
if (!d.Accept(validator)) {
    // 輸入的 JSON 不合乎 schema
}

以我所知,現時所有 JSON Schema 實現都是校驗一個 DOM 是否合乎 Schema。RapidJSON 做了一個創新的嘗試,以事件流(SAX 風格)的方式去做校驗。上面的例子利用 Document::Accept() 產生事件流,然後送交 SchemaValidator 校驗。也許讀者會問:「這也是在校驗一個 DOM 是否合乎 Schema,有什麼特別嗎?」

這實際意味著,RapidJSON 的 JSON Schema 校驗器除了可以校驗 DOM,也可以校驗更底層的 SAX。例如,我們可以用 SAX 解析 JSON 時,同時進行 JSON Schema 校驗。如果中途不合乎 JSON Schema,就能直接中止解析。

SchemaValidator validator(schema);
Reader reader;
if (!reader.Parse(stream, validator)) {
    if (!validator.IsValid()) {
        // 輸入的 JSON 不合乎 schema
    }
}

也可以同時把事件轉發至一個自定義 handler:

MyHandler handler;
GenericSchemaValidator<SchemaDocument, MyHandler> validator(schema, handler);
Reader reader;
if (!reader.Parse(stream, validator)) {
    if (!validator.IsValid()) {
        // 輸入的 JSON 不合乎 schema
    }
}

由於 DOM 解析 JSON 時,底層也是使用 SAX,所以也可以同時做 Schema 校驗。其實除瞭解析,在生成時也可以進行校驗,以確保輸出的 JSON 也是乎合 Schema 的。這些用法都可參考 RapidJSON 使用手冊:Schema。要學習 JSON Schema 的寫法,筆者推薦 Understanding JSON Schema 這個英文網站。

C++11 範圍 for 迴圈

此版本還加入了 ArrayObject 輔助類型(包裹類),可分別通過 Value::GetArray()Value::GetObject() 獲取。這兩個輔助類型提供該 JSON 類型專門的介面,例如 Array::PushBack()Object::AddMember() 等。更重要的是,為了令 C++11 用戶使用得更順手,它們可做範圍 for 迴圈(range-based for loop):

// C++03
for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr)
    printf("%d ", itr->GetInt());

// C++11
for (auto& v : a.GetArray())
    printf("%d ", v.GetInt());

// C++03
for (Value::ConstMemberIterator itr = document.MemberBegin();
    itr != document.MemberEnd(); ++itr)
{
    printf("Type of member %s is %s\n",
        itr->name.GetString(), kTypeNames[itr->value.GetType()]);
}

// C++11
for (auto& m : document.GetObject())
    printf("Type of member %s is %s\n",
        m.name.GetString(), kTypeNames[m.value.GetType()]);

其他相關詳情可參閱 RapidJSON 使用手冊:教程

結語

這個 RapidJSON 版本對我而言是一個挑戰。

JSON Schema 實際上也需要 JSON Pointer,所以 JSON Pointer 可算是一舉兩得的新功能。但實現 JSON Schema 時有兩個難點。一個是 JSON Schema 需要正則引擎,在 C++11 下能直接使用 std::regex;而為了 C++03,我還實現了一個 500 行代碼的 Thompson NFA 正則引擎。另一個難點在於,事件流的校驗不容易實現 allOfanyOfoneOfnot 等關鍵字,需要多個校驗器同時檢驗事件流。

新功能 JSON Schema 和 JSON Pointer 都是附加功能,完全不影響 v1.0.x 的 API。

除新功能外,此版本含有一個重要的記憶體優化。在 x86-64 架構下,64 位指針只使用到 48 位,我重新設計了 Value 的排布,使每個值的記憶體開銷從 24 位元組縮減至 16 位元組。雖然存儲指針時會有時間開銷,但因大量縮減記憶體,更好的緩存一致性應該可以釐補損失,甚至能進一步提升整體性能。

屈指一算,RapidJSON 已快近 5 個年頭了,最近一年我轉部門後,更少機會在工作上使用 RapidJSON,所以我可能較少機會發現問題和新需求。雖然是這樣,我仍然會繼續維護這個項目,也要靠大家去發現問題和新需求,希望能得到大家的意見。

P.S. 可能大家會關心性能,我會儘快更新 nativejson-benchmark


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Mybatis是輕量級的持久化框架,的確上手非常快. Mybatis大體上的思路就是由一個總的config文件配置全局的信息,比如mysql連接信息等。然後再mapper中指定查詢的sql,以及參數和返回值。 在Service中直接調用這個mapper即可。 依賴的jar包 主要的mybatis配置 ...
  • ...
  • JPA規範推薦使用Annotation來管理實體類與數據表之間的映射關係,從而避免同時維護兩份文件(Java 實體類 和 XML 映射文件),將映射信息(寫在Annotation中)與實體類集中在一起。 以下我將使用eclipse來構建一個簡單使用註解取代*.hbm.xml的查詢小例子。(p.s 建 ...
  • 首先需要導入包 然後寫方法,由於我使用的框架全部是spring 此處我只寫入實現層和控制層代碼 其中有個selectMatYearPlan(MAT_NO_, MAT_DESC_, MAT_TYPE_, MAT_SPEC_CAT_CODE_, COM_CODE_, DEPT_CODE_, YEAR_, ...
  • 應對場景: 相對於開發在一臺電腦上運行的單個程式,如何讓一個應用中的多個獨立的程式協同工作是一件非常困難的事情。開發這樣的應用,很容易讓很多開發人員陷入如何使多個程式協同工作的邏輯中,最後導致沒有時間更好地思考和實現他們自己的應用程式邏輯;又或者開發人員對系統邏輯關註不夠,只是用很少的時間開發了一 ...
  • 文件操作 1.能調用方法的一定是對象,比如數值、字元串、列表、元組、字典,甚至文件也是對象,Python中一切皆為對象。 2.三種基本的文件操作模式:r(only-read)、w(only-write)、a(append) 對文件進行操作的流程:第一,建立文件對象。第二,調用文件方法進行操作。第三, ...
  • 使用Maven管理項目,pom文件為: 1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2 xsi:schemaLocation="h ...
  • 其他鏈接:http://blog.csdn.net/jinwufeiyang/article/details/52338268 如何將我們網站的其它內容(如菜單、標題等)做國際化處理呢?這就是本篇要將的內容—>國際化。 在項目的spring.xml文件添加的內容如下 [html] view plai ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...