From 4df34363934e4d6ce937a59c0e61608cb17811da Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Thu, 23 Jul 2020 02:18:56 +0700 Subject: [PATCH] Add support for clonin `JObject` and `JArray` Addition of cloning also allows us to add regular setters for JSON object and arrays that would clone a passed object to ensure each object can only have one parent. --- sources/Data/JSON/JArray.uc | 79 ++++++++++++++++++++++++++++ sources/Data/JSON/JObject.uc | 69 ++++++++++++++++++++++-- sources/Data/JSON/JSON.uc | 5 ++ sources/Data/JSON/Tests/TEST_JSON.uc | 52 ++++++++++++++++++ 4 files changed, 201 insertions(+), 4 deletions(-) diff --git a/sources/Data/JSON/JArray.uc b/sources/Data/JSON/JArray.uc index 1588401..18a6a95 100644 --- a/sources/Data/JSON/JArray.uc +++ b/sources/Data/JSON/JArray.uc @@ -316,6 +316,60 @@ public final function JArray SetNull return self; } +public final function JArray SetArray( + int index, + JArray template, + optional bool preventExpansion +) +{ + local JStorageAtom newStorageValue; + if (index < 0) return self; + if (template == none) return self; + + if (index >= data.length) + { + if (preventExpansion) + { + return self; + } + else + { + SetLength(index + 1); + } + } + newStorageValue.type = JSON_Array; + newStorageValue.complexValue = template.Clone(); + data[index] = newStorageValue; + return self; +} + +public final function JArray SetObject( + int index, + JObject template, + optional bool preventExpansion +) +{ + local JStorageAtom newStorageValue; + if (index < 0) return self; + if (template == none) return self; + + if (index >= data.length) + { + if (preventExpansion) + { + return self; + } + else + { + SetLength(index + 1); + } + } + newStorageValue.type = JSON_Object; + newStorageValue.complexValue = template.Clone(); + data[index] = newStorageValue; + return self; +} + // JSON array and object types don't have setters, but instead have // functions to create a new, empty array/object under a certain name. // If passed index is negative - does nothing. @@ -452,6 +506,31 @@ public function bool IsSubsetOf(JSON rightValue) return true; } +public function JSON Clone() +{ + local int i; + local JArray clonedArray; + local array clonedData; + clonedArray = _.json.NewArray(); + if (clonedArray == none) + { + _.logger.Failure("Cannot clone `JArray`: cannot spawn a new instance."); + return none; + } + clonedData = data; + for (i = 0; i < clonedData.length; i += 1) + { + if (clonedData[i].complexValue == none) continue; + if ( clonedData[i].type != JSON_Array + && clonedData[i].type != JSON_Object) { + continue; + } + clonedData[i].complexValue = clonedData[i].complexValue.Clone(); + } + clonedArray.data = clonedData; + return clonedArray; +} + defaultproperties { } \ No newline at end of file diff --git a/sources/Data/JSON/JObject.uc b/sources/Data/JSON/JObject.uc index 81208c1..b657ec5 100644 --- a/sources/Data/JSON/JObject.uc +++ b/sources/Data/JSON/JObject.uc @@ -389,6 +389,34 @@ public final function JObject SetNull(string name) return self; } +public final function JObject SetArray(string name, JArray template) +{ + local JProperty property; + if (template == none) return self; + FindProperty(name, property); + if (property.value.complexValue != none) { + property.value.complexValue.Destroy(); + } + property.value.type = JSON_Array; + property.value.complexValue = template.Clone(); + UpdateProperty(property); + return self; +} + +public final function JObject SetObject(string name, JObject template) +{ + local JProperty property; + if (template == none) return self; + FindProperty(name, property); + if (property.value.complexValue != none) { + property.value.complexValue.Destroy(); + } + property.value.type = JSON_Object; + property.value.complexValue = template.Clone(); + UpdateProperty(property); + return self; +} + // JSON array and object types don't have setters, but instead have // functions to create a new, empty array/object under a certain name. // They return object itself, allowing user to chain calls like this: @@ -400,8 +428,8 @@ public final function JObject CreateArray(string name) if (property.value.complexValue != none) { property.value.complexValue.Destroy(); } - property.value.type = JSON_Array; - property.value.complexValue = _.json.NewArray(); + property.value.type = JSON_Array; + property.value.complexValue = _.json.NewArray(); UpdateProperty(property); return self; } @@ -413,8 +441,8 @@ public final function JObject CreateObject(string name) if (property.value.complexValue != none) { property.value.complexValue.Destroy(); } - property.value.type = JSON_Object; - property.value.complexValue = _.json.NewObject(); + property.value.type = JSON_Object; + property.value.complexValue = _.json.NewObject(); UpdateProperty(property); return self; } @@ -466,6 +494,39 @@ public function bool IsSubsetOf(JSON rightJSON) return true; } +public function JSON Clone() +{ + local int i, j; + local JObject clonedObject; + local array clonedHashTable; + local array nextProperties; + clonedObject = _.json.NewObject(); + if (clonedObject == none) + { + _.logger.Failure("Cannot clone `JObject`: cannot spawn a" + @ "new instance."); + return none; + } + clonedHashTable = hashTable; + for (i = 0; i < clonedHashTable.length; i += 1) + { + nextProperties = clonedHashTable[i].properties; + for (j = 0; j < nextProperties.length; j += 1) + { + if (nextProperties[j].value.complexValue == none) continue; + if ( nextProperties[j].value.type != JSON_Array + && nextProperties[j].value.type != JSON_Object) { + continue; + } + nextProperties[j].value.complexValue = + nextProperties[j].value.complexValue.Clone(); + } + clonedHashTable[i].properties = nextProperties; + } + clonedObject.hashTable = clonedHashTable; + return clonedObject; +} + defaultproperties { ABSOLUTE_LOWER_CAPACITY_LIMIT = 10 diff --git a/sources/Data/JSON/JSON.uc b/sources/Data/JSON/JSON.uc index b5ccc37..36aa5b4 100644 --- a/sources/Data/JSON/JSON.uc +++ b/sources/Data/JSON/JSON.uc @@ -84,6 +84,11 @@ enum JComparisonResult JCR_Equal }; +public function JSON Clone() +{ + return none; +} + public function bool IsSubsetOf(JSON rightJSON) { return false; diff --git a/sources/Data/JSON/Tests/TEST_JSON.uc b/sources/Data/JSON/Tests/TEST_JSON.uc index b5464e8..95e1d38 100644 --- a/sources/Data/JSON/Tests/TEST_JSON.uc +++ b/sources/Data/JSON/Tests/TEST_JSON.uc @@ -29,6 +29,8 @@ protected static function TESTS() Test_ObjectKeys(); Test_ArrayGetSetRemove(); Test_JSONComparison(); + Test_JSONCloning(); + Test_JSONSetComplexValues(); } protected static function Test_ObjectGetSetRemove() @@ -1109,6 +1111,56 @@ protected static function SubTest_JSONCompare() TEST_ExpectTrue(test2.Compare(test1) == JCR_Incomparable); } +protected static function Test_JSONCloning() +{ + local JObject original, clone; + original = Prepare_FoldedObject(); + clone = JObject(original.Clone()); + Context("Testing cloning functionality of JSON data."); + Issue("JSON data is cloned incorrectly."); + TEST_ExpectTrue(original.IsEqual(clone)); + + Issue("`Clone()` produces only a shallow copy."); + TEST_ExpectTrue(original != clone); + TEST_ExpectTrue( original.GetObject("innerObject") + != clone.GetObject("innerObject")); + TEST_ExpectTrue( original.GetObject("innerObject").GetArray("array") + != clone.GetObject("innerObject").GetArray("array")); + TEST_ExpectTrue( original.GetObject("innerObject").GetObject("one more") + != clone.GetObject("innerObject").GetObject("one more")); + TEST_ExpectTrue( + original.GetObject("innerObject").GetArray("array").GetObject(3) + != clone.GetObject("innerObject").GetArray("array").GetObject(3)); +} + +protected static function Test_JSONSetComplexValues() +{ + local JObject testObject, original; + local JArray testArray; + Context("Testing `Set...()` operation for `JObject` / `JArray`."); + original = Prepare_FoldedObject(); + testObject = Prepare_FoldedObject(); + testArray = + JArray(testObject.GetObject("innerObject").GetArray("array").Clone()); + testObject.SetObject("newObjectCopy", testObject); + testObject.SetArray("newArrayCopy", testArray); + testArray.SetObject(0, testObject); + testArray.SetArray(1, testArray); + Issue("`Set() for `JObject` / `JArray` does not produce correct copy."); + Test_ExpectTrue(testObject.GetObject("newObjectCopy").IsEqual(original)); + Test_ExpectTrue(testObject.GetArray("newArrayCopy") + .IsEqual(original.GetObject("innerObject").GetArray("array"))); + Test_ExpectTrue(testArray.GetObject(0).IsEqual(testObject)); + + Issue("`Set() for `JObject` / `JArray` produces a shallow copy."); + Test_ExpectTrue(testObject.GetObject("newObjectCopy") != original); + Test_ExpectTrue( testObject.GetObject("newArrayCopy") + != original.GetObject("innerObject").GetArray("array")); + Test_ExpectTrue(testArray.GetObject(0) != original); + Test_ExpectTrue( testArray.GetArray(1) + != original.GetObject("innerObject").GetArray("array")); +} + defaultproperties { caseName = "JSON"