cJSON,c語言的JSON庫!

Welcome to cJSON.

cJSON,c語言的JSON庫!

cJSON的目標是成為您能夠完成工作的“最愚蠢(最便捷)”的解析器。它是一個C文件和一個頭文件。


JSON它類似於XML,但不含冗餘。您可以使用它來移動數據、存儲數據,或者只是表示程序的狀態。


作為一個庫,cJSON的存在可以帶走儘可能多的跑腿工作(重複造輪子),但不會妨礙您的工作。作為實用主義的觀點(即忽略事實),我想說你可以在兩種模式中使用它:自動模式手動模式。讓我們快速瀏覽一下。


Building

有幾種方法可以將cJSON合併到您的項目中。

複製源文件

因為整個庫只有一個C文件和一個頭文件,所以您可以將cJSON.h和cJSON.c複製到您的項目源代碼並開始使用它。


cJSON是用ANSI C (C89)編寫的,以支持儘可能多的平臺和編譯器。

CMake

使用CMake, cJSON支持完整的構建系統。通過這種方式,您可以獲得最多的功能。支持與2.8.5相同或更高版本的CMake。使用CMake時,建議執行out of tree構建,即將編譯後的文件放在與源文件分開的目錄中。因此,為了在Unix平臺上使用CMake構建cJSON,需要創建一個構建目錄並在其中運行CMake。

<code>mkdir build
cd build
cmake ../<code>

這將創建一個Makefile和一堆其他文件。然後你可以編譯它:

<code>make/<code>

如果你想安裝的話,可以使用make install。默認情況下,它將標頭/usr/local/include/cjson和庫安裝到/usr/local/lib。它還為pkg-config安裝文件,以便更容易地檢測和使用CMake的現有安裝。它安裝CMake配置文件,其他基於CMake的項目可以使用這些配置文件來發現庫。


您可以使用可以傳遞給CMake的不同選項列表來更改構建過程,打開和關閉:

  • -DENABLE_CJSON_TEST=On:啟用構建測試。(默認情況下)
  • -DENABLE_CJSON_UTILS=On:啟用構建cJSON_Utils。(默認情況下)
  • -DENABLE_TARGET_EXPORT=On:啟用CMake目標的導出。如果有問題就關掉。(默認情況下)
  • -DENABLE_CUSTOM_COMPILER_FLAGS=On:啟用自定義編譯器標記(目前適用於Clang、GCC和MSVC)。如果有問題就關掉。(默認情況下)
  • -DENABLE_VALGRIND=On:使用valgrind運行測試。(默認情況下)
  • -DENABLE_SANITIZERS=On:在啟用了AddressSanitizer和UndefinedBehaviorSanitizer功能(如果可能的話)的情況下編譯cJSON。(默認情況下)
  • -DENABLE_SAFE_STACK:啟用SafeStack檢測傳遞。目前只適用於Clang編譯器。(默認情況下)
  • -DBUILD_SHARED_LIBS=On:構建共享庫。(默認情況下)
  • -DBUILD_SHARED_AND_STATIC_LIBS=On:構建共享庫和靜態庫。(默認情況下)
  • -DCMAKE_INSTALL_PREFIX=/usr:設置安裝的前綴。
  • -DENABLE_LOCALES=On:啟用localeconv方法。(默認開啟)
  • -DCJSON_OVERRIDE_BUILD_SHARED_LIBS=On:啟用- dcjson_build_build_shared_libs覆蓋BUILD_SHARED_LIBS的值。


如果您正在為一個Linux發行版打包cJSON,您可能會採取以下步驟:

<code>mkdir build
cd build
cmake .. -DENABLE_CJSON_UTILS=On -DENABLE_CJSON_TEST=Off -DCMAKE_INSTALL_PREFIX=/usr
make
make DESTDIR=$pkgdir install/<code>


