Browse Source

Change `JObject` to use hash tables and classes

pull/8/head
Anton Tarasenko 4 years ago
parent
commit
c0b719a2f2
  1. 434
      sources/Data/JSON/JObject.uc
  2. 33
      sources/Data/JSON/JSON.uc
  3. 171
      sources/Data/JSON/Tests/TEST_JSON.uc

434
sources/Data/JSON/JObject.uc

@ -42,21 +42,170 @@ struct JProperty
var string name; var string name;
var JStorageAtom value; var JStorageAtom value;
}; };
var private array<JProperty> properties; // Bucket of alias-value pairs, with the same alias hash.
struct PropertyBucket
{
var array<JProperty> properties;
};
var private array<PropertyBucket> hashTable;
var private int storedElementCount;
// Reasonable lower and upper limits on hash table capacity,
// that will be enforced if user requires something outside those bounds
var private config const int MINIMUM_CAPACITY;
var private config const int MAXIMUM_CAPACITY;
var private config const float MINIMUM_DENSITY;
var private config const float MAXIMUM_DENSITY;
var private config const int MINIMUM_DIFFERENCE_FOR_REALLOCATION;
var private config const int ABSOLUTE_LOWER_CAPACITY_LIMIT;
// Helper method that is needed as a replacement for `%`, since it is
// an operation on `float`s in UnrealScript and does not have enough precision
// to work with hashes.
// Assumes positive input.
private function int Remainder(int number, int divisor)
{
local int quotient;
quotient = number / divisor;
return (number - quotient * divisor);
}
// Returns index of name-value pair in `properties` for a given name. // Finds indices for:
// Returns `-1` if such a pair does not exist. // 1. Bucked that contains specified alias (`bucketIndex`);
private final function int GetPropertyIndex(string name) // 2. Pair for specified alias in the bucket's collection
// (`propertyIndex`).
// `bucketIndex` is always found,
// `propertyIndex` is valid iff method returns `true`, otherwise it's equal to
// the index at which new property can get inserted.
private final function bool FindPropertyIndices(
string name,
out int bucketIndex,
out int propertyIndex)
{ {
local int i; local int i;
for (i = 0; i < properties.length; i += 1) local array<JProperty> bucketProperties;
TouchHashTable();
bucketIndex = _.text.GetHash(name);
if (bucketIndex < 0) {
bucketIndex *= -1;
}
bucketIndex = Remainder(bucketIndex, hashTable.length);
// Check if bucket actually has given name.
bucketProperties = hashTable[bucketIndex].properties;
for (i = 0; i < bucketProperties.length; i += 1)
{ {
if (name == properties[i].name) if (bucketProperties[i].name == name)
{ {
return i; propertyIndex = i;
return true;
}
}
propertyIndex = bucketProperties.length;
return false;
}
// Creates hash table in case it does not exist yet
private final function TouchHashTable()
{
if (hashTable.length <= 0) {
UpdateHashTableCapacity();
}
}
// Attempts to find a property in a caller `JObject` by the name `name` and
// writes it into `result`. Returns `true` if it succeeds and `false` otherwise
// (in that case writes a blank property with a given name in `result`).
private final function bool FindProperty(string name, out JProperty result)
{
local JProperty newProperty;
local int bucketIndex, propertyIndex;
if (FindPropertyIndices(name, bucketIndex, propertyIndex))
{
result = hashTable[bucketIndex].properties[propertyIndex];
return true;
}
newProperty.name = name;
result = newProperty;
return false;
}
// Creates/replaces a property with a name `newProperty.name` in caller
// JSON object
private final function UpdateProperty(JProperty newProperty)
{
local bool overriddenProperty;
local int bucketIndex, propertyIndex;
overriddenProperty = !FindPropertyIndices( newProperty.name,
bucketIndex, propertyIndex);
hashTable[bucketIndex].properties[propertyIndex] = newProperty;
if (overriddenProperty) {
storedElementCount += 1;
UpdateHashTableCapacity();
}
}
// Removes a property with a name `newProperty.name` from caller JSON object
// Returns `true` if something was actually removed.
private final function bool RemoveProperty(string propertyName)
{
local int bucketIndex, propertyIndex;
// Ensure has table was initialized before any updates
if (hashTable.length <= 0) {
UpdateHashTableCapacity();
}
if (FindPropertyIndices(propertyName, bucketIndex, propertyIndex)) {
hashTable[bucketIndex].properties.Remove(propertyIndex, 1);
storedElementCount = Max(0, storedElementCount - 1);
UpdateHashTableCapacity();
return true;
}
return false;
}
// Checks if we need to change our current capacity and does so if needed
private final function UpdateHashTableCapacity()
{
local int oldCapacity, newCapacity;
oldCapacity = hashTable.length;
// Calculate new capacity (and whether it is needed) based on amount of
// stored properties and current capacity
newCapacity = oldCapacity;
if (storedElementCount < newCapacity * MINIMUM_DENSITY) {
newCapacity /= 2;
}
if (storedElementCount > newCapacity * MAXIMUM_DENSITY) {
newCapacity *= 2;
}
// Enforce our limits
newCapacity = Clamp(newCapacity, MINIMUM_CAPACITY, MAXIMUM_CAPACITY);
newCapacity = Max(ABSOLUTE_LOWER_CAPACITY_LIMIT, newCapacity);
// Only resize if difference is huge enough or table does not exists yet
if ( newCapacity - oldCapacity > MINIMUM_DIFFERENCE_FOR_REALLOCATION
|| oldCapacity - newCapacity > MINIMUM_DIFFERENCE_FOR_REALLOCATION
|| oldCapacity <= 0) {
ResizeHashTable(newCapacity);
}
}
// Change size of the hash table, does not check any limits, does not check
// if `newCapacity` is a valid capacity (`newCapacity > 0`).
// Use `UpdateHashTableCapacity()` for that.
private final function ResizeHashTable(int newCapacity)
{
local int i, j;
local array<JProperty> bucketProperties;
local array<PropertyBucket> oldHashTable;
oldHashTable = hashTable;
// Clean current hash table
hashTable.length = 0;
hashTable.length = newCapacity;
for (i = 0; i < oldHashTable.length; i += 1)
{
bucketProperties = oldHashTable[i].properties;
for (j = 0; j < bucketProperties.length; j += 1) {
UpdateProperty(bucketProperties[j]);
} }
} }
return -1;
} }
// Returns `JType` of a variable with a given name in our properties. // Returns `JType` of a variable with a given name in our properties.
@ -65,11 +214,11 @@ private final function int GetPropertyIndex(string name)
// function will return `JSON_Undefined`. // function will return `JSON_Undefined`.
public final function JType GetTypeOf(string name) public final function JType GetTypeOf(string name)
{ {
local int index; local JProperty property;
index = GetPropertyIndex(name); FindProperty(name, property);
if (index < 0) return JSON_Undefined; // If we did not find anything - `property` will be set up as
// a `JSON_Undefined` type value.
return properties[index].value.type; return property.value.type;
} }
// Following functions are getters for various types of variables. // Following functions are getters for various types of variables.
@ -82,66 +231,93 @@ public final function JType GetTypeOf(string name)
// will simply return `none`. // will simply return `none`.
public final function float GetNumber(string name, optional float defaultValue) public final function float GetNumber(string name, optional float defaultValue)
{ {
local int index; local JProperty property;
index = GetPropertyIndex(name); FindProperty(name, property);
if (index < 0) return defaultValue; if (property.value.type != JSON_Number) {
if (properties[index].value.type != JSON_Number) return defaultValue; return defaultValue;
}
return property.value.numberValue;
}
return properties[index].value.numberValue; public final function int GetInteger(string name, optional int defaultValue)
{
local JProperty property;
FindProperty(name, property);
if (property.value.type != JSON_Number) {
return defaultValue;
}
return property.value.numberValueAsInt;
} }
public final function string GetString public final function string GetString(
( string name,
string name,
optional string defaultValue optional string defaultValue
) )
{ {
local int index; local JProperty property;
index = GetPropertyIndex(name); FindProperty(name, property);
if (index < 0) return defaultValue; if (property.value.type != JSON_String) {
if (properties[index].value.type != JSON_String) return defaultValue; return defaultValue;
}
return property.value.stringValue;
}
return properties[index].value.stringValue; public final function class<Object> GetClass(
string name,
optional class<Object> defaultValue
)
{
local JProperty property;
FindProperty(name, property);
if (property.value.type != JSON_String) {
return defaultValue;
}
if (!property.value.classLoadingWasAttempted)
{
property.value.classLoadingWasAttempted = true;
property.value.stringValueAsClass =
class<Object>(DynamicLoadObject( property.value.stringValue,
class'Class', true));
}
if (property.value.stringValueAsClass != none) {
return property.value.stringValueAsClass;
}
return defaultValue;
} }
public final function bool GetBoolean(string name, optional bool defaultValue) public final function bool GetBoolean(string name, optional bool defaultValue)
{ {
local int index; local JProperty property;
index = GetPropertyIndex(name); FindProperty(name, property);
if (index < 0) return defaultValue; if (property.value.type != JSON_Boolean) {
if (properties[index].value.type != JSON_Boolean) return defaultValue; return defaultValue;
}
return properties[index].value.booleanValue; return property.value.booleanValue;
} }
public final function bool IsNull(string name) public final function bool IsNull(string name)
{ {
local int index; local JProperty property;
index = GetPropertyIndex(name); FindProperty(name, property);
if (index < 0) return false; return (property.value.type == JSON_Null);
if (properties[index].value.type != JSON_Null) return false;
return (properties[index].value.type == JSON_Null);
} }
public final function JArray GetArray(string name) public final function JArray GetArray(string name)
{ {
local int index; local JProperty property;
index = GetPropertyIndex(name); FindProperty(name, property);
if (index < 0) return none; if (property.value.type != JSON_Array) {
if (properties[index].value.type != JSON_Array) return none; return none;
}
return JArray(properties[index].value.complexValue); return JArray(property.value.complexValue);
} }
public final function JObject GetObject(string name) public final function JObject GetObject(string name)
{ {
local int index; local JProperty property;
index = GetPropertyIndex(name); FindProperty(name, property);
if (index < 0) return none; if (property.value.type != JSON_Object) return none;
if (properties[index].value.type != JSON_Object) return none; return JObject(property.value.complexValue);
return JObject(properties[index].value.complexValue);
} }
// Following functions provide simple setters for boolean, string, number // Following functions provide simple setters for boolean, string, number
@ -150,65 +326,72 @@ public final function JObject GetObject(string name)
// `object.SetNumber("num1", 1).SetNumber("num2", 2);`. // `object.SetNumber("num1", 1).SetNumber("num2", 2);`.
public final function JObject SetNumber(string name, float value) public final function JObject SetNumber(string name, float value)
{ {
local int index; local JProperty property;
local JProperty newProperty; FindProperty(name, property);
index = GetPropertyIndex(name); property.value.type = JSON_Number;
if (index < 0) property.value.numberValue = value;
{ property.value.numberValueAsInt = int(value);
index = properties.length; property.value.complexValue = none;
} UpdateProperty(property);
return self;
}
newProperty.name = name; public final function JObject SetInteger(string name, int value)
newProperty.value.type = JSON_Number; {
newProperty.value.numberValue = value; local JProperty property;
properties[index] = newProperty; FindProperty(name, property);
property.value.type = JSON_Number;
property.value.numberValue = float(value);
property.value.numberValueAsInt = value;
property.value.complexValue = none;
UpdateProperty(property);
return self; return self;
} }
public final function JObject SetString(string name, string value) public final function JObject SetString(string name, string value)
{ {
local int index; local JProperty property;
local JProperty newProperty; FindProperty(name, property);
index = GetPropertyIndex(name); property.value.type = JSON_String;
if (index < 0) property.value.stringValue = value;
{ property.value.stringValueAsClass = none;
index = properties.length; property.value.classLoadingWasAttempted = false;
} property.value.complexValue = none;
newProperty.name = name; UpdateProperty(property);
newProperty.value.type = JSON_String; return self;
newProperty.value.stringValue = value; }
properties[index] = newProperty;
public final function JObject SetClass(string name, class<Object> value)
{
local JProperty property;
FindProperty(name, property);
property.value.type = JSON_String;
property.value.stringValue = string(value);
property.value.stringValueAsClass = value;
property.value.classLoadingWasAttempted = true;
property.value.complexValue = none;
UpdateProperty(property);
return self; return self;
} }
public final function JObject SetBoolean(string name, bool value) public final function JObject SetBoolean(string name, bool value)
{ {
local int index; local JProperty property;
local JProperty newProperty; FindProperty(name, property);
index = GetPropertyIndex(name); property.value.type = JSON_Boolean;
if (index < 0) property.value.booleanValue = value;
{ property.value.complexValue = none;
index = properties.length; UpdateProperty(property);
}
newProperty.name = name;
newProperty.value.type = JSON_Boolean;
newProperty.value.booleanValue = value;
properties[index] = newProperty;
return self; return self;
} }
public final function JObject SetNull(string name) public final function JObject SetNull(string name)
{ {
local int index; local JProperty property;
local JProperty newProperty; FindProperty(name, property);
index = GetPropertyIndex(name); property.value.type = JSON_Null;
if (index < 0) property.value.complexValue = none;
{ UpdateProperty(property);
index = properties.length;
}
newProperty.name = name;
newProperty.value.type = JSON_Null;
properties[index] = newProperty;
return self; return self;
} }
@ -218,48 +401,59 @@ public final function JObject SetNull(string name)
// `object.CreateObject("folded object").CreateArray("names list");`. // `object.CreateObject("folded object").CreateArray("names list");`.
public final function JObject CreateArray(string name) public final function JObject CreateArray(string name)
{ {
local int index; local JProperty property;
local JProperty newProperty; FindProperty(name, property);
index = GetPropertyIndex(name); if (property.value.complexValue != none) {
if (index < 0) property.value.complexValue.Destroy();
{
index = properties.length;
} }
newProperty.name = name; property.value.type = JSON_Array;
newProperty.value.type = JSON_Array; property.value.complexValue = _.json.NewArray();
newProperty.value.complexValue = _.json.newArray(); UpdateProperty(property);
properties[index] = newProperty;
return self; return self;
} }
public final function JObject CreateObject(string name) public final function JObject CreateObject(string name)
{ {
local int index; local JProperty property;
local JProperty newProperty; FindProperty(name, property);
index = GetPropertyIndex(name); if (property.value.complexValue != none) {
if (index < 0) property.value.complexValue.Destroy();
{
index = properties.length;
} }
newProperty.name = name; property.value.type = JSON_Object;
newProperty.value.type = JSON_Object; property.value.complexValue = _.json.NewObject();
newProperty.value.complexValue = _.json.newObject(); UpdateProperty(property);
properties[index] = newProperty;
return self; return self;
} }
// Removes values with a given name. // Removes values with a given name.
// Returns `true` if value was actually removed and `false` if it didn't exist. // Returns `true` if value was actually removed and `false` if it didn't exist.
public final function bool RemoveValue(string name) public final function JObject RemoveValue(string name)
{ {
local int index; RemoveProperty(name);
index = GetPropertyIndex(name); return self;
if (index < 0) return false; }
properties.Remove(index, 1); public final function array<string> GetKeys()
return true; {
local int i, j;
local array<string> result;
local array<JProperty> nextProperties;
for (i = 0; i < hashTable.length; i += 1)
{
nextProperties = hashTable[i].properties;
for (j = 0; j < nextProperties.length; j += 1) {
result[result.length] = nextProperties[j].name;
}
}
return result;
} }
defaultproperties defaultproperties
{ {
ABSOLUTE_LOWER_CAPACITY_LIMIT = 10
MINIMUM_CAPACITY = 50
MAXIMUM_CAPACITY = 100000
MINIMUM_DENSITY = 0.25
MAXIMUM_DENSITY = 0.75
MINIMUM_DIFFERENCE_FOR_REALLOCATION = 50
} }

