/* Copyright (c) 2009-2017 Dave Gamble and cJSON contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #pragma GCC visibility push(default) #include #include #include #include #include #pragma GCC visibility pop #include "cJSON_Utils.h" static unsigned char* cJSONUtils_strdup(const unsigned char* const string) { size_t length = 0; unsigned char *copy = NULL; length = strlen((const char*)string) + sizeof(""); copy = (unsigned char*) cJSON_malloc(length); if (copy == NULL) { return NULL; } memcpy(copy, string, length); return copy; } /* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ static int cJSONUtils_strcasecmp(const unsigned char *string1, const unsigned char *string2) { if ((string1 == NULL) || (string2 == NULL)) { return 1; } if (string1 == string2) { return 0; } for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) { if (*string1 == '\0') { return 0; } } return tolower(*string1) - tolower(*string2); } /* Compare the next path element of two JSON pointers, two NULL pointers are considered unequal: */ static int cJSONUtils_Pstrcasecmp(const unsigned char *name, const unsigned char *pointer) { if ((name == NULL) || (pointer == NULL)) { return 1; } for (; (*name != '\0') && (*pointer != '\0') && (*pointer != '/'); (void)name++, pointer++) /* compare until next '/' */ { if (*pointer == '~') { /* check for escaped '~' (~0) and '/' (~1) */ if (((pointer[1] != '0') || (*name != '~')) && ((pointer[1] != '1') || (*name != '/'))) { /* invalid escape sequence or wrong character in *name */ return 1; } else { pointer++; } } else if (tolower(*name) != tolower(*pointer)) { return 1; } } if (((*pointer != 0) && (*pointer != '/')) != (*name != 0)) { /* one string has ended, the other not */ return 1; } return 0; } /* calculate the length of a string if encoded as JSON pointer with ~0 and ~1 escape sequences */ static size_t cJSONUtils_PointerEncodedstrlen(const unsigned char *string) { size_t length; for (length = 0; *string != '\0'; (void)string++, length++) { /* character needs to be escaped? */ if ((*string == '~') || (*string == '/')) { length++; } } return length; } /* copy a string while escaping '~' and '/' with ~0 and ~1 JSON pointer escape codes */ static void cJSONUtils_PointerEncodedstrcpy(unsigned char *destination, const unsigned char *source) { for (; source[0] != '\0'; (void)source++, destination++) { if (source[0] == '/') { destination[1] = '1'; destination++; } else if (source[0] == '~') { destination[0] = '~'; destination[1] = '1'; destination++; } else { destination[0] = source[0]; } } destination[0] = '\0'; } CJSON_PUBLIC(char *) cJSONUtils_FindPointerFromObjectTo(const cJSON * const object, const cJSON * const target) { size_t child_index = 0; cJSON *current_child = 0; if (object == target) { /* found */ return (char*)cJSONUtils_strdup((const unsigned char*)""); } /* recursively search all children of the object or array */ for (current_child = object->child; current_child != NULL; (void)(current_child = current_child->next), child_index++) { unsigned char *target_pointer = (unsigned char*)cJSONUtils_FindPointerFromObjectTo(current_child, target); /* found the target? */ if (target_pointer != NULL) { if (cJSON_IsArray(object)) { /* reserve enough memory for a 64 bit integer + '/' and '\0' */ unsigned char *full_pointer = (unsigned char*)cJSON_malloc(strlen((char*)target_pointer) + 20 + sizeof("/")); /* check if conversion to unsigned long is valid * This should be eliminated at compile time by dead code elimination * if size_t is an alias of unsigned long, or if it is bigger */ if (child_index > ULONG_MAX) { cJSON_free(target_pointer); return NULL; } sprintf((char*)full_pointer, "/%lu%s", (unsigned long)child_index, target_pointer); /* / */ cJSON_free(target_pointer); return (char*)full_pointer; } if (cJSON_IsObject(object)) { unsigned char *full_pointer = (unsigned char*)cJSON_malloc(strlen((char*)target_pointer) + cJSONUtils_PointerEncodedstrlen((unsigned char*)current_child->string) + 2); full_pointer[0] = '/'; cJSONUtils_PointerEncodedstrcpy(full_pointer + 1, (unsigned char*)current_child->string); strcat((char*)full_pointer, (char*)target_pointer); cJSON_free(target_pointer); return (char*)full_pointer; } /* reached leaf of the tree, found nothing */ cJSON_free(target_pointer); return NULL; } } /* not found */ return NULL; } /* non broken version of cJSON_GetArrayItem */ static cJSON *get_array_item(const cJSON *array, size_t item) { cJSON *child = array ? array->child : NULL; while ((child != NULL) && (item > 0)) { item--; child = child->next; } return child; } static cJSON_bool decode_array_index_from_pointer(const unsigned char * const pointer, size_t * const index) { size_t parsed_index = 0; size_t position = 0; if ((pointer[0] == '0') && ((pointer[1] != '\0') && (pointer[1] != '/'))) { /* leading zeroes are not permitted */ return 0; } for (position = 0; (pointer[position] >= '0') && (pointer[0] <= '9'); position++) { parsed_index = (10 * parsed_index) + (size_t)(pointer[position] - '0'); } if ((pointer[position] != '\0') && (pointer[position] != '/')) { return 0; } *index = parsed_index; return 1; } CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON * const object, const char *pointer) { cJSON *current_element = object; /* follow path of the pointer */ while ((pointer[0] == '/') && (current_element != NULL)) { pointer++; if (cJSON_IsArray(current_element)) { size_t index = 0; if (!decode_array_index_from_pointer((const unsigned char*)pointer, &index)) { return NULL; } current_element = get_array_item(current_element, index); } else if (cJSON_IsObject(current_element)) { current_element = current_element->child; /* GetObjectItem. */ while ((current_element != NULL) && cJSONUtils_Pstrcasecmp((unsigned char*)current_element->string, (const unsigned char*)pointer)) { current_element = current_element->next; } /* skip to the next path token or end of string */ while ((pointer[0] != '\0') && (pointer[0] != '/')) { pointer++; } } else { return NULL; } } return current_element; } /* JSON Patch implementation. */ static void cJSONUtils_InplaceDecodePointerString(unsigned char *string) { unsigned char *decoded_string = string; if (string == NULL) { return; } for (; *string; (void)decoded_string++, string++) { if (string[0] == '~') { if (string[1] == '0') { decoded_string[0] = '~'; } else if (string[1] == '1') { decoded_string[1] = '/'; } else { /* invalid escape sequence */ return; } string++; } } decoded_string[0] = '\0'; } /* non-broken cJSON_DetachItemFromArray */ static cJSON *detach_item_from_array(cJSON *array, size_t which) { cJSON *c = array->child; while (c && (which > 0)) { c = c->next; which--; } if (!c) { /* item doesn't exist */ return NULL; } if (c->prev) { /* not the first element */ c->prev->next = c->next; } if (c->next) { c->next->prev = c->prev; } if (c==array->child) { array->child = c->next; } /* make sure the detached item doesn't point anywhere anymore */ c->prev = c->next = NULL; return c; } static cJSON *cJSONUtils_PatchDetach(cJSON *object, const unsigned char *path) { unsigned char *parent_pointer = NULL; unsigned char *child_pointer = NULL; cJSON *parent = NULL; cJSON *detached_item = NULL; /* copy path and split it in parent and child */ parent_pointer = cJSONUtils_strdup(path); if (parent_pointer == NULL) { goto cleanup; } child_pointer = (unsigned char*)strrchr((char*)parent_pointer, '/'); /* last '/' */ if (child_pointer == NULL) { goto cleanup; } /* split strings */ child_pointer[0] = '\0'; child_pointer++; parent = cJSONUtils_GetPointer(object, (char*)parent_pointer); cJSONUtils_InplaceDecodePointerString(child_pointer); if (cJSON_IsArray(parent)) { size_t index = 0; if (!decode_array_index_from_pointer(child_pointer, &index)) { goto cleanup; } detached_item = detach_item_from_array(parent, index); } else if (cJSON_IsObject(parent)) { detached_item = cJSON_DetachItemFromObject(parent, (char*)child_pointer); } else { /* Couldn't find object to remove child from. */ goto cleanup; } cleanup: if (parent_pointer != NULL) { cJSON_free(parent_pointer); } return detached_item; } static int cJSONUtils_Compare(cJSON *a, cJSON *b) { if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) { /* mismatched type. */ return -1; } switch (a->type & 0xFF) { case cJSON_Number: /* numeric mismatch. */ if ((a->valueint != b->valueint) || (a->valuedouble != b->valuedouble)) { return -2; } else { return 0; } case cJSON_String: /* string mismatch. */ if (strcmp(a->valuestring, b->valuestring) != 0) { return -3; } else { return 0; } case cJSON_Array: for ((void)(a = a->child), b = b->child; (a != NULL) && (b != NULL); (void)(a = a->next), b = b->next) { int status = cJSONUtils_Compare(a, b); if (status != 0) { return status; } } /* array size mismatch? (one of both children is not NULL) */ if ((a != NULL) || (b != NULL)) { return -4; } else { return 0; } case cJSON_Object: cJSONUtils_SortObject(a); cJSONUtils_SortObject(b); for ((void)(a = a->child), b = b->child; (a != NULL) && (b != NULL); (void)(a = a->next), b = b->next) { int status = 0; /* compare object keys */ if (cJSONUtils_strcasecmp((unsigned char*)a->string, (unsigned char*)b->string)) { /* missing member */ return -6; } status = cJSONUtils_Compare(a, b); if (status != 0) { return status; } } /* object length mismatch (one of both children is not null) */ if ((a != NULL) || (b != NULL)) { return -5; } else { return 0; } default: break; } /* null, true or false */ return 0; } /* non broken version of cJSON_InsertItemInArray */ static cJSON_bool insert_item_in_array(cJSON *array, size_t which, cJSON *newitem) { cJSON *child = array->child; while (child && (which > 0)) { child = child->next; which--; } if (which > 0) { /* item is after the end of the array */ return 0; } if (child == NULL) { cJSON_AddItemToArray(array, newitem); return 1; } /* insert into the linked list */ newitem->next = child; newitem->prev = child->prev; child->prev = newitem; /* was it at the beginning */ if (child == array->child) { array->child = newitem; } else { newitem->prev->next = newitem; } return 1; } enum patch_operation { INVALID, ADD, REMOVE, REPLACE, MOVE, COPY, TEST }; static enum patch_operation decode_patch_operation(const cJSON * const patch) { cJSON *operation = cJSON_GetObjectItem(patch, "op"); if (!cJSON_IsString(operation)) { return INVALID; } if (strcmp(operation->valuestring, "add") == 0) { return ADD; } if (strcmp(operation->valuestring, "remove") == 0) { return REMOVE; } if (strcmp(operation->valuestring, "replace") == 0) { return REPLACE; } if (strcmp(operation->valuestring, "move") == 0) { return MOVE; } if (strcmp(operation->valuestring, "copy") == 0) { return COPY; } if (strcmp(operation->valuestring, "test") == 0) { return TEST; } return INVALID; } /* overwrite and existing item with another one and free resources on the way */ static void overwrite_item(cJSON * const root, const cJSON replacement) { if (root == NULL) { return; } if (root->string != NULL) { cJSON_free(root->string); } if (root->valuestring != NULL) { cJSON_free(root->valuestring); } if (root->child != NULL) { cJSON_Delete(root->child); } memcpy(root, &replacement, sizeof(cJSON)); } static int cJSONUtils_ApplyPatch(cJSON *object, const cJSON *patch) { cJSON *path = NULL; cJSON *value = NULL; cJSON *parent = NULL; enum patch_operation opcode = INVALID; unsigned char *parent_pointer = NULL; unsigned char *child_pointer = NULL; int status = 0; path = cJSON_GetObjectItem(patch, "path"); if (!cJSON_IsString(path)) { /* malformed patch. */ status = 2; goto cleanup; } opcode = decode_patch_operation(patch); if (opcode == INVALID) { status = 3; goto cleanup; } else if (opcode == TEST) { /* compare value: {...} with the given path */ status = cJSONUtils_Compare(cJSONUtils_GetPointer(object, path->valuestring), cJSON_GetObjectItem(patch, "value")); goto cleanup; } /* special case for replacing the root */ if (path->valuestring[0] == '\0') { if (opcode == REMOVE) { static const cJSON invalid = { NULL, NULL, NULL, cJSON_Invalid, NULL, 0, 0, NULL}; overwrite_item(object, invalid); status = 0; goto cleanup; } if ((opcode == REPLACE) || (opcode == ADD)) { value = cJSON_GetObjectItem(patch, "value"); if (value == NULL) { /* missing "value" for add/replace. */ status = 7; goto cleanup; } value = cJSON_Duplicate(value, 1); if (value == NULL) { /* out of memory for add/replace. */ status = 8; goto cleanup; } overwrite_item(object, *value); /* delete the duplicated value */ cJSON_free(value); value = NULL; /* the string "value" isn't needed */ if (object->string != NULL) { cJSON_free(object->string); object->string = NULL; } status = 0; goto cleanup; } } if ((opcode == REMOVE) || (opcode == REPLACE)) { /* Get rid of old. */ cJSON *old_item = cJSONUtils_PatchDetach(object, (unsigned char*)path->valuestring); if (old_item == NULL) { status = 13; goto cleanup; } cJSON_Delete(old_item); if (opcode == REMOVE) { /* For Remove, this job is done. */ status = 0; goto cleanup; } } /* Copy/Move uses "from". */ if ((opcode == MOVE) || (opcode == COPY)) { cJSON *from = cJSON_GetObjectItem(patch, "from"); if (from == NULL) { /* missing "from" for copy/move. */ status = 4; goto cleanup; } if (opcode == MOVE) { value = cJSONUtils_PatchDetach(object, (unsigned char*)from->valuestring); } if (opcode == COPY) { value = cJSONUtils_GetPointer(object, from->valuestring); } if (value == NULL) { /* missing "from" for copy/move. */ status = 5; goto cleanup; } if (opcode == COPY) { value = cJSON_Duplicate(value, 1); } if (value == NULL) { /* out of memory for copy/move. */ status = 6; goto cleanup; } } else /* Add/Replace uses "value". */ { value = cJSON_GetObjectItem(patch, "value"); if (value == NULL) { /* missing "value" for add/replace. */ status = 7; goto cleanup; } value = cJSON_Duplicate(value, 1); if (value == NULL) { /* out of memory for add/replace. */ status = 8; goto cleanup; } } /* Now, just add "value" to "path". */ /* split pointer in parent and child */ parent_pointer = cJSONUtils_strdup((unsigned char*)path->valuestring); child_pointer = (unsigned char*)strrchr((char*)parent_pointer, '/'); if (child_pointer != NULL) { child_pointer[0] = '\0'; child_pointer++; } parent = cJSONUtils_GetPointer(object, (char*)parent_pointer); cJSONUtils_InplaceDecodePointerString(child_pointer); /* add, remove, replace, move, copy, test. */ if ((parent == NULL) || (child_pointer == NULL)) { /* Couldn't find object to add to. */ status = 9; goto cleanup; } else if (cJSON_IsArray(parent)) { if (strcmp((char*)child_pointer, "-") == 0) { cJSON_AddItemToArray(parent, value); value = NULL; } else { size_t index = 0; if (!decode_array_index_from_pointer(child_pointer, &index)) { status = 11; goto cleanup; } if (!insert_item_in_array(parent, index, value)) { status = 10; goto cleanup; } value = NULL; } } else if (cJSON_IsObject(parent)) { cJSON_DeleteItemFromObject(parent, (char*)child_pointer); cJSON_AddItemToObject(parent, (char*)child_pointer, value); value = NULL; } cleanup: if (value != NULL) { cJSON_Delete(value); } if (parent_pointer != NULL) { cJSON_free(parent_pointer); } return status; } CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON * const object, const cJSON * const patches) { const cJSON *current_patch = NULL; int status = 0; if (!cJSON_IsArray(patches)) { /* malformed patches. */ return 1; } if (patches != NULL) { current_patch = patches->child; } while (current_patch != NULL) { status = cJSONUtils_ApplyPatch(object, current_patch); if (status != 0) { return status; } current_patch = current_patch->next; } return 0; } static void cJSONUtils_GeneratePatch(cJSON *patches, const unsigned char *op, const unsigned char *path, const unsigned char *suffix, cJSON *val) { cJSON *patch = cJSON_CreateObject(); cJSON_AddItemToObject(patch, "op", cJSON_CreateString((const char*)op)); if (suffix) { unsigned char *newpath = (unsigned char*)cJSON_malloc(strlen((const char*)path) + cJSONUtils_PointerEncodedstrlen(suffix) + 2); cJSONUtils_PointerEncodedstrcpy(newpath + sprintf((char*)newpath, "%s/", (const char*)path), suffix); cJSON_AddItemToObject(patch, "path", cJSON_CreateString((const char*)newpath)); free(newpath); } else { cJSON_AddItemToObject(patch, "path", cJSON_CreateString((const char*)path)); } if (val) { cJSON_AddItemToObject(patch, "value", cJSON_Duplicate(val, 1)); } cJSON_AddItemToArray(patches, patch); } CJSON_PUBLIC(void) cJSONUtils_AddPatchToArray(cJSON *array, const char *op, const char *path, cJSON *val) { cJSONUtils_GeneratePatch(array, (const unsigned char*)op, (const unsigned char*)path, 0, val); } static void cJSONUtils_CompareToPatch(cJSON *patches, const unsigned char *path, cJSON *from, cJSON *to) { if ((from == NULL) || (to == NULL)) { return; } if ((from->type & 0xFF) != (to->type & 0xFF)) { cJSONUtils_GeneratePatch(patches, (const unsigned char*)"replace", path, 0, to); return; } switch ((from->type & 0xFF)) { case cJSON_Number: if ((from->valueint != to->valueint) || (from->valuedouble != to->valuedouble)) { cJSONUtils_GeneratePatch(patches, (const unsigned char*)"replace", path, 0, to); } return; case cJSON_String: if (strcmp(from->valuestring, to->valuestring) != 0) { cJSONUtils_GeneratePatch(patches, (const unsigned char*)"replace", path, 0, to); } return; case cJSON_Array: { size_t c = 0; unsigned char *newpath = (unsigned char*)cJSON_malloc(strlen((const char*)path) + 23); /* Allow space for 64bit int. */ /* generate patches for all array elements that exist in "from" and "to" */ for ((void)(c = 0), (void)(from = from->child), to = to->child; from && to; (void)(from = from->next), (void)(to = to->next), c++) { /* check if conversion to unsigned long is valid * This should be eliminated at compile time by dead code elimination * if size_t is an alias of unsigned long, or if it is bigger */ if (c > ULONG_MAX) { free(newpath); return; } sprintf((char*)newpath, "%s/%lu", path, (unsigned long)c); /* path of the current array element */ cJSONUtils_CompareToPatch(patches, newpath, from, to); } /* remove leftover elements from 'from' that are not in 'to' */ for (; from; (void)(from = from->next)) { /* check if conversion to unsigned long is valid * This should be eliminated at compile time by dead code elimination * if size_t is an alias of unsigned long, or if it is bigger */ if (c > ULONG_MAX) { free(newpath); return; } sprintf((char*)newpath, "%lu", (unsigned long)c); cJSONUtils_GeneratePatch(patches, (const unsigned char*)"remove", path, newpath, 0); } /* add new elements in 'to' that were not in 'from' */ for (; to; (void)(to = to->next), c++) { cJSONUtils_GeneratePatch(patches, (const unsigned char*)"add", path, (const unsigned char*)"-", to); } free(newpath); return; } case cJSON_Object: { cJSON *a = NULL; cJSON *b = NULL; cJSONUtils_SortObject(from); cJSONUtils_SortObject(to); a = from->child; b = to->child; /* for all object values in the object with more of them */ while (a || b) { int diff = (!a) ? 1 : ((!b) ? -1 : cJSONUtils_strcasecmp((unsigned char*)a->string, (unsigned char*)b->string)); if (!diff) { /* both object keys are the same */ unsigned char *newpath = (unsigned char*)cJSON_malloc(strlen((const char*)path) + cJSONUtils_PointerEncodedstrlen((unsigned char*)a->string) + 2); cJSONUtils_PointerEncodedstrcpy(newpath + sprintf((char*)newpath, "%s/", path), (unsigned char*)a->string); /* create a patch for the element */ cJSONUtils_CompareToPatch(patches, newpath, a, b); free(newpath); a = a->next; b = b->next; } else if (diff < 0) { /* object element doesn't exist in 'to' --> remove it */ cJSONUtils_GeneratePatch(patches, (const unsigned char*)"remove", path, (unsigned char*)a->string, 0); a = a->next; } else { /* object element doesn't exist in 'from' --> add it */ cJSONUtils_GeneratePatch(patches, (const unsigned char*)"add", path, (unsigned char*)b->string, b); b = b->next; } } return; } default: break; } } CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatches(cJSON *from, cJSON *to) { cJSON *patches = cJSON_CreateArray(); cJSONUtils_CompareToPatch(patches, (const unsigned char*)"", from, to); return patches; } /* sort lists using mergesort */ static cJSON *cJSONUtils_SortList(cJSON *list) { cJSON *first = list; cJSON *second = list; cJSON *ptr = list; if (!list || !list->next) { /* One entry is sorted already. */ return list; } while (ptr && ptr->next && (cJSONUtils_strcasecmp((unsigned char*)ptr->string, (unsigned char*)ptr->next->string) < 0)) { /* Test for list sorted. */ ptr = ptr->next; } if (!ptr || !ptr->next) { /* Leave sorted lists unmodified. */ return list; } /* reset ptr to the beginning */ ptr = list; while (ptr) { /* Walk two pointers to find the middle. */ second = second->next; ptr = ptr->next; /* advances ptr two steps at a time */ if (ptr) { ptr = ptr->next; } } if (second && second->prev) { /* Split the lists */ second->prev->next = NULL; } /* Recursively sort the sub-lists. */ first = cJSONUtils_SortList(first); second = cJSONUtils_SortList(second); list = ptr = NULL; while (first && second) /* Merge the sub-lists */ { if (cJSONUtils_strcasecmp((unsigned char*)first->string, (unsigned char*)second->string) < 0) { if (!list) { /* start merged list with the first element of the first list */ list = ptr = first; } else { /* add first element of first list to merged list */ ptr->next = first; first->prev = ptr; ptr = first; } first = first->next; } else { if (!list) { /* start merged list with the first element of the second list */ list = ptr = second; } else { /* add first element of second list to merged list */ ptr->next = second; second->prev = ptr; ptr = second; } second = second->next; } } if (first) { /* Append rest of first list. */ if (!list) { return first; } ptr->next = first; first->prev = ptr; } if (second) { /* Append rest of second list */ if (!list) { return second; } ptr->next = second; second->prev = ptr; } return list; } CJSON_PUBLIC(void) cJSONUtils_SortObject(cJSON *object) { object->child = cJSONUtils_SortList(object->child); } CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatch(cJSON *target, cJSON *patch) { if (!cJSON_IsObject(patch)) { /* scalar value, array or NULL, just duplicate */ cJSON_Delete(target); return cJSON_Duplicate(patch, 1); } if (!cJSON_IsObject(target)) { cJSON_Delete(target); target = cJSON_CreateObject(); } patch = patch->child; while (patch) { if (cJSON_IsNull(patch)) { /* NULL is the indicator to remove a value, see RFC7396 */ cJSON_DeleteItemFromObject(target, patch->string); } else { cJSON *replaceme = cJSON_DetachItemFromObject(target, patch->string); cJSON_AddItemToObject(target, patch->string, cJSONUtils_MergePatch(replaceme, patch)); } patch = patch->next; } return target; } CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON *from, cJSON *to) { cJSON *patch = NULL; if (!to) { /* patch to delete everything */ return cJSON_CreateNull(); } if (!cJSON_IsObject(to) || !cJSON_IsObject(from)) { return cJSON_Duplicate(to, 1); } cJSONUtils_SortObject(from); cJSONUtils_SortObject(to); from = from->child; to = to->child; patch = cJSON_CreateObject(); while (from || to) { int compare = from ? (to ? strcmp(from->string, to->string) : -1) : 1; if (compare < 0) { /* from has a value that to doesn't have -> remove */ cJSON_AddItemToObject(patch, from->string, cJSON_CreateNull()); from = from->next; } else if (compare > 0) { /* to has a value that from doesn't have -> add to patch */ cJSON_AddItemToObject(patch, to->string, cJSON_Duplicate(to, 1)); to = to->next; } else { /* object key exists in both objects */ if (cJSONUtils_Compare(from, to)) { /* not identical --> generate a patch */ cJSON_AddItemToObject(patch, to->string, cJSONUtils_GenerateMergePatch(from, to)); } /* next key in the object */ from = from->next; to = to->next; } } if (!patch->child) { cJSON_Delete(patch); return NULL; } return patch; }