在Windows上,CMake通常用於創建Visual Studio解決方案文件,方法是在Visual Studio的開發人員命令提示符中運行它,具體步驟遵循CMake和Microsoft的官方文檔,並使用您選擇的在線搜索引擎。上述選項的描述仍然普遍適用,儘管並非所有選項都適用於Windows。

Makefile

注意:不推薦使用此方法。儘可能使用CMake。Makefile支持僅限於修復bug。

如果你沒有可用的CMake,但仍然有GNU make。您可以使用makefile來構建cJSON: 在帶有源代碼的目錄中運行這個命令,它將自動編譯靜態和共享庫以及一個小測試程序(不是完整的測試套件)。

<code>make all/<code>


如果需要,可以使用make install將編譯後的庫安裝到系統中。默認情況下,它將在/usr/local/include/cjson中安裝標頭,在/usr/local/lib中安裝庫。但是您可以通過設置

PREFIXDESTDIR變量來更改此行為:make PREFIX=/usr DESTDIR=temp install。然後使用:make PREFIX=/usr DESTDIR=temp uninstall來卸載它們。

Vcpkg

你可以使用vcpkg依賴管理器下載和安裝cJSON:

<code>git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
vcpkg install cjson/<code>

vcpkg中的cJSON端口由Microsoft團隊成員和社區貢獻者保持最新。如果版本過期,請在vcpkg存儲庫中創建問題或拉出請求。

Including cJSON

如果你通過CMake或Makefile安裝它,你可以像這樣包含cJSON:

<code>#include /<code>

Data Structure

cJSON表示使用cJSON結構數據類型的JSON數據:

<code>/* cJSON結構: */
typedef struct cJSON
{
    struct cJSON *next;
    struct cJSON *prev;
    struct cJSON *child;
    int type;
    char *valuestring;
    /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
    int valueint;
    double valuedouble;
    char *string;
} cJSON;/<code>

這種類型的項表示JSON值。類型以位標誌的形式存儲在type中(這意味著僅通過比較type的值無法找到類型)。

要檢查項的類型,請使用相應的cJSON_Is…函數。它執行一個NULL檢查,然後執行一個類型檢查,如果項目是這種類型,則返回一個布爾值。

可以是以下類型之一:

  • cJSON_Invalid(使用cJSON_IsInvalid進行檢查):表示不包含任何值的無效項。如果將項設置為所有零字節,則會自動擁有此類型。
  • cJSON_False(用cJSON_IsFalse檢查):表示一個假布爾值。您還可以使用cJSON_IsBool檢查布爾值。
  • cJSON_True(用cJSON_IsTrue檢查):表示一個真正的布爾值。您還可以使用cJSON_IsBool檢查布爾值。
  • cJSON_NULL(使用cJSON_IsNull檢查):表示一個空值。
  • cJSON_Number(用cJSON_IsNumber檢查):表示一個數值。該值在valuedouble和valueint中存儲為double。如果該數字超出了整數的範圍,則INT_MAX或INT_MIN用於valueint。
  • cJSON_String(用cJSON_IsString檢查):表示一個字符串值。它以零終止字符串的形式存儲在valuestring中。
  • cJSON_Array(使用cJSON_IsArray檢查):表示一個數組值。這是通過將child指向一個表示數組中值的cJSON項的鏈表來實現的。這些元素使用next和prev鏈接在一起,其中第一個元素有prev。next == NULL,最後一個元素next == NULL。
  • cJSON_Object(用cJSON_IsObject檢查):表示一個對象值。對象的存儲方式與數組相同,唯一的區別是對象中的項將鍵存儲為字符串。
  • cJSON_Raw(使用cjson_w進行檢查):表示以零結尾的字符數組形式存儲在valuestring中的任何JSON類型。例如,這可以用來避免反覆打印相同的靜態JSON以節省性能。cJSON在解析時永遠不會創建這種類型。還要注意,cJSON不會檢查它是否是有效的JSON。