33
sources/Data/JSON/JSON.uc

@ -58,27 +58,24 @@ struct JStorageAtom
{ {
// What type is stored exactly? // What type is stored exactly?
// Depending on that, uses one of the other fields as a storage. // Depending on that, uses one of the other fields as a storage.
var protected JType type; var protected JType type;
var protected float numberValue; var protected float numberValue;
var protected string stringValue; var protected string stringValue;
var protected bool booleanValue; var protected bool booleanValue;
// Used for storing both JSON objects and arrays. // Used for storing both JSON objects and arrays.
var protected JSON complexValue; var protected JSON complexValue;
// Numeric value might not fit into a `float` very well, so we will store
// them as both `float` and `integer` and allow user to request any version
// of them
var protected int numberValueAsInt;
// Some `string` values might be actually used to represent classes,
// so we will give users an ability to request `string` value as a class.
var protected class<Object> stringValueAsClass;
// To avoid several unsuccessful attempts to load `class` object from
// a `string`, we will record whether we've already tied that.
var protected bool classLoadingWasAttempted;
}; };
// TODO: Rewrite JSON object to use more efficient storage data structures
// that will support subtypes:
// ~ Number: byte, int, float
// ~ String: string, class
// (maybe move to auto generated code?).
// TODO: Add cleanup queue to efficiently and without crashes clean up
// removed objects.
// TODO: Add `JValue` - a reference type for number / string / boolean / null
// TODO: Add accessors for last values.
// TODO: Add path-getters.
// TODO: Add iterators.
// TODO: Add parsing/printing.
// TODO: Add functions for deep copy.
defaultproperties defaultproperties
{ {
} }

