From 060e6563c01b6906c238ffea61e6cbb9955a0dd1 Mon Sep 17 00:00:00 2001 From: Dave Gamble Date: Wed, 11 Feb 2015 01:29:40 +0000 Subject: [PATCH] Add more patch functionality and some more utils. Also add a new #define to cJSON.h for SetNumberValue. git-svn-id: svn://svn.code.sf.net/p/cjson/code@66 e3330c51-1366-4df0-8b21-3ccf24e3d50e --- cJSON.h | 1 + cJSON_Utils.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++---- cJSON_Utils.h | 5 +- test_utils.c | 62 +++++++++++++------ 4 files changed, 201 insertions(+), 32 deletions(-) diff --git a/cJSON.h b/cJSON.h index 9b04cb1..2cea73d 100644 --- a/cJSON.h +++ b/cJSON.h @@ -140,6 +140,7 @@ extern void cJSON_Minify(char *json); /* When assigning an integer value, it needs to be propagated to valuedouble too. */ #define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) +#define cJSON_SetNumberValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) #ifdef __cplusplus } diff --git a/cJSON_Utils.c b/cJSON_Utils.c index cfc1619..7d325e8 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "cJSON_Utils.h" // JSON Pointer implementation: @@ -15,23 +16,65 @@ static int cJSONUtils_Pstrcasecmp(const char *a,const char *e) return 0; } +static int cJSONUtils_PointerEncodedstrlen(const char *s) {int l=0;for (;*s;s++,l++) if (*s=='~' || *s=='/') l++;return l;} + +static void cJSONUtils_PointerEncodedstrcpy(char *d,const char *s) +{ + for (;*s;s++) + { + if (*s=='/') {*d++='~';*d++='1';} + else if (*s=='~') {*d++='~';*d++='0';} + else *d++=*s; + } + *d=0; +} + +char *cJSONUtils_FindPointerFromObjectTo(cJSON *object,cJSON *target) +{ + if (object==target) return strdup(""); + + int type=object->type,c=0; + for (cJSON *obj=object->child;obj;obj=obj->next,c++) + { + char *found=cJSONUtils_FindPointerFromObjectTo(obj,target); + if (found) + { + if (type==cJSON_Array) + { + char *ret=(char*)malloc(strlen(found)+23); + sprintf(ret,"/%d%s",c,found); + free(found); + return ret; + } + else if (type==cJSON_Object) + { + char *ret=(char*)malloc(strlen(found)+cJSONUtils_PointerEncodedstrlen(obj->string)+2); + *ret='/';cJSONUtils_PointerEncodedstrcpy(ret+1,obj->string); + strcat(ret,found); + free(found); + return ret; + } + free(found); + return 0; + } + } + return 0; +} + cJSON *cJSONUtils_GetPointer(cJSON *object,const char *pointer) { - cJSON *target=object;int which=0;const char *element=0; - - while (*pointer=='/' && object) + while (*pointer++=='/' && object) { - pointer++; if (object->type==cJSON_Array) { - which=0; while (*pointer>='0' && *pointer<='9') which=(10*which) + *pointer++ - '0'; + int which=0; while (*pointer>='0' && *pointer<='9') which=(10*which) + *pointer++ - '0'; if (*pointer && *pointer!='/') return 0; object=cJSON_GetArrayItem(object,which); } else if (object->type==cJSON_Object) { - element=pointer; while (*pointer && *pointer!='/') pointer++; - object=object->child; while (object && cJSONUtils_Pstrcasecmp(object->string,element)) object=object->next; // GetObjectItem. + object=object->child; while (object && cJSONUtils_Pstrcasecmp(object->string,pointer)) object=object->next; // GetObjectItem. + while (*pointer && *pointer!='/') pointer++; } else return 0; } @@ -42,8 +85,7 @@ cJSON *cJSONUtils_GetPointer(cJSON *object,const char *pointer) static void cJSONUtils_InplaceDecodePointerString(char *string) { char *s2=string; - for (;*string;s2++,string++) - *s2=(*string!='~')?(*string):((*(++string)=='0')?'~':'/'); + for (;*string;s2++,string++) *s2=(*string!='~')?(*string):((*(++string)=='0')?'~':'/'); *s2=0; } @@ -63,6 +105,28 @@ static cJSON *cJSONUtils_PatchDetach(cJSON *object,const char *path) return ret; } +static int cJSONUtils_Compare(cJSON *a,cJSON *b) +{ + if (a->type!=b->type) return -1; // mismatched type. + switch (a->type) + { + case cJSON_Number: return (a->valueint!=b->valueint || a->valuedouble!=b->valuedouble)?-2:0; // numeric mismatch. + case cJSON_String: return (strcmp(a->valuestring,b->valuestring)!=0)?-3:0; // string mismatch. + case cJSON_Array: for (a=a->child,b=b->child;a && b;a=a->next,b=b->next) {int err=cJSONUtils_Compare(a,b);if (err) return err;} + return (a || b)?-4:0; // array size mismatch. + case cJSON_Object: + if (cJSON_GetArraySize(a)!=cJSON_GetArraySize(b)) return -5; // object length mismatch. + for (a=a->child;a;a=a->next) + { + int err=0;cJSON *s=cJSON_GetObjectItem(b,a->string); if (!s) return -6; // missing object member. + err=cJSONUtils_Compare(a,s);if (err) return err; + } + return 0; + default: break; + } + return 0; +} + static int cJSONUtils_ApplyPatch(cJSON *object,cJSON *patch) { cJSON *op=0,*path=0,*value=0;int opcode=0; @@ -76,10 +140,8 @@ static int cJSONUtils_ApplyPatch(cJSON *object,cJSON *patch) else if (!strcmp(op->valuestring,"replace"))opcode=2; else if (!strcmp(op->valuestring,"move")) opcode=3; else if (!strcmp(op->valuestring,"copy")) opcode=4; - else if (!strcmp(op->valuestring,"test")) opcode=5; + else if (!strcmp(op->valuestring,"test")) return cJSONUtils_Compare(cJSONUtils_GetPointer(object,path->valuestring),cJSON_GetObjectItem(patch,"value")); else return 3; // unknown opcode. - - if (opcode==5) return 10; // TEST IS CURRENTLY UNIMPLEMENTED. if (opcode==1 || opcode==2) // Remove/Replace { @@ -142,3 +204,82 @@ int cJSONUtils_ApplyPatches(cJSON *object,cJSON *patches) return 0; } +static void cJSONUtils_GeneratePatch(cJSON *patches,const char *op,const char *path,const char *suffix,cJSON *val) +{ + cJSON *patch=cJSON_CreateObject(); + cJSON_AddItemToObject(patch,"op",cJSON_CreateString(op)); + if (suffix) + { + char *newpath=(char*)malloc(strlen(path)+cJSONUtils_PointerEncodedstrlen(suffix)+2); + cJSONUtils_PointerEncodedstrcpy(newpath+sprintf(newpath,"%s/",path),suffix); + cJSON_AddItemToObject(patch,"path",cJSON_CreateString(newpath)); + free(newpath); + } + else cJSON_AddItemToObject(patch,"path",cJSON_CreateString(path)); + if (val) cJSON_AddItemToObject(patch,"value",cJSON_Duplicate(val,1)); + cJSON_AddItemToArray(patches,patch); +} + +void cJSONUtils_AddPatchToArray(cJSON *array,const char *op,const char *path,cJSON *val) {cJSONUtils_GeneratePatch(array,op,path,0,val);} + +static void cJSONUtils_CompareToPatch(cJSON *patches,const char *path,cJSON *from,cJSON *to) +{ + if (from->type!=to->type) {cJSONUtils_GeneratePatch(patches,"replace",path,0,to); return; } + + switch (from->type) + { + case cJSON_Number: + if (from->valueint!=to->valueint || from->valuedouble!=to->valuedouble) + cJSONUtils_GeneratePatch(patches,"replace",path,0,to); + return; + + case cJSON_String: + if (strcmp(from->valuestring,to->valuestring)!=0) + cJSONUtils_GeneratePatch(patches,"replace",path,0,to); + return; + + case cJSON_Array: + { + int c;char *newpath=(char*)malloc(strlen(path)+23); // Allow space for 64bit int. + for (c=0,from=from->child,to=to->child;from && to;from=from->next,to=to->next,c++){ + sprintf(newpath,"%s/%d",path,c); cJSONUtils_CompareToPatch(patches,newpath,from,to); + } + for (;from;from=from->next,c++) {sprintf(newpath,"%d",c); cJSONUtils_GeneratePatch(patches,"remove",path,newpath,0); } + for (;to;to=to->next,c++) cJSONUtils_GeneratePatch(patches,"add",path,"-",to); + free(newpath); + return; + } + + case cJSON_Object: + for (cJSON *a=from->child;a;a=a->next) + { + if (!cJSON_GetObjectItem(to,a->string)) cJSONUtils_GeneratePatch(patches,"remove",path,a->string,0); + } + for (cJSON *a=to->child;a;a=a->next) + { + cJSON *other=cJSON_GetObjectItem(from,a->string); + if (!other) cJSONUtils_GeneratePatch(patches,"add",path,a->string,a); + else + { + char *newpath=(char*)malloc(strlen(path)+cJSONUtils_PointerEncodedstrlen(a->string)+2); + cJSONUtils_PointerEncodedstrcpy(newpath+sprintf(newpath,"%s/",path),a->string); + cJSONUtils_CompareToPatch(patches,newpath,other,a); + free(newpath); + } + } + return; + + default: break; + } +} + + +cJSON* cJSONUtils_GeneratePatches(cJSON *from,cJSON *to) +{ + cJSON *patches=cJSON_CreateArray(); + cJSONUtils_CompareToPatch(patches,"",from,to); + return patches; +} + + + diff --git a/cJSON_Utils.h b/cJSON_Utils.h index 91ee08c..0b68d3c 100644 --- a/cJSON_Utils.h +++ b/cJSON_Utils.h @@ -4,7 +4,8 @@ cJSON *cJSONUtils_GetPointer(cJSON *object,const char *pointer); // Implement RFC6902 (https://tools.ietf.org/html/rfc6902) JSON Patch spec. -//cJSON* cJSONUtils_GeneratePatches(cJSON *from,cJSON *to); // Not yet implemented. +cJSON* cJSONUtils_GeneratePatches(cJSON *from,cJSON *to); +void cJSONUtils_AddPatchToArray(cJSON *array,const char *op,const char *path,cJSON *val); // Utility for generating patch array entries. int cJSONUtils_ApplyPatches(cJSON *object,cJSON *patches); // Returns 0 for success. // Note that ApplyPatches is NOT atomic on failure. To implement an atomic ApplyPatches, use: @@ -17,3 +18,5 @@ int cJSONUtils_ApplyPatches(cJSON *object,cJSON *patches); // Returns 0 for succ // return error; //} // Code not added to library since this strategy is a LOT slower. + +char *cJSONUtils_FindPointerFromObjectTo(cJSON *object,cJSON *target); // Given a root object and a target object, construct a pointer from one to the other. diff --git a/test_utils.c b/test_utils.c index fc222de..cc9f270 100644 --- a/test_utils.c +++ b/test_utils.c @@ -1,5 +1,6 @@ #include #include +#include #include "cJSON_Utils.h" int main() @@ -30,25 +31,25 @@ int main() } cJSON_Delete(root); - // JSON Patch tests: - const char *patches[15][2]={ - {"{ \"foo\": \"bar\"}", "[{ \"op\": \"add\", \"path\": \"/baz\", \"value\": \"qux\" }]"}, - {"{ \"foo\": [ \"bar\", \"baz\" ] }", "[{ \"op\": \"add\", \"path\": \"/foo/1\", \"value\": \"qux\" }]"}, - {"{\"baz\": \"qux\",\"foo\": \"bar\"}"," [{ \"op\": \"remove\", \"path\": \"/baz\" }]"}, - {"{ \"foo\": [ \"bar\", \"qux\", \"baz\" ] }","[{ \"op\": \"remove\", \"path\": \"/foo/1\" }]"}, - {"{ \"baz\": \"qux\",\"foo\": \"bar\"}","[{ \"op\": \"replace\", \"path\": \"/baz\", \"value\": \"boo\" }]"}, - {"{\"foo\": {\"bar\": \"baz\",\"waldo\": \"fred\"},\"qux\": {\"corge\": \"grault\"}}","[{ \"op\": \"move\", \"from\": \"/foo/waldo\", \"path\": \"/qux/thud\" }]"}, - {"{ \"foo\": [ \"all\", \"grass\", \"cows\", \"eat\" ] }","[ { \"op\": \"move\", \"from\": \"/foo/1\", \"path\": \"/foo/3\" }]"}, - {"{\"baz\": \"qux\",\"foo\": [ \"a\", 2, \"c\" ]}","[{ \"op\": \"test\", \"path\": \"/baz\", \"value\": \"qux\" },{ \"op\": \"test\", \"path\": \"/foo/1\", \"value\": 2 }]"}, - {"{ \"baz\": \"qux\" }","[ { \"op\": \"test\", \"path\": \"/baz\", \"value\": \"bar\" }]"}, - {"{ \"foo\": \"bar\" }","[{ \"op\": \"add\", \"path\": \"/child\", \"value\": { \"grandchild\": { } } }]"}, - {"{ \"foo\": \"bar\" }","[{ \"op\": \"add\", \"path\": \"/baz\", \"value\": \"qux\", \"xyz\": 123 }]"}, - {"{ \"foo\": \"bar\" }","[{ \"op\": \"add\", \"path\": \"/baz/bat\", \"value\": \"qux\" }]"}, - {"{\"/\": 9,\"~1\": 10}","[{\"op\": \"test\", \"path\": \"/~01\", \"value\": 10}]"}, - {"{\"/\": 9,\"~1\": 10}","[{\"op\": \"test\", \"path\": \"/~01\", \"value\": \"10\"}]"}, - {"{ \"foo\": [\"bar\"] }","[ { \"op\": \"add\", \"path\": \"/foo/-\", \"value\": [\"abc\", \"def\"] }]"}}; + // JSON Apply Patch tests: + const char *patches[15][3]={ + {"{ \"foo\": \"bar\"}", "[{ \"op\": \"add\", \"path\": \"/baz\", \"value\": \"qux\" }]","{\"baz\": \"qux\",\"foo\": \"bar\"}"}, + {"{ \"foo\": [ \"bar\", \"baz\" ] }", "[{ \"op\": \"add\", \"path\": \"/foo/1\", \"value\": \"qux\" }]","{\"foo\": [ \"bar\", \"qux\", \"baz\" ] }"}, + {"{\"baz\": \"qux\",\"foo\": \"bar\"}"," [{ \"op\": \"remove\", \"path\": \"/baz\" }]","{\"foo\": \"bar\" }"}, + {"{ \"foo\": [ \"bar\", \"qux\", \"baz\" ] }","[{ \"op\": \"remove\", \"path\": \"/foo/1\" }]","{\"foo\": [ \"bar\", \"baz\" ] }"}, + {"{ \"baz\": \"qux\",\"foo\": \"bar\"}","[{ \"op\": \"replace\", \"path\": \"/baz\", \"value\": \"boo\" }]","{\"baz\": \"boo\",\"foo\": \"bar\"}"}, + {"{\"foo\": {\"bar\": \"baz\",\"waldo\": \"fred\"},\"qux\": {\"corge\": \"grault\"}}","[{ \"op\": \"move\", \"from\": \"/foo/waldo\", \"path\": \"/qux/thud\" }]","{\"foo\": {\"bar\": \"baz\"},\"qux\": {\"corge\": \"grault\",\"thud\": \"fred\"}}"}, + {"{ \"foo\": [ \"all\", \"grass\", \"cows\", \"eat\" ] }","[ { \"op\": \"move\", \"from\": \"/foo/1\", \"path\": \"/foo/3\" }]","{ \"foo\": [ \"all\", \"cows\", \"eat\", \"grass\" ] }"}, + {"{\"baz\": \"qux\",\"foo\": [ \"a\", 2, \"c\" ]}","[{ \"op\": \"test\", \"path\": \"/baz\", \"value\": \"qux\" },{ \"op\": \"test\", \"path\": \"/foo/1\", \"value\": 2 }]",""}, + {"{ \"baz\": \"qux\" }","[ { \"op\": \"test\", \"path\": \"/baz\", \"value\": \"bar\" }]",""}, + {"{ \"foo\": \"bar\" }","[{ \"op\": \"add\", \"path\": \"/child\", \"value\": { \"grandchild\": { } } }]","{\"foo\": \"bar\",\"child\": {\"grandchild\": {}}}"}, + {"{ \"foo\": \"bar\" }","[{ \"op\": \"add\", \"path\": \"/baz\", \"value\": \"qux\", \"xyz\": 123 }]","{\"foo\": \"bar\",\"baz\": \"qux\"}"}, + {"{ \"foo\": \"bar\" }","[{ \"op\": \"add\", \"path\": \"/baz/bat\", \"value\": \"qux\" }]",""}, + {"{\"/\": 9,\"~1\": 10}","[{\"op\": \"test\", \"path\": \"/~01\", \"value\": 10}]",""}, + {"{\"/\": 9,\"~1\": 10}","[{\"op\": \"test\", \"path\": \"/~01\", \"value\": \"10\"}]",""}, + {"{ \"foo\": [\"bar\"] }","[ { \"op\": \"add\", \"path\": \"/foo/-\", \"value\": [\"abc\", \"def\"] }]","{\"foo\": [\"bar\", [\"abc\", \"def\"]] }"}}; - printf("JSON Patch Tests\n"); + printf("JSON Apply Patch Tests\n"); for (int i=0;i<15;i++) { cJSON *object=cJSON_Parse(patches[i][0]); @@ -56,8 +57,31 @@ int main() int err=cJSONUtils_ApplyPatches(object,patch); char *output=cJSON_Print(object); printf("Test %d (err %d):\n%s\n\n",i+1,err,output); - free(output); + free(output);cJSON_Delete(object);cJSON_Delete(patch); } + // JSON Generate Patch tests: + printf("JSON Generate Patch Tests\n"); + for (int i=0;i<15;i++) + { + if (!strlen(patches[i][2])) continue; + cJSON *from=cJSON_Parse(patches[i][0]); + cJSON *to=cJSON_Parse(patches[i][2]); + cJSON *patch=cJSONUtils_GeneratePatches(from,to); + char *out=cJSON_Print(patch); + printf("Test %d: (patch: %s):\n%s\n\n",i+1,patches[i][1],out); + free(out);cJSON_Delete(from);cJSON_Delete(to);cJSON_Delete(patch); + } + + // Misc tests: + printf("JSON Pointer construct\n"); + int numbers[10]={0,1,2,3,4,5,6,7,8,9}; + cJSON *object=cJSON_CreateObject(); + cJSON *nums=cJSON_CreateIntArray(numbers,10); + cJSON *num6=cJSON_GetArrayItem(nums,6); + cJSON_AddItemToObject(object,"numbers",nums); + printf("Pointer: [%s]\n",cJSONUtils_FindPointerFromObjectTo(object,num6)); + printf("Pointer: [%s]\n",cJSONUtils_FindPointerFromObjectTo(object,nums)); + printf("Pointer: [%s]\n",cJSONUtils_FindPointerFromObjectTo(object,object)); }