此外,還有以下兩個標誌:

  • cJSON_IsReference:指定子元素指向的項和/或valuestring不屬於這個元素,它只是一個引用。所以cJSON_Delete和其他函數將只釋放這個項目,而不是它的子/valuestring。
  • cJSON_StringIsConst:這意味著字符串指向一個常量字符串。這意味著cJSON_Delete和其他函數不會嘗試釋放字符串。


Working with the data structure

對於每個值類型都有一個cJSON_Create…函數,可用於創建該類型的項。所有這些都將分配一個cJSON結構,稍後可以使用cJSON_Delete刪除它。請注意,您必須在某個時候刪除它們,否則將導致內存洩漏。

重要提示:如果您已經向數組或對象添加了項,則不能使用cJSON_Delete刪除它。將其添加到數組或對象中會轉移其所有權,以便在刪除該數組或對象時也將其刪除。您也可以使用cJSON_SetValuestring來更改cJSON_String的valuestring,而不必手動釋放先前的valuestring。

基本類型

  • null 是用cJSON_CreateNull創建的
  • booleans 是用cJSON_CreateTrue創建的,cJSON_CreateFalse或cJSON_CreateBool
  • numbers 是用cJSON_CreateNumber創建的。這將設置valuedouble和valueint。如果數字超出了整數的範圍,則使用INT_MAX或INT_MIN來創建valueint
  • strings ,使用cJSON_CreateString(複製該字符串)或cJSON_CreateStringReference(直接指向該字符串)創建該字符串。這意味著valuestring不會被cJSON_Delete刪除,您要對它的生存期負責,這對常量很有用)

數組

您可以使用cJSON_CreateArray創建一個空數組。
cJSON_CreateArrayReference可以用來創建一個不“擁有”其內容的數組,所以它的內容不會被cJSON_Delete刪除。


若要將項添加到數組中,請使用cJSON_AddItemToArray將項追加到末尾。使用


cJSON_AddItemReferenceToArray可以將一個元素添加為另一個項、數組或字符串的引用。這意味著cJSON_Delete將不會刪除那些項的子屬性或valuestring屬性,因此,如果它們已經在其他地方使用了,就不會發生重複釋放。要在中間插入項,可以使用cJSON_InsertItemInArray。它將在給定的基於0的索引處插入一個項,並將所有現有項向右移動。


如果您想從一個給定索引的數組中取出一個項目並繼續使用它,那麼使用cJSON_DetachItemFromArray,它將返回分離的項目,所以一定要將它分配給一個指針,否則您將有內存洩漏。

刪除項目是使用cJSON_DeleteItemFromArray完成的。它的工作原理類似於cJSON_DetachItemFromArray,但是通過cJSON_Delete刪除分離的項目。


您還可以在適當的位置替換數組中的項。使用索引的cJSON_ReplaceItemInArray或使用給定元素指針的
cJSON_ReplaceItemViaPointer。如果
cJSON_ReplaceItemViaPointer失敗,它將返回0。這在內部做的是分離舊項、刪除它並在其位置插入新項。


要獲得數組的大小,請使用cJSON_GetArraySize。使用cJSON_GetArrayItem獲取給定索引處的元素。

因為數組存儲為一個鏈表,通過迭代索引效率低下(O (n²)),所以你可以使用cJSON_ArrayForEach宏遍歷一個數組在O (n)時間複雜度。


對象

您可以使用cJSON_CreateObject創建一個空對象。
cJSON_CreateObjectReference可以用來創建一個不“擁有”其內容的對象,因此它的內容不會被cJSON_Delete刪除。


要向對象添加項,請使用cJSON_AddItemToObject。使用cJSON_AddItemToObjectCS向名稱為常量或引用(該項的鍵,cJSON結構中的字符串)的對象添加項,這樣cJSON_Delete就不會釋放它。使用
cJSON_AddItemReferenceToArray可以將一個元素添加為另一個對象、數組或字符串的引用。這意味著cJSON_Delete將不會刪除那些項的子屬性或valuestring屬性,因此,如果它們已經在其他地方使用了,就不會發生重複釋放。