171
sources/Data/JSON/Tests/TEST_JSON.uc

@ -26,6 +26,7 @@ protected static function TESTS()
local JObject jsonData; local JObject jsonData;
jsonData = _().json.newObject(); jsonData = _().json.newObject();
Test_ObjectGetSetRemove(); Test_ObjectGetSetRemove();
Test_ObjectKeys();
Test_ArrayGetSetRemove(); Test_ArrayGetSetRemove();
} }
@ -33,8 +34,12 @@ protected static function Test_ObjectGetSetRemove()
{ {
SubTest_Undefined(); SubTest_Undefined();
SubTest_StringGetSetRemove(); SubTest_StringGetSetRemove();
SubTest_ClassGetSetRemove();
SubTest_StringAsClass();
SubTest_BooleanGetSetRemove(); SubTest_BooleanGetSetRemove();
SubTest_NumberGetSetRemove(); SubTest_NumberGetSetRemove();
SubTest_IntegerGetSetRemove();
SubTest_FloatAndInteger();
SubTest_NullGetSetRemove(); SubTest_NullGetSetRemove();
SubTest_MultipleVariablesGetSet(); SubTest_MultipleVariablesGetSet();
SubTest_Object(); SubTest_Object();
@ -141,6 +146,72 @@ protected static function SubTest_StringGetSetRemove()
TEST_ExpectTrue(testJSON.GetString("some_string", "other") == "other"); TEST_ExpectTrue(testJSON.GetString("some_string", "other") == "other");
} }
protected static function SubTest_ClassGetSetRemove()
{
local JObject testJSON;
testJSON = _().json.newObject();
testJSON.SetClass("info_class", class'Info');
Context("Testing `JObject`'s get/set/remove functions for" @
"class variables");
Issue("String type isn't properly set by `SetClass`");
TEST_ExpectTrue(testJSON.GetTypeOf("info_class") == JSON_String);
Issue("Value is incorrectly assigned by `SetClass`");
TEST_ExpectTrue(testJSON.GetClass("info_class") == class'Info');
Issue( "Providing default variable value makes 'GetClass'" @
"return wrong value");
TEST_ExpectTrue( testJSON.GetClass("info_class", class'Actor')
== class'Info');
Issue("Variable value isn't correctly reassigned by `SetClass`");
testJSON.SetClass("info_class", class'ReplicationInfo');
TEST_ExpectTrue(testJSON.GetClass("info_class") == class'ReplicationInfo');
Issue( "Getting class variable as a wrong type" @
"doesn't yield default value");
TEST_ExpectTrue(testJSON.GetBoolean("info_class", true) == true);
Issue("Class variable isn't being properly removed");
testJSON.RemoveValue("info_class");
TEST_ExpectTrue(testJSON.GetTypeOf("info_class") == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored class value, but got removed");
TEST_ExpectTrue( testJSON.GetClass("info_class", class'Actor')
== class'Actor');
}
protected static function SubTest_StringAsClass()
{
local JObject testJSON;
testJSON = _().json.newObject();
testJSON.SetString("SetString", "Engine.Actor");
testJSON.SetString("SetStringIncorrect", "blahblahblah");
testJSON.SetClass("SetClass", class'Info');
testJSON.SetClass("none", none);
Context("Testing how `JObject` treats mixed string and"
@ "class setters/getters.");
Issue("Incorrect result of `SetClass().GetString()` sequence.");
TEST_ExpectTrue(testJSON.GetString("SetClass") == "Engine.Info");
TEST_ExpectTrue(testJSON.GetString("none") == "None");
TEST_ExpectTrue(testJSON.GetString("none", "alternative") == "None");
Issue("Incorrect result of `SetString().GetClass()` sequence for"
@ "correct value in `SetString()`.");
TEST_ExpectTrue(testJSON.GetClass("SetString") == class'Actor');
TEST_ExpectTrue( testJSON.GetClass("SetString", class'Object')
== class'Actor');
Issue("Incorrect result of `SetString().GetClass()` sequence for"
@ "incorrect value in `SetString()`.");
TEST_ExpectTrue(testJSON.GetClass("SetStringIncorrect") == none);
TEST_ExpectTrue( testJSON.GetClass("SetStringIncorrect", class'Object')
== class'Object');
}
protected static function SubTest_NumberGetSetRemove() protected static function SubTest_NumberGetSetRemove()
{ {
local JObject testJSON; local JObject testJSON;
@ -148,7 +219,7 @@ protected static function SubTest_NumberGetSetRemove()
testJSON.SetNumber("some_number", 3.5); testJSON.SetNumber("some_number", 3.5);
Context("Testing `JObject`'s get/set/remove functions for" @ Context("Testing `JObject`'s get/set/remove functions for" @
"number variables"); "number variables as floats");
Issue("Number type isn't properly set by `SetNumber`"); Issue("Number type isn't properly set by `SetNumber`");
TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Number); TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Number);
@ -176,6 +247,64 @@ protected static function SubTest_NumberGetSetRemove()
TEST_ExpectTrue(testJSON.GetNumber("some_number", 13) == 13); TEST_ExpectTrue(testJSON.GetNumber("some_number", 13) == 13);
} }
protected static function SubTest_IntegerGetSetRemove()
{
local JObject testJSON;
testJSON = _().json.newObject();
testJSON.SetInteger("some_number", 33653);
Context("Testing `JObject`'s get/set/remove functions for" @
"number variables as integers");
Issue("Number type isn't properly set by `SetInteger`");
TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Number);
Issue("Value is incorrectly assigned by `SetInteger`");
TEST_ExpectTrue(testJSON.GetInteger("some_number") == 33653);
Issue( "Providing default variable value makes 'GetInteger'" @
"return wrong value");
TEST_ExpectTrue(testJSON.GetInteger("some_number", 5) == 33653);
Issue("Variable value isn't correctly reassigned by `SetInteger`");
testJSON.SetInteger("some_number", MaxInt);
TEST_ExpectTrue(testJSON.GetInteger("some_number") == MaxInt);
Issue( "Getting number variable as a wrong type" @
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetString("some_number", "default") == "default");
Issue("Number type isn't being properly removed");
testJSON.RemoveValue("some_number");
TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored number value, that got removed");
TEST_ExpectTrue(testJSON.GetInteger("some_number", -235) == -235);
}
protected static function SubTest_FloatAndInteger()
{
local JObject testJSON;
testJSON = _().json.newObject();
testJSON.SetNumber("SetNumber", 6.70087);
testJSON.SetInteger("SetInteger", 62478623874);
Context("Testing how `JObject` treats mixed float and"
@ "integer setters/getters.");
Issue("Incorrect result of `SetNumber().GetInteger()` sequence.");
TEST_ExpectTrue(testJSON.GetInteger("SetNumber") == 6);
testJSON.SetInteger("SetNumber", 11);
testJSON.SetNumber("SetInteger", 0.43);
Issue("SetNumber().SetInteger() for same variable name does not overwrite"
@ "initial number value.");
TEST_ExpectTrue(testJSON.GetNumber("SetNumber") == 11);
Issue("SetInteger().SetNumber() for same variable name does not overwrite"
@ "initial integer value.");
TEST_ExpectTrue(testJSON.GetInteger("SetInteger") == 0);
}
protected static function SubTest_NullGetSetRemove() protected static function SubTest_NullGetSetRemove()
{ {
local JObject testJSON; local JObject testJSON;
@ -284,6 +413,46 @@ protected static function SubTest_Object()
.GetString("in", "default") == "string inside"); .GetString("in", "default") == "string inside");
} }
protected static function Test_ObjectKeys()
{
local int i;
local bool varFound, clsFound, objFound;
local JObject testObject;
local array<string> keys;
testObject = _().json.newObject();
Context("Testing getting list of keys from the `JObject`.");
Issue("Just created `JObject` returns non-empty key list.");
TEST_ExpectTrue(testObject.GetKeys().length == 0);
Issue("`JObject` returns incorrect key list.");
keys = testObject.SetInteger("var", 7).SetClass("cls", class'Actor')
.CreateObject("obj").GetKeys();
TEST_ExpectTrue(keys.length == 3);
for (i = 0; i < keys.length; i += 1)
{
if (keys[i] == "var") { varFound = true; }
if (keys[i] == "cls") { clsFound = true; }
if (keys[i] == "obj") { objFound = true; }
}
TEST_ExpectTrue(varFound && clsFound && objFound);
Issue("`JObject` returns incorrect key list after removing an element.");
keys = testObject.RemoveValue("cls").GetKeys();
TEST_ExpectTrue(keys.length == 2);
varFound = false;
objFound = false;
for (i = 0; i < keys.length; i += 1)
{
if (keys[i] == "var") { varFound = true; }
if (keys[i] == "obj") { objFound = true; }
}
TEST_ExpectTrue(varFound && objFound);
Issue("`JObject` returns incorrect key list after removing all elements.");
keys = testObject.RemoveValue("var").RemoveValue("obj").GetKeys();
TEST_ExpectTrue(keys.length == 0);
}
protected static function SubTest_ArrayUndefined() protected static function SubTest_ArrayUndefined()
{ {
local JArray testJSON; local JArray testJSON;

Loading…
Cancel
Save