浏览代码

start of JSON Patch implementation. cJSON gained a cJSON_InsertItemToArray which pushes elements up by one.
This is needed for JSON Patch. Everything but Test is implemented for ApplyPatches.


git-svn-id: svn://svn.code.sf.net/p/cjson/code@65 e3330c51-1366-4df0-8b21-3ccf24e3d50e

Dave Gamble 10 年之前
父节点
当前提交
3c6b3cc617
共有 5 个文件被更改,包括 157 次插入2 次删除
  1. 2 0
      cJSON.c
  2. 1 0
      cJSON.h
  3. 106 1
      cJSON_Utils.c
  4. 14 0
      cJSON_Utils.h
  5. 34 1
      test_utils.c

+ 2 - 0
cJSON.c

@@ -691,6 +691,8 @@ cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int i=0;cJS
 void   cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));}
 
 /* Replace array/object items with new ones. */
+void   cJSON_InsertItemInArray(cJSON *array,int which,cJSON *newitem)		{cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) {cJSON_AddItemToArray(array,newitem);return;}
+	newitem->next=c;newitem->prev=c->prev;c->prev=newitem;if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;}
 void   cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem)		{cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return;
 	newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem;
 	if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);}

+ 1 - 0
cJSON.h

@@ -115,6 +115,7 @@ extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string);
 extern void   cJSON_DeleteItemFromObject(cJSON *object,const char *string);
 	
 /* Update array items. */
+extern void cJSON_InsertItemInArray(cJSON *array,int which,cJSON *newitem);	// Shifts pre-existing items to the right.
 extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem);
 extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
 

+ 106 - 1
cJSON_Utils.c

@@ -1,6 +1,9 @@
 #include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
 #include "cJSON_Utils.h"
 
+// JSON Pointer implementation:
 static int cJSONUtils_Pstrcasecmp(const char *a,const char *e)
 {
 	if (!a || !e) return (a==e)?0:1;
@@ -12,7 +15,6 @@ static int cJSONUtils_Pstrcasecmp(const char *a,const char *e)
 	return 0;
 }
 
-
 cJSON *cJSONUtils_GetPointer(cJSON *object,const char *pointer)
 {
 	cJSON *target=object;int which=0;const char *element=0;
@@ -36,4 +38,107 @@ cJSON *cJSONUtils_GetPointer(cJSON *object,const char *pointer)
 	return object;
 }
 