如果你想從一個對象中取出一個項目,使用
cJSON_DetachItemFromObjectCaseSensitive,它將返回分離的項目,所以一定要把它分配到一個指針,否則你會有一個內存洩漏。


刪除項目是用
cJSON_DeleteItemFromObjectCaseSensitive完成的。它的工作原理類似於
cJSON_DetachItemFromObjectCaseSensitive,後面跟著cJSON_Delete。


您還可以在適當的位置替換對象中的項。或者使用鍵使用
cJSON_ReplaceItemInObjectCaseSensitive,或者使用
cJSON_ReplaceItemViaPointer給出一個指向元素的指針。如果
cJSON_ReplaceItemViaPointer失敗,它將返回0。這在內部做的是分離舊項、刪除它並在其位置插入新項。


要獲得對象的大小,可以使用cJSON_GetArraySize,這是因為在內部對象是作為數組存儲的。

如果你想訪問對象中的一個項目,使用
cJSON_GetObjectItemCaseSensitive。

要在對象上進行迭代,可以使用cJSON_ArrayForEach宏,方法與數組相同。

cJSON還提供了方便的幫助函數,用於快速創建新項並將其添加到對象中,如cJSON_AddNullToObject。它們返回指向新項的指針,如果失敗則返回NULL。

解析JSON

給定以零結尾的字符串中的一些JSON,您可以使用cJSON_Parse解析它。

<code>cJSON *json = cJSON_Parse(string);/<code>

給定一個字符串中的一些JSON(無論是否終止為0),您可以使用cJSON_ParseWithLength解析它。

<code>cJSON *json = cJSON_ParseWithLength(string, buffer_length);/<code>

它將解析JSON並分配一個表示它的cJSON項樹。一旦它返回,您將完全負責在與cJSON_Delete一起使用後對它進行釋放。

cJSON_Parse使用的分配器默認是malloc和free,但是可以使用cJSON_InitHooks(全局)更改。


如果發生錯誤,可以使用cJSON_GetErrorPtr訪問指向輸入字符串中錯誤位置的指針。注意,這可能會在多線程場景中產生競爭條件,在這種情況下,最好使用cJSON_ParseWithOpts和return_parse_end。默認情況下,解析後的JSON之後的輸入字符串中的字符不會被視為錯誤。


如果你想要更多的選項,使用cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_ended)。return_parse_end返回一個指針,指向輸入字符串中的JSON結尾或錯誤發生的位置(從而以線程安全的方式替換cJSON_GetErrorPtr)。require_null_ended,如果設置為1,那麼如果輸入字符串包含JSON之後的數據,則會導致錯誤。

如果你想要更多的設置緩衝區長度的選項,可以使用cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_)

輸出JSON

給定一個cJSON項樹,您可以使用cJSON_Print將它們打印為字符串。

<code>char *string = cJSON_Print(json);/<code>

它將分配一個字符串並將樹的JSON表示形式打印到其中。一旦它返回,您就完全有責任在與分配器一起使用後重新分配它。(通常是免費的,取決於cJSON_InitHooks設置了什麼)。

cJSON_Print將使用空白來打印格式。如果您想打印沒有格式,使用cjson_printunformatting。


如果您對結果字符串的大小有一個大致的概念,那麼您可以使用cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)。fmt是一個布爾值,用於打開和關閉空白格式。prebuffer指定用於打印的第一個緩衝區大小。cJSON_Print的第一個緩衝區大小是256字節。一旦打印耗盡空間,就會分配一個新的緩衝區,並在繼續打印之前複製舊的緩衝區。


通過使用cjson_printpre(cJSON *item, char *buffer, const int length, const cJSON_bool格式)可以完全避免這些動態緩衝區分配。它接受一個緩衝區的指針打印到它的長度。如果達到該長度,打印將失敗並返回0。如果成功,則返回1。注意,您應該提供比實際需要更多的5個字節,因為cJSON在估計所提供的內存是否足夠時不是100%準確的。

舉例

