From 2bb320fa4dac99682c89476face76c28fc91dde1 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Wed, 22 Jul 2020 21:26:40 +0700 Subject: [PATCH] Add equality/subset checks for JSON data --- sources/Data/JSON/JArray.uc | 18 +++++ sources/Data/JSON/JObject.uc | 24 ++++++ sources/Data/JSON/JSON.uc | 69 ++++++++++++++++ sources/Data/JSON/Tests/TEST_JSON.uc | 113 +++++++++++++++++++++++++++ 4 files changed, 224 insertions(+) diff --git a/sources/Data/JSON/JArray.uc b/sources/Data/JSON/JArray.uc index 8612fd3..1588401 100644 --- a/sources/Data/JSON/JArray.uc +++ b/sources/Data/JSON/JArray.uc @@ -434,6 +434,24 @@ public final function bool RemoveValue(int index, optional int amount) return true; } +public function bool IsSubsetOf(JSON rightValue) +{ + local int i; + local JArray rightArray; + local array rightAtomArray; + rightArray = JArray(rightValue); + if (rightArray == none) return false; + if (data.length > rightArray.data.length) return false; + rightAtomArray = rightArray.data; + for (i = 0; i < data.length; i += 1) + { + if (!AreAtomsEqual(data[i], rightAtomArray[i])) { + return false; + } + } + return true; +} + defaultproperties { } \ No newline at end of file diff --git a/sources/Data/JSON/JObject.uc b/sources/Data/JSON/JObject.uc index 4f8f48e..81208c1 100644 --- a/sources/Data/JSON/JObject.uc +++ b/sources/Data/JSON/JObject.uc @@ -442,6 +442,30 @@ public final function array GetKeys() return result; } +public function bool IsSubsetOf(JSON rightJSON) +{ + local int i, j; + local JObject rightObject; + local JProperty rightProperty; + local array nextProperties; + rightObject = JObject(rightJSON); + if (rightObject == none) return false; + for (i = 0; i < hashTable.length; i += 1) + { + nextProperties = hashTable[i].properties; + for (j = 0; j < nextProperties.length; j += 1) { + rightObject.FindProperty(nextProperties[j].name, rightProperty); + if (rightProperty.value.type == JSON_Undefined) { + return false; + } + if (!AreAtomsEqual(nextProperties[j].value, rightProperty.value)) { + return false; + } + } + } + return true; +} + defaultproperties { ABSOLUTE_LOWER_CAPACITY_LIMIT = 10 diff --git a/sources/Data/JSON/JSON.uc b/sources/Data/JSON/JSON.uc index 5642057..b5ccc37 100644 --- a/sources/Data/JSON/JSON.uc +++ b/sources/Data/JSON/JSON.uc @@ -76,6 +76,75 @@ struct JStorageAtom var protected bool classLoadingWasAttempted; }; +enum JComparisonResult +{ + JCR_Incomparable, + JCR_SubSet, + JCR_Overset, + JCR_Equal +}; + +public function bool IsSubsetOf(JSON rightJSON) +{ + return false; +} + +public final function JComparisonResult Compare(JSON rightJSON) +{ + local bool firstIsSubset, secondIsSubset; + if (rightJSON == none) return JCR_Incomparable; + firstIsSubset = IsSubsetOf(rightJSON); + secondIsSubset = rightJSON.IsSubsetOf(self); + if (firstIsSubset) + { + if (secondIsSubset) { + return JCR_Equal; + } + else { + return JCR_SubSet; + } + } + else { + if (secondIsSubset) { + return JCR_Overset; + } + else { + return JCR_Incomparable; + } + } +} + +public final function bool IsEqual(JSON rightJSON) +{ + return (Compare(rightJSON) == JCR_Equal); +} + +protected final function bool AreAtomsEqual( + JStorageAtom atom1, + JStorageAtom atom2) +{ + if (atom1.type != atom2.type) return false; + if (atom1.type == JSON_Undefined) return true; + if (atom1.type == JSON_Null) return true; + if (atom1.type == JSON_Number) { + return ( atom1.numberValue == atom2.numberValue + && atom1.numberValueAsInt == atom2.numberValueAsInt); + } + if (atom1.type == JSON_Boolean) { + return (atom1.booleanValue == atom2.booleanValue); + } + if (atom1.type == JSON_String) { + return (atom1.stringValue == atom2.stringValue); + } + if (atom1.complexValue == none && atom2.complexValue == none) { + return true; + } + if (atom1.complexValue == none || atom2.complexValue == none) { + return false; + } + return atom1.complexValue.IsEqual(atom2.complexValue); +} + protected final function TryLoadingStringAsClass(out JStorageAtom atom) { if (atom.classLoadingWasAttempted) return; diff --git a/sources/Data/JSON/Tests/TEST_JSON.uc b/sources/Data/JSON/Tests/TEST_JSON.uc index 827a3d3..b5464e8 100644 --- a/sources/Data/JSON/Tests/TEST_JSON.uc +++ b/sources/Data/JSON/Tests/TEST_JSON.uc @@ -28,6 +28,7 @@ protected static function TESTS() Test_ObjectGetSetRemove(); Test_ObjectKeys(); Test_ArrayGetSetRemove(); + Test_JSONComparison(); } protected static function Test_ObjectGetSetRemove() @@ -996,6 +997,118 @@ protected static function SubSubTest_ArraySetBooleanExpansions() TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined); } +protected static function JObject Prepare_FoldedObject() +{ + local JObject testObject; + testObject = _().json.NewObject(); + testObject.SetNumber("some_var", 7.32); + testObject.SetString("another_var", "aye!"); + testObject.CreateObject("innerObject"); + testObject.GetObject("innerObject").SetBoolean("my_bool", true) + .SetInteger("my_int", 9823452).CreateArray("array"); + testObject.GetObject("innerObject").GetArray("array").AddClass(class'Actor') + .AddBoolean(false).AddNull().AddObject().AddNumber(56.6); + testObject.GetObject("innerObject").GetArray("array").GetObject(3) + .SetString("something here", "yes").SetNumber("maybe", 0.003); + testObject.GetObject("innerObject").CreateObject("one more"); + testObject.GetObject("innerObject").GetObject("one more") + .SetString("o rly?", "ya rly").SetBoolean("whatever", false) + .SetNumber("nope", 324532); + return testObject; +} + +protected static function Test_JSONComparison() +{ + Context("Testing comparison of JSON objects"); + SubTest_JSONIsEqual(); + SubTest_JSONIsSubsetOf(); + SubTest_JSONCompare(); +} + +protected static function SubTest_JSONIsEqual() +{ + local JObject test1, test2, empty; + test1 = Prepare_FoldedObject(); + test2 = Prepare_FoldedObject(); + empty = _().json.NewObject(); + + Issue("`IsEqual()` does not recognize identical JSON objects as equal."); + TEST_ExpectTrue(test1.IsEqual(test1)); + TEST_ExpectTrue(test1.IsEqual(test2)); + TEST_ExpectTrue(empty.IsEqual(empty)); + + Issue("`IsEqual()` reports non-empty JSON object as equal to" + @ "an empty one."); + TEST_ExpectFalse(test1.IsEqual(empty)); + + Issue("`IsEqual()` reports JSON objects with identical variable names," + @ "but different values as equal."); + test2.GetObject("innerObject").GetObject("one more").SetNumber("nope", 2); + TEST_ExpectFalse(test1.IsEqual(test2)); + test2 = Prepare_FoldedObject(); + test2.GetObject("innerObject").GetArray("array").SetBoolean(1, true); + TEST_ExpectFalse(test1.IsEqual(test2)); + + Issue("`IsEqual()` reports JSON objects with different" + @ "structure as equal."); + test2 = Prepare_FoldedObject(); + test2.GetObject("innerObject").SetNumber("ahaha", 8); + TEST_ExpectFalse(test1.IsEqual(test2)); + test2 = Prepare_FoldedObject(); + test2.GetObject("innerObject").GetArray("array").AddNull(); + TEST_ExpectFalse(test1.IsEqual(test2)); +} + +protected static function SubTest_JSONIsSubsetOf() +{ + local JObject test1, test2, empty; + test1 = Prepare_FoldedObject(); + test2 = Prepare_FoldedObject(); + empty = _().json.NewObject(); + + Issue("`IsSubsetOf()` incorrectly handles equal objects."); + TEST_ExpectTrue(test1.IsSubsetOf(test1)); + TEST_ExpectTrue(test1.IsSubsetOf(test2)); + TEST_ExpectTrue(empty.IsSubsetOf(empty)); + + Issue("`IsSubsetOf()` incorrectly handles object subsets."); + test1.SetNumber("Garage", 234); + TEST_ExpectTrue(test2.IsSubsetOf(test1)); + TEST_ExpectFalse(test1.IsSubsetOf(test2)); + TEST_ExpectTrue(empty.IsSubsetOf(test1)); + TEST_ExpectFalse(test1.IsSubsetOf(empty)); + + Issue("`IsSubsetOf()` incorrectly handles objects that cannot be compared."); + test2.GetObject("innerObject").GetArray("array").SetNull(1); + TEST_ExpectFalse(test1.IsSubsetOf(test2)); + TEST_ExpectFalse(test2.IsSubsetOf(test1)); +} + +protected static function SubTest_JSONCompare() +{ + local JObject test1, test2, empty; + test1 = Prepare_FoldedObject(); + test2 = Prepare_FoldedObject(); + empty = _().json.NewObject(); + + Issue("`Compare()` incorrectly handles equal objects."); + TEST_ExpectTrue(test1.Compare(test1) == JCR_Equal); + TEST_ExpectTrue(test1.Compare(test2) == JCR_Equal); + TEST_ExpectTrue(empty.Compare(empty) == JCR_Equal); + + Issue("`Compare()` incorrectly handles object subsets."); + test1.SetNumber("Garage", 234); + TEST_ExpectTrue(test2.Compare(test1) == JCR_SubSet); + TEST_ExpectTrue(test1.Compare(test2) == JCR_Overset); + TEST_ExpectTrue(empty.Compare(test1) == JCR_SubSet); + TEST_ExpectTrue(test1.Compare(empty) == JCR_Overset); + + Issue("`Compare()` incorrectly handles objects that cannot be compared."); + test2.GetObject("innerObject").GetArray("array").AddNull(); + TEST_ExpectTrue(test1.Compare(test2) == JCR_Incomparable); + TEST_ExpectTrue(test2.Compare(test1) == JCR_Incomparable); +} + defaultproperties { caseName = "JSON"