+// JSON Patch implementation.
+static void cJSONUtils_InplaceDecodePointerString(char *string)
+{
+	char *s2=string;
+	for (;*string;s2++,string++)
+		*s2=(*string!='~')?(*string):((*(++string)=='0')?'~':'/');
+	*s2=0;
+}
+
+static cJSON *cJSONUtils_PatchDetach(cJSON *object,const char *path)
+{
+	char *parentptr=0,*childptr=0;cJSON *parent=0;
+
+	parentptr=strdup(path);	childptr=strrchr(parentptr,'/');	if (childptr) *childptr++=0;
+	parent=cJSONUtils_GetPointer(object,parentptr);
+	cJSONUtils_InplaceDecodePointerString(childptr);
+
+	cJSON *ret=0;
+	if (!parent) ret=0;	// Couldn't find object to remove child from.
+	else if (parent->type==cJSON_Array)		ret=cJSON_DetachItemFromArray(parent,atoi(childptr));
+	else if (parent->type==cJSON_Object)	ret=cJSON_DetachItemFromObject(parent,childptr);
+	free(parentptr);
+	return ret;
+}
+
+static int cJSONUtils_ApplyPatch(cJSON *object,cJSON *patch)
+{
+	cJSON *op=0,*path=0,*value=0;int opcode=0;
+	
+	op=cJSON_GetObjectItem(patch,"op");
+	path=cJSON_GetObjectItem(patch,"path");
+	if (!op || !path) return 2;	// malformed patch.
+
+	if		(!strcmp(op->valuestring,"add"))	opcode=0;
+	else if (!strcmp(op->valuestring,"remove")) opcode=1;
+	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 return 3; // unknown opcode.
+	
+	if (opcode==5) return 10; // TEST IS CURRENTLY UNIMPLEMENTED.
+
+	if (opcode==1 || opcode==2)	// Remove/Replace
+	{
+		cJSON_Delete(cJSONUtils_PatchDetach(object,path->valuestring));	// Get rid of old.
+		if (opcode==1) return 0;	// For Remove, this is job done.
+	}
+
+	if (opcode==3 || opcode==4)	// Copy/Move uses "from".
+	{
+		cJSON *from=cJSON_GetObjectItem(patch,"from");	if (!from) return 4; // missing "from" for copy/move.
+
+		if (opcode==3) value=cJSONUtils_PatchDetach(object,from->valuestring);
+		if (opcode==4) value=cJSONUtils_GetPointer(object,from->valuestring);
+		if (!value) return 5; // missing "from" for copy/move.
+		if (opcode==4) value=cJSON_Duplicate(value,1);
+		if (!value) return 6; // out of memory for copy/move.
+	}
+	else	// Add/Replace uses "value".
+	{
+		value=cJSON_GetObjectItem(patch,"value");
+		if (!value) return 7; // missing "value" for add/replace.
+		value=cJSON_Duplicate(value,1);
+		if (!value) return 8; // out of memory for add/replace.
+	}
+		
+	// Now, just add "value" to "path".
+	char *parentptr=0,*childptr=0;cJSON *parent=0;
+
+	parentptr=strdup(path->valuestring);	childptr=strrchr(parentptr,'/');	if (childptr) *childptr++=0;
+	parent=cJSONUtils_GetPointer(object,parentptr);
+	cJSONUtils_InplaceDecodePointerString(childptr);
+
+	// add, remove, replace, move, copy, test.
+	if (!parent) {free(parentptr); return 9;}	// Couldn't find object to add to.
+	else if (parent->type==cJSON_Array)
+	{
+		if (!strcmp(childptr,"-"))	cJSON_AddItemToArray(parent,value);
+		else						cJSON_InsertItemInArray(parent,atoi(childptr),value);
+	}
+	else if (parent->type==cJSON_Object)
+	{
+		cJSON_DeleteItemFromObject(parent,childptr);
+		cJSON_AddItemToObject(parent,childptr,value);
+	}
+	free(parentptr);
+	return 0;
+}
+
+
+int cJSONUtils_ApplyPatches(cJSON *object,cJSON *patches)
+{
+	int err;
+	if (!patches->type==cJSON_Array) return 1;	// malformed patches.
+	if (patches) patches=patches->child;
+	while (patches)
+	{
+		if ((err=cJSONUtils_ApplyPatch(object,patches))) return err;
+		patches=patches->next;
+	}
+	return 0;
+}
 

+ 14 - 0
cJSON_Utils.h

@@ -3,3 +3,17 @@
 // Implement RFC6901 (https://tools.ietf.org/html/rfc6901) JSON Pointer spec.
 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.
+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:
+//int cJSONUtils_AtomicApplyPatches(cJSON **object, cJSON *patches)
+//{
+//	cJSON *modme=cJSON_Duplicate(*object,1);
+//	int error=cJSONUtils_ApplyPatches(modme,patches);
+//	if (!error)	{cJSON_Delete(*object);*object=modme;}
+//	else		cJSON_Delete(modme);
+//	return error;
+//}
+// Code not added to library since this strategy is a LOT slower.

+ 34 - 1
test_utils.c

@@ -4,6 +4,7 @@
 
 int main()
 {
+	// JSON Pointer tests:
 	const char *json="{"
 		"\"foo\": [\"bar\", \"baz\"],"
 		"\"\": 0,"
@@ -23,8 +24,40 @@ int main()
 	cJSON *root=cJSON_Parse(json);
 	for (int i=0;i<12;i++)
 	{
-		printf("Test %d:\n%s\n\n",i+1,cJSON_Print(cJSONUtils_GetPointer(root,tests[i])));
+		char *output=cJSON_Print(cJSONUtils_GetPointer(root,tests[i]));
+		printf("Test %d:\n%s\n\n",i+1,output);
+		free(output);
 	}
+	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\"] }]"}};
 
+	printf("JSON Patch Tests\n");
+	for (int i=0;i<15;i++)
+	{
+		cJSON *object=cJSON_Parse(patches[i][0]);
+		cJSON *patch=cJSON_Parse(patches[i][1]);
+		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);	
+	}
+
+	
 }