在這個例子中,我們想要構建和解析以下JSON:

<code>{
    "name": "Awesome 4K",
    "resolutions": [
        {
            "width": 1280,
            "height": 720
        },
        {
            "width": 1920,
            "height": 1080
        },
        {
            "width": 3840,
            "height": 2160
        }
    ]
}/<code>

輸出

讓我們構建上面的JSON並將其打印為一個字符串:

<code>//創建一個具有受支持的列表的監視器
//注意:返回一個堆分配的字符串,您需要在使用後釋放它。
char *create_monitor(void)
{
    const unsigned int resolution_numbers[3][2] = {
        {1280, 720},
        {1920, 1080},
        {3840, 2160}
    };
    char *string = NULL;
    cJSON *name = NULL;
    cJSON *resolutions = NULL;
    cJSON *resolution = NULL;
    cJSON *width = NULL;
    cJSON *height = NULL;
    size_t index = 0;

    cJSON *monitor = cJSON_CreateObject();
    if (monitor == NULL)
    {
        goto end;
    }

    name = cJSON_CreateString("Awesome 4K");
    if (name == NULL)
    {
        goto end;
    }
    /* 創建成功後,立即將其添加到監視器中
     * 從而轉移到它的指針的所有權 */
    cJSON_AddItemToObject(monitor, "name", name);

    resolutions = cJSON_CreateArray();
    if (resolutions == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(monitor, "resolutions", resolutions);

    for (index = 0; index < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++index)
    {
        resolution = cJSON_CreateObject();
        if (resolution == NULL)
        {
            goto end;
        }
        cJSON_AddItemToArray(resolutions, resolution);

        width = cJSON_CreateNumber(resolution_numbers[index][0]);
        if (width == NULL)
        {
            goto end;
        }
        cJSON_AddItemToObject(resolution, "width", width);

        height = cJSON_CreateNumber(resolution_numbers[index][1]);
        if (height == NULL)
        {
            goto end;
        }
        cJSON_AddItemToObject(resolution, "height", height);
    }

    string = cJSON_Print(monitor);
    if (string == NULL)
    {
        fprintf(stderr, "Failed to print monitor.\n");
    }

end:
    cJSON_Delete(monitor);
    return string;
}/<code>

或者,我們可以使用cJSON_Add…ToObject輔助功能,讓我們的生活更輕鬆:

<code>
//注意:返回一個堆分配的字符串,您需要在使用後釋放它。
char *create_monitor_with_helpers(void)
{
    const unsigned int resolution_numbers[3][2] = {
        {1280, 720},
        {1920, 1080},
        {3840, 2160}
    };
    char *string = NULL;
    cJSON *resolutions = NULL;
    size_t index = 0;

    cJSON *monitor = cJSON_CreateObject();

    if (cJSON_AddStringToObject(monitor, "name", "Awesome 4K") == NULL)
    {
        goto end;
    }

    resolutions = cJSON_AddArrayToObject(monitor, "resolutions");
    if (resolutions == NULL)
    {
        goto end;
    }

    for (index = 0; index < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++index)
    {
        cJSON *resolution = cJSON_CreateObject();

        if (cJSON_AddNumberToObject(resolution, "width", resolution_numbers[index][0]) == NULL)
        {
            goto end;
        }

        if (cJSON_AddNumberToObject(resolution, "height", resolution_numbers[index][1]) == NULL)
        {
            goto end;
        }

        cJSON_AddItemToArray(resolutions, resolution);
    }

    string = cJSON_Print(monitor);
    if (string == NULL)
    {
        fprintf(stderr, "Failed to print monitor.\n");
    }

end:
    cJSON_Delete(monitor);
    return string;
}/<code>

解析

在這個例子中,我們將解析上述格式的JSON,並檢查監視器是否支持全高清分辨率,同時打印一些診斷輸出:

<code>/* 如果監視器支持全高清,返回1,否則返回0 */
int supports_full_hd(const char * const monitor)
{
    const cJSON *resolution = NULL;
    const cJSON *resolutions = NULL;
    const cJSON *name = NULL;
    int status = 0;
    cJSON *monitor_json = cJSON_Parse(monitor);
    if (monitor_json == NULL)
    {
        const char *error_ptr = cJSON_GetErrorPtr();
        if (error_ptr != NULL)
        {
            fprintf(stderr, "Error before: %s\n", error_ptr);
        }
        status = 0;
        goto end;
    }

    name = cJSON_GetObjectItemCaseSensitive(monitor_json, "name");
    if (cJSON_IsString(name) && (name->valuestring != NULL))
    {
        printf("Checking monitor "%s"\n", name->valuestring);
    }

    resolutions = cJSON_GetObjectItemCaseSensitive(monitor_json, "resolutions");
    cJSON_ArrayForEach(resolution, resolutions)
    {
        cJSON *width = cJSON_GetObjectItemCaseSensitive(resolution, "width");
        cJSON *height = cJSON_GetObjectItemCaseSensitive(resolution, "height");

        if (!cJSON_IsNumber(width) || !cJSON_IsNumber(height))
        {
            status = 0;
            goto end;
        }

        if ((width->valuedouble == 1920) && (height->valuedouble == 1080))
        {
            status = 1;
            goto end;
        }
    }

end:
    cJSON_Delete(monitor_json);
    return status;
}/<code>

注意,除了cJSON_Parse的結果之外,沒有任何空檢查,因為


cJSON_GetObjectItemCaseSensitive已經檢查了空輸入,所以只傳播空值,如果輸入為空,則cJSON_IsNumber和cJSON_IsString返回0。


警告

Zero Character零字符

cJSON不支持包含0字符'\0'或\u0000的字符串。這在當前API中是不可能的,因為字符串是零終止的。

Character Encoding字符編碼

cJSON只支持UTF-8編碼的輸入。但在大多數情況下,它不會拒絕無效的UTF-8作為輸入,只是按原樣傳播它。只要輸入不包含無效的UTF-8,輸出就始終是有效的UTF-8。

C StandardC標準

cJSON是用ANSI C(或C89, C90)編寫的。如果編譯器或C庫不遵循這個標準,就不能保證正確的行為。 注意:ANSI C不是c++,所以它不應該用c++編譯器來編譯。不過,您可以使用C編譯器編譯它,並將它與您的c++代碼鏈接起來。雖然使用c++編譯器進行編譯可能有效,但不能保證正確的行為。

Floating Point Numbers浮點數

除了IEEE754雙精度浮點數外,cJSON不支持任何雙精度實現。它可能仍然可以與其他實現一起工作,但是其中的bug將被認為是無效的。 cJSON支持的浮點文字的最大長度目前是63個字符。

Deep Nesting Of Arrays And Objects數組和對象的深度嵌套

cJSON不支持深度嵌套的數組和對象,因為這會導致堆棧溢出。為了防止這種情況,cJSON將深度限制為CJSON_NESTING_LIMIT,默認值為1000,但是可以在編譯時更改。

Thread Safety線程安全性

一般來說,cJSON不是線程安全的。 但在以下情況下是線程安全的:


  • cJSON_GetErrorPtr從未被使用過(可以使用cjson_parse_end參數cJSON_ParseWithOpts)
  • cJSON_InitHooks只在任何線程中使用cJSON之前被調用。
  • 在所有對cJSON函數的調用返回之前,從未調用setlocale。

Case Sensitivity大小寫敏感性

在最初創建cJSON時,它沒有遵循JSON標準,也沒有區分大寫和小寫字母。如果您想要正確的、標準的兼容的行為,您需要在可用的地方使用案例敏感函數。

Duplicate Object Members複製對象成員

cJSON支持解析和打印包含具有多個同名成員的對象的JSON。然而,


cJSON_GetObjectItemCaseSensitive總是隻返回第一個。

Enjoy cJSON!

cJSON,c語言的JSON庫!


分享到:


相關文章: