Browse Source

Document JSON API

pull/8/head
Anton Tarasenko 4 years ago
parent
commit
2fd74dc1f9
  1. 684
      sources/Data/JSON/JArray.uc
  2. 457
      sources/Data/JSON/JObject.uc
  3. 485
      sources/Data/JSON/JSON.uc
  4. 377
      sources/Data/JSON/Tests/TEST_JSON.uc

684
sources/Data/JSON/JArray.uc

@ -3,7 +3,20 @@
* Array stores ordered JSON values that can be referred by their index.
* It can contain any mix of JSON value types and cannot have any gaps,
* i.e. in array of length N, there must be a valid value for all indices
* from 0 to N-1.
* from 0 to N-1. Values of the array can beL
* ~ Boolean, string, null or number (float in this implementation) data;
* ~ Other JSON Arrays;
* ~ Other JSON objects (see `JObject` class).
*
* This implementation provides a variety of functionality,
* including parsing, displaying, getters and setters for JSON types that
* allow to freely set and fetch their values by index.
* JSON objects and arrays can be fetched by getters, but you cannot
* add existing object or array to another object. Instead one has to either
* clone existing object or create an empty one and then manually fill
* with data.
* This allows to avoid loop situations, where object is
* contained in itself.
* Copyright 2020 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
@ -23,11 +36,16 @@
*/
class JArray extends JSON;
// Data will simply be stored as an array of JSON values
// Data will be stored as an array of JSON values
var private array<JStorageAtom> data;
// Return type of value stored at a given index.
// Returns `JSON_Undefined` if and only if given index is out of bounds.
/**
* Returns type (`JType`) of a property with a index in our collection.
*
* @param index Index of the JSON value to get the type of.
* @return Type of the property at the index `index`.
* `JSON_Undefined` iff element at that index does not exist.
*/
public final function JType GetTypeOf(int index)
{
if (index < 0) return JSON_Undefined;
@ -36,22 +54,37 @@ public final function JType GetTypeOf(int index)
return data[index].type;
}
// Returns current length of this array.
/**
* Returns current length of the caller array.
*
* @return Length (amount of elements) in the caller array.
* Means that max index with recorded value is `GetLength() - 1`
* (min index is `0`).
*/
public final function int GetLength()
{
return data.length;
}
// Changes length of this array.
// In case of the increase - fills new indices with `null` values.
/**
* Changes length of the caller `JArray`.
*
* If length is decreased - variables that fit into new length will be
* preserved, others - erased.
* In case of the increase - sets values at new indices to "null".
*
* @param newLength New length of the caller `JArray`.
* Negative values will be treated as zero.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function SetLength(int newLength)
{
local int i;
local int oldLength;
newLength = Max(0, newLength);
oldLength = data.length;
data.length = newLength;
if (oldLength >= newLength)
{
if (oldLength >= newLength) {
return;
}
i = oldLength;
@ -62,14 +95,24 @@ public final function SetLength(int newLength)
}
}
// Following functions are getters for various types of variables.
// Getter for null value simply checks if it's null
// and returns true/false as a result.
// Getters for simple types (number, string, boolean) can have optional
// default value specified, that will be returned if requested variable
// doesn't exist or has a different type.
// Getters for object and array types don't take default values and
// will simply return `none`.
/**
* Gets the value (as a `float`) at the index `index`, assuming it has
* `JSON_Number` type.
*
* Forms a pair with `GetInteger()` method. JSON allows to specify
* arbitrary precision for the number variables, but UnrealScript can only
* store a limited range of numeric value.
* To alleviate this problem we store numeric JSON values as both
* `float` and `int` and can return either of the requested versions.
*
* @param index Index of the value to get;
* must be between 0 and `GetLength() - 1` inclusively.
* @param defaultValue Value to return if element does not exist or
* has a different type (can be checked by `GetTypeOf()`).
* @return Number value of the element at the index `index`,
* if it exists and has `JSON_Number` type.
* Otherwise returns passed `defaultValue`.
*/
public final function float GetNumber(int index, optional float defaultValue)
{
if (index < 0) return defaultValue;
@ -79,6 +122,24 @@ public final function float GetNumber(int index, optional float defaultValue)
return data[index].numberValue;
}
/**
* Gets the value (as an `int`) at the index `index`, assuming it has
* `JSON_Number` type.
*
* Forms a pair with `GetInteger()` method. JSON allows to specify
* arbitrary precision for the number variables, but UnrealScript can only
* store a limited range of numeric value.
* To alleviate this problem we store numeric JSON values as both
* `float` and `int` and can return either of the requested versions.
*
* @param index Index of the value to get;
* must be between 0 and `GetLength() - 1` inclusively.
* @param defaultValue Value to return if element does not exist or
* has a different type (can be checked by `GetTypeOf()`).
* @return Number value of the element at the index `index`,
* if it exists and has `JSON_Number` type.
* Otherwise returns passed `defaultValue`.
*/
public final function float GetInteger(int index, optional float defaultValue)
{
if (index < 0) return defaultValue;
@ -88,6 +149,19 @@ public final function float GetInteger(int index, optional float defaultValue)
return data[index].numberValueAsInt;
}
/**
* Gets the value at the index `index`, assuming it has `JSON_String` type.
*
* See also `GetClass()` method.
*
* @param index Index of the value to get;
* must be between 0 and `GetLength() - 1` inclusively.
* @param defaultValue Value to return if element does not exist or
* has a different type (can be checked by `GetTypeOf()`).
* @return String value of the element at the index `index`,
* if it exists and has `JSON_String` type.
* Otherwise returns passed `defaultValue`.
*/
public final function string GetString(int index, optional string defaultValue)
{
if (index < 0) return defaultValue;
@ -97,6 +171,24 @@ public final function string GetString(int index, optional string defaultValue)
return data[index].stringValue;
}
/**
* Gets the value at the index `index` as a `class`, assuming it has
* `JSON_String` type.
*
* JSON does not support to store class data type, but we can use string type
* for that. This method attempts to load a class object from it's full name,
* (like `Engine.Actor`) recorded inside an appropriate string value.
*
* @param index Index of the value to get;
* must be between 0 and `GetLength() - 1` inclusively.
* @param defaultValue Value to return if element does not exist,
* has a different type (can be checked by `GetTypeOf()`) or not
* a valid class name.
* @return Class value of the element at the index `index`,
* if it exists, has `JSON_String` type and it represents
* a full name of some class.
* Otherwise returns passed `defaultValue`.
*/
public final function class<Object> GetClass(
int index,
optional class<Object> defaultValue)
@ -112,6 +204,19 @@ public final function class<Object> GetClass(
return defaultValue;
}
/**
* Gets the value at the index `index`, assuming it has `JSON_Boolean` type.
*
* See also `GetClass()` method.
*
* @param index Index of the value to get;
* must be between 0 and `GetLength() - 1` inclusively.
* @param defaultValue Value to return if property does not exist or
* has a different type (can be checked by `GetTypeOf()`).
* @return String value of the element at the index `index`,
* if it exists and has `JSON_Boolean` type.
* Otherwise returns passed `defaultValue`.
*/
public final function bool GetBoolean(int index, optional bool defaultValue)
{
if (index < 0) return defaultValue;
@ -121,6 +226,15 @@ public final function bool GetBoolean(int index, optional bool defaultValue)
return data[index].booleanValue;
}
/**
* Checks if an array element at the index `index` has `JSON_Null` type.
*
* Alternatively consider using `GetType()` method.
*
* @param index Index of the element to check for being `null`.
* @return `true` if element at the given index exists and
* has type `JSON_Null`; `false` otherwise.
*/
public final function bool IsNull(int index)
{
if (index < 0) return false;
@ -129,6 +243,15 @@ public final function bool IsNull(int index)
return (data[index].type == JSON_Null);
}
/**
* Gets the value at the index `index`, assuming it has `JSON_Array` type.
*
* @param index Index of the value to check for being "null";
* must be between 0 and `GetLength() - 1` inclusively.
* @return `JArray` object value at the given index, if it exists and
* has `JSON_Array` type.
* Otherwise returns `none`.
*/
public final function JArray GetArray(int index)
{
if (index < 0) return none;
@ -138,6 +261,15 @@ public final function JArray GetArray(int index)
return JArray(data[index].complexValue);
}
/**
* Gets the value at the index `index`, assuming it has `JSON_Object` type.
*
* @param index Index of the value to check for being "null";
* must be between 0 and `GetLength() - 1` inclusively.
* @return `JObject` object value at the given index, if it exists and
* has `JSON_Array` type.
* Otherwise returns `none`.
*/
public final function JObject GetObject(int index)
{
if (index < 0) return none;
@ -147,34 +279,35 @@ public final function JObject GetObject(int index)
return JObject(data[index].complexValue);
}
// Following functions provide simple setters for boolean, string, number
// and null values.
// If passed index is negative - does nothing.
// If index lies beyond array length (`>= GetLength()`), -
// these functions will expand array in the same way as `GetLength()` function.
// This can be prevented by setting optional parameter `preventExpansion` to
// `false` (nothing will be done in this case).
// They return object itself, allowing user to chain calls like this:
// `array.SetNumber("num1", 1).SetNumber("num2", 2);`.
public final function JArray SetNumber(
int index,
float value,
optional bool preventExpansion
)
/**
* Sets the number value (as `float`) at the index `index`, erasing previous
* value (if it was recorded).
*
* If negative index is given - does nothing.
* If given index is too large (`>= GetLength()`) then array will be
* extended, setting values at new indices (except specified `index`)
* to "null" value (`JSON_Null`).
*
* Forms a pair with `SetInteger()` method.
* While JSON standard allows to store numbers with arbitrary precision,
* UnrealScript's types have a limited range.
* To alleviate this problem we store numbers in both `float`- and
* `int`-type variables to extended supported range of values.
* So if you need to store a number with fractional part, you should
* prefer `SetNumber()` and for integer values `SetInteger()` is preferable.
* Both will record a value of type `JSON_Number`.
*
* @param index Index at which to set given numeric value.
* @param value Value to set at given index.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray SetNumber(int index, float value)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
if (index >= data.length) {
SetLength(index + 1);
}
newStorageValue.type = JSON_Number;
newStorageValue.numberValue = value;
@ -184,25 +317,35 @@ public final function JArray SetNumber(
return self;
}
public final function JArray SetInteger(
int index,
int value,
optional bool preventExpansion
)
/**
* Sets the number value (as `int`) at the index `index`, erasing previous
* value (if it was recorded).
*
* If negative index is given - does nothing.
* If given index is too large (`>= GetLength()`) then array will be
* extended, setting values at new indices (except specified `index`)
* to "null" value (`JSON_Null`).
*
* Forms a pair with `SetNumber()` method.
* While JSON standard allows to store numbers with arbitrary precision,
* UnrealScript's types have a limited range.
* To alleviate this problem we store numbers in both `float`- and
* `int`-type variables to extended supported range of values.
* So if you need to store a number with fractional part, you should
* prefer `SetNumber()` and for integer values `SetInteger()` is preferable.
* Both will record a value of type `JSON_Number`.
*
* @param index Index at which to set given numeric value.
* @param value Value to set at given index.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray SetInteger(int index, int value)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
if (index >= data.length) {
SetLength(index + 1);
}
newStorageValue.type = JSON_Number;
newStorageValue.numberValue = float(value);
@ -212,26 +355,28 @@ public final function JArray SetInteger(
return self;
}
public final function JArray SetString
(
int index,
string value,
optional bool preventExpansion
)
/**
* Sets the string value at the given index `index`, erasing previous value
* (if it was recorded).
*
* If negative index is given - does nothing.
* If given index is too large (`>= GetLength()`) then array will be
* extended, setting values at new indices (except specified `index`)
* to "null" value (`JSON_Null`).
*
* Also see `SetClass()` method.
*
* @param index Index at which to set given string value.
* @param value Value to set at given index.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray SetString(int index, string value)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
if (index >= data.length) {
SetLength(index + 1);
}
newStorageValue.type = JSON_String;
newStorageValue.stringValue = value;
@ -239,25 +384,33 @@ public final function JArray SetString
return self;
}
public final function JArray SetClass(
int index,
class<Object> value,
optional bool preventExpansion
)
/**
* Sets the string value, corresponding to a given class `value`,
* at the index `index`, erasing previous value (if it was recorded).
*
* Value in question will have `JSON_String` type.
*
* If negative index is given - does nothing.
* If given index is too large (`>= GetLength()`) then array will be
* extended, setting values at new indices (except specified `index`)
* to "null" value (`JSON_Null`).
*
* We want to allow storing `class` data in our JSON containers, but JSON
* standard does not support such a type, so we have to use string type
* to store `class`' name instead.
* Also see `GetClass()` method`.
*
* @param index Index at which to set given value.
* @param value Value to set at given index.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray SetClass(int index, class<Object> value)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
if (index >= data.length) {
SetLength(index + 1);
}
newStorageValue.type = JSON_String;
newStorageValue.stringValue = string(value);
@ -266,26 +419,26 @@ public final function JArray SetClass(
return self;
}
public final function JArray SetBoolean
(
int index,
bool value,
optional bool preventExpansion
)
/**
* Sets the boolean value at the given index `index`, erasing previous value
* (if it was recorded).
*
* If negative index is given - does nothing.
* If given index is too large (`>= GetLength()`) then array will be
* extended, setting values at new indices (except specified `index`)
* to "null" value (`JSON_Null`).
*
* @param index Index at which to set given boolean value.
* @param value Value to set at given index.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray SetBoolean(int index, bool value)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
if (index >= data.length) {
SetLength(index + 1);
}
newStorageValue.type = JSON_Boolean;
newStorageValue.booleanValue = value;
@ -293,51 +446,58 @@ public final function JArray SetBoolean
return self;
}
public final function JArray SetNull
(
int index,
optional bool preventExpansion
)
/**
* Sets the value at the given index `index` to be "null" (`JSON_Null`),
* erasing previous value (if it was recorded).
*
* If negative index is given - does nothing.
* If given index is too large (`>= GetLength()`) then array will be
* extended, setting values at new indices (except specified `index`)
* to "null" value (`JSON_Null`).
*
* @param index Index at which to set "null" value to.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray SetNull(int index)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
if (index >= data.length) {
SetLength(index + 1);
}
newStorageValue.type = JSON_Null;
data[index] = newStorageValue;
return self;
}
public final function JArray SetArray(
int index,
JArray template,
optional bool preventExpansion
)
/**
* Sets the value at the given index `index` to store `JArray` object
* (JSON array type).
*
* If negative index is given - does nothing.
* If given index is too large (`>= GetLength()`) then array will be
* extended, setting values at new indices (except specified `index`)
* to "null" value (`JSON_Null`).
*
* NOTE: This method DOES NOT make caller `JArray` store a
* given reference, instead it clones it (see `Clone()`) into a new copy and
* stores that. This is made this way to ensure you can not, say, store
* an object in itself or it's children.
* See also `CreateArray()` method.
*
* @param index Index at which to set given array value.
* @param template Template `JArray` to clone.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray SetArray(int index, JArray template)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (template == none) return self;
if (index < 0) return self;
if (template == none) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
if (index >= data.length) {
SetLength(index + 1);
}
newStorageValue.type = JSON_Array;
newStorageValue.complexValue = template.Clone();
@ -345,26 +505,33 @@ public final function JArray SetArray(
return self;
}
public final function JArray SetObject(
int index,
JObject template,
optional bool preventExpansion
)
/**
* Sets the value at the given index `index` to store `JObject` object
* (JSON object type).
*
* If negative index is given - does nothing.
* If given index is too large (`>= GetLength()`) then array will be
* extended, setting values at new indices (except specified `index`)
* to "null" value (`JSON_Null`).
*
* NOTE: This method DOES NOT make caller `JArray` store a
* given reference, instead it clones it (see `Clone()`) into a new copy and
* stores that. This is made this way to ensure you can not, say, store
* an object in itself or it's children.
* See also `CreateObject()` method.
*
* @param index Index at which to set given array value.
* @param template Template `JObject` to clone.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray SetObject(int index, JObject template)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (template == none) return self;
if (index < 0) return self;
if (template == none) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
if (index >= data.length) {
SetLength(index + 1);
}
newStorageValue.type = JSON_Object;
newStorageValue.complexValue = template.Clone();
@ -372,34 +539,27 @@ public final function JArray SetObject(
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.
// If index lies beyond array length (`>= GetLength()`), -
// these functions will expand array in the same way as `GetLength()` function.
// This can be prevented by setting optional parameter `preventExpansion` to
// `false` (nothing will be done in this case).
// They return object itself, allowing user to chain calls like this:
// `array.CreateObject("sub object").CreateArray("sub array");`.
public final function JArray CreateArray
(
int index,
optional bool preventExpansion
)
/**
* Sets the value oat the given index `index` to store a new
* `JArray` object (JSON array type).
*
* See also `SetArray()` method.
*
* If negative index is given - does nothing.
* If given index is too large (`>= GetLength()`) then array will be
* extended, setting values at new indices (except specified `index`)
* to "null" value (`JSON_Null`).
*
* @param index Index at which to create a new `JArray`.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray CreateArray(int index)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
if (index >= data.length) {
SetLength(index + 1);
}
newStorageValue.type = JSON_Array;
newStorageValue.complexValue = _.json.newArray();
@ -407,25 +567,27 @@ public final function JArray CreateArray
return self;
}
public final function JArray CreateObject
(
int index,
optional bool preventExpansion
)
/**
* Sets the value oat the given index `index` to store a new
* `JObject` object (JSON object type).
*
* See also `SetObject()` method.
*
* If negative index is given - does nothing.
* If given index is too large (`>= GetLength()`) then array will be
* extended, setting values at new indices (except specified `index`)
* to "null" value (`JSON_Null`).
*
* @param index Index at which to create a new `JObject`.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray CreateObject(int index)
{
local JStorageAtom newStorageValue;
if (index < 0) return self;
if (index >= data.length)
{
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
if (index >= data.length) {
SetLength(index + 1);
}
newStorageValue.type = JSON_Object;
newStorageValue.complexValue = _.json.newObject();
@ -433,43 +595,116 @@ public final function JArray CreateObject
return self;
}
// Wrappers for setter functions that don't take index or
// `preventExpansion` parameters and add/create value at the end of the array.
/**
* Appends numeric value (as `float`) at the end of the caller `JArray`.
*
* Forms a pair with `AddInteger()` method.
* While JSON standard allows to store numbers with arbitrary precision,
* UnrealScript's types have a limited range.
* To alleviate this problem we store numbers in both `float`- and
* `int`-type variables to extended supported range of values.
* So if you need to store a number with fractional part, you should
* prefer `AddNumber()` and for integer values `AddInteger()` is preferable.
* Both will record a value of type `JSON_Number`.
*
* @param value Numeric value to append.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray AddNumber(float value)
{
return SetNumber(data.length, value);
}
/**
* Appends numeric value (as `int`) at the end of the caller `JArray`.
*
* Forms a pair with `AddNumber()` method.
* While JSON standard allows to store numbers with arbitrary precision,
* UnrealScript's types have a limited range.
* To alleviate this problem we store numbers in both `float`- and
* `int`-type variables to extended supported range of values.
* So if you need to store a number with fractional part, you should
* prefer `AddNumber()` and for integer values `AddInteger()` is preferable.
* Both will record a value of type `JSON_Number`.
*
* @param value Numeric value to append.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray AddInteger(int value)
{
return SetInteger(data.length, value);
}
/**
* Appends string value at the end of the caller `JArray`.
*
* Also see `AddClass()` method.
*
* @param value String value to append.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray AddString(string value)
{
return SetString(data.length, value);
}
/**
* Appends string value, corresponding to a given class `value`, at the end of
* the caller `JArray`.
*
* Value in question will have `JSON_String` type.
*
* We want to allow storing `class` data in our JSON containers, but JSON
* standard does not support such a type, so we have to use string type
* to store `class`' name instead.
* Also see `GetClass()` method`.
*
* @param value Class value to append.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray AddClass(class<Object> value)
{
return SetClass(data.length, value);
}
/**
* Appends boolean value at the end of the caller `JArray`.
*
* @param value Boolean value to append.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray AddBoolean(bool value)
{
return SetBoolean(data.length, value);
}
/**
* Appends "null" value at the end of the caller `JArray`.
*
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray AddNull()
{
return SetNull(data.length);
}
/**
* Appends new empty `JArray` (JSON array type) at the end of
* the caller `JArray`.
*
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray AddArray()
{
return CreateArray(data.length);
}
/**
* Appends new empty `JObject` (JSON object type) at the end of
* the caller `JArray`.
*
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JArray AddObject()
{
return CreateObject(data.length);
@ -479,6 +714,16 @@ public final function JArray AddObject()
// a given index.
// If `index` falls outside array boundaries - nothing will be done.
// Returns `true` if value was actually removed and `false` if it didn't exist.
/**
* Removes up to `amount` (minimum of `1`) of values, starting from
* a given index `index`.
*
* If `index` falls outside array boundaries - nothing will be done.
*
* @param index Index of first value to remove.
* @param amount Amount of values to remove.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function bool RemoveValue(int index, optional int amount)
{
local int i;
@ -497,11 +742,21 @@ public final function bool RemoveValue(int index, optional int amount)
return true;
}
/**
* Completely clears caller `JObject` of all values.
*/
public function Clear()
{
RemoveValue(0, data.length);
}
/**
* Checks if caller JSON container's values form a subset of
* `rightJSON`'s values.
*
* @return `true` if caller ("left") object is a subset of `rightJSON`
* and `false` otherwise.
*/
public function bool IsSubsetOf(JSON rightValue)
{
local int i;
@ -520,6 +775,12 @@ public function bool IsSubsetOf(JSON rightValue)
return true;
}
/**
* Makes an exact copy of the caller `JArray`
*
* @return Copy of the caller `JArray`. Guaranteed to be `JArray`
* (or `none`, if appropriate object could not be created).
*/
public function JSON Clone()
{
local int i;
@ -545,6 +806,31 @@ public function JSON Clone()
return clonedArray;
}
/**
* Uses given parser to parse a new array of values (append them to the end of
* the caller array) inside the caller `JArray`.
*
* Only adds new values if parsing the whole array was successful,
* otherwise even successfully parsed properties will be discarded.
*
* `parser` must point at the text describing a JSON array in
* a valid notation. Then it parses that container inside memory, but
* instead of creating it as a separate entity, adds it's values to
* the caller `JArray`.
*
* This method does not try to validate passed JSON and can accept invalid
* JSON by making some assumptions, but it is an undefined behavior and
* one should not expect it.
* Method is only guaranteed to work on valid JSON.
*
* @param parser Parser that method would use to parse `JArray` from
* wherever it left. It's confirmed will not be changed, but if parsing
* was successful, - it will point at the next available character.
* Do not treat `parser` being in a non-failed state as a confirmation of
* successful parsing: JSON parsing might fail regardless.
* Check return value for that.
* @return `true` if parsing was successful and `false` otherwise.
*/
public function bool ParseIntoSelfWith(Parser parser)
{
local bool parsingSucceeded;
@ -586,6 +872,8 @@ public function bool ParseIntoSelfWith(Parser parser)
return parsingSucceeded;
}
// Either cleans up or adds a list of parsed values,
// depending on whether parsing was successful or not.
private function HandleParsedAtoms(
array<JStorageAtom> parsedAtoms,
bool parsingSucceeded)
@ -606,6 +894,16 @@ private function HandleParsedAtoms(
}
}
/**
* Displays caller `JArray` with a provided preset.
*
* See `Display()` for a simpler to use method.
*
* @param displaySettings Struct that describes precisely how to display
* caller `JArray`. Can be used to emulate `Display()` call.
* @return String representation of caller JSON container in format defined by
* `displaySettings`.
*/
public function string DisplayWith(JSONDisplaySettings displaySettings)
{
local int i;

457
sources/Data/JSON/JObject.uc

@ -1,22 +1,19 @@
/**
* This class implements JSON object storage capabilities.
* Whenever one wants to store JSON data, they need to define such object.
* It stores name-value pairs, where names are strings and values can be:
* ~ Boolean, string, null or number (float in this implementation) data;
* ~ Other JSON objects;
* ~ JSON Arrays (see `JArray` class).
*
* This implementation provides getters and setters for boolean, string,
* null or number types that allow to freely set and fetch their values
* by name.
* This implementation provides a variety of functionality,
* including parsing, displaying, getters and setters for JSON types that
* allow to freely set and fetch their values by name.
* JSON objects and arrays can be fetched by getters, but you cannot
* add existing object or array to another object. Instead one has to create
* a new, empty object with a certain name and then fill it with data.
* This allows to avoid loop situations, where object is contained in itself.
* Functions to remove existing values are also provided and are applicable
* to all variable types.
* Setters can also be used to overwrite any value by a different value,
* even of a different type.
* add existing object or array to another object. Instead one has to either
* clone existing object or create an empty one and then manually fill
* with data.
* This allows to avoid loop situations, where object is
* contained in itself.
* Copyright 2020 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
@ -54,9 +51,19 @@ var private int storedElementCount;
// 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;
// Minimum and maximum allowed density of elements
// (`storedElementCount / hashTable.length`).
// If density falls outside this range, - we have to resize hash table to
// get into (MINIMUM_DENSITY; MAXIMUM_DENSITY) bounds,
// as long as it does not violate other restrictions.
var private config const float MINIMUM_DENSITY;
var private config const float MAXIMUM_DENSITY;
// Only ever reallocate hash table if new size will differ by
// at least that much, regardless of other restrictions.
var private config const int MINIMUM_DIFFERENCE_FOR_REALLOCATION;
// Never use any hash table capacity below this limit,
// regardless of other variables
// (like `MINIMUM_CAPACITY` or `MINIMUM_DENSITY`).
var private config const int ABSOLUTE_LOWER_CAPACITY_LIMIT;
// Helper method that is needed as a replacement for `%`, since it is
@ -213,10 +220,17 @@ private final function ResizeHashTable(int newCapacity)
}
}
// Returns `JType` of a variable with a given name in our properties.
// This function can be used to check if certain variable exists
// in this object, since if such variable does not exist -
// function will return `JSON_Undefined`.
/**
* Returns `JType` of a property with a given name in our collection.
*
* This function can be used to check if certain variable exists
* in this object, since if such variable does not exist -
* function will return `JSON_Undefined`.
*
* @param name Name of the property to get the type of.
* @return Type of the property by the name `name`.
* `JSON_Undefined` iff property with that name does not exist.
*/
public final function JType GetTypeOf(string name)
{
local JProperty property;
@ -226,14 +240,23 @@ public final function JType GetTypeOf(string name)
return property.value.type;
}
// Following functions are getters for various types of variables.
// Getter for null value simply checks if it's null
// and returns true/false as a result.
// Getters for simple types (number, string, boolean) can have optional
// default value specified, that will be returned if requested variable
// doesn't exist or has a different type.
// Getters for object and array types don't take default values and
// will simply return `none`.
/**
* Gets the value (as a `float`) of a property by the name `name`,
* assuming it has `JSON_Number` type.
*
* Forms a pair with `GetInteger()` method. JSON allows to specify
* arbitrary precision for the number variables, but UnrealScript can only
* store a limited range of numeric value.
* To alleviate this problem we store numeric JSON values as both
* `float` and `int` and can return either of the requested versions.
*
* @param name Name of the property to return a value of.
* @param defaultValue Value to return if property does not exist or
* has a different type (can be checked by `GetTypeOf()`).
* @return Number value of the property under name `name`,
* if it exists and has `JSON_Number` type.
* Otherwise returns passed `defaultValue`.
*/
public final function float GetNumber(string name, optional float defaultValue)
{
local JProperty property;
@ -244,6 +267,23 @@ public final function float GetNumber(string name, optional float defaultValue)
return property.value.numberValue;
}
/**
* Gets the value (as an `int`) of a property by the name `name`,
* assuming it has `JSON_Number` type.
*
* Forms a pair with `GetNumber()` method. JSON allows to specify
* arbitrary precision for the number variables, but UnrealScript can only
* store a limited range of numeric value.
* To alleviate this problem we store numeric JSON values as both
* `float` and `int` and can return either of the requested versions.
*
* @param name Name of the property to return a value of.
* @param defaultValue Value to return if property does not exist or
* has a different type (can be checked by `GetTypeOf()`).
* @return Number value of the property under name `name`,
* if it exists and has `JSON_Number` type.
* Otherwise returns passed `defaultValue`.
*/
public final function int GetInteger(string name, optional int defaultValue)
{
local JProperty property;
@ -254,6 +294,19 @@ public final function int GetInteger(string name, optional int defaultValue)
return property.value.numberValueAsInt;
}
/**
* Gets the value of a property by the name `name`,
* assuming it has `JSON_String` type.
*
* See also `GetClass()` method.
*
* @param name Name of the property to return a value of.
* @param defaultValue Value to return if property does not exist or
* has a different type (can be checked by `GetTypeOf()`).
* @return String value of the property under name `name`,
* if it exists and has `JSON_String` type.
* Otherwise returns passed `defaultValue`.
*/
public final function string GetString(
string name,
optional string defaultValue
@ -267,6 +320,23 @@ public final function string GetString(
return property.value.stringValue;
}
/**
* Gets the value of a property by the name `name` as a `class`,
* assuming it has `JSON_String` type.
*
* JSON does not support to store class data type, but we can use string type
* for that. This method attempts to load a class object from it's full name,
* (like `Engine.Actor`) recorded inside an appropriate string value.
*
* @param name Name of the property to return a value of.
* @param defaultValue Value to return if property does not exist,
* has a different type (can be checked by `GetTypeOf()`) or not
* a valid class name.
* @return Class value of the property under name `name`,
* if it exists, has `JSON_String` type and it represents
* a full name of some class.
* Otherwise returns passed `defaultValue`.
*/
public final function class<Object> GetClass(
string name,
optional class<Object> defaultValue
@ -284,6 +354,17 @@ public final function class<Object> GetClass(
return defaultValue;
}
/**
* Gets the value of a property by the name `name`,
* assuming it has `JSON_Boolean` type.
*
* @param name Name of the property to return a value of.
* @param defaultValue Value to return if property does not exist or
* has a different type (can be checked by `GetTypeOf()`).
* @return Boolean value of the property under name `name`,
* if it exists and has `JSON_Boolean` type.
* Otherwise returns passed `defaultValue`.
*/
public final function bool GetBoolean(string name, optional bool defaultValue)
{
local JProperty property;
@ -294,6 +375,15 @@ public final function bool GetBoolean(string name, optional bool defaultValue)
return property.value.booleanValue;
}
/**
* Checks if a property by the name `name` has `JSON_Null` type.
*
* Alternatively consider using `GetType()` method.
*
* @param name Name of the property to check for being `null`.
* @return `true` if property under given name `name` exists and
* has type `JSON_Null`; `false` otherwise.
*/
public final function bool IsNull(string name)
{
local JProperty property;
@ -301,6 +391,15 @@ public final function bool IsNull(string name)
return (property.value.type == JSON_Null);
}
/**
* Gets the value of a property by the name `name`,
* assuming it has `JSON_Array` type.
*
* @param name Name of the property to return a value of.
* @return `JArray` object value of the property under name `name`,
* if it exists and has `JSON_Array` type.
* Otherwise returns `none`.
*/
public final function JArray GetArray(string name)
{
local JProperty property;
@ -311,6 +410,15 @@ public final function JArray GetArray(string name)
return JArray(property.value.complexValue);
}
/**
* Gets the value of a property by the name `name`,
* assuming it has `JSON_Object` type.
*
* @param name Name of the property to return a value of.
* @return `JObject` object value of the property under name `name`,
* if it exists and has `JSON_Object` type.
* Otherwise returns `none`.
*/
public final function JObject GetObject(string name)
{
local JProperty property;
@ -319,10 +427,25 @@ public final function JObject GetObject(string name)
return JObject(property.value.complexValue);
}
// Following functions provide simple setters for boolean, string, number
// and null values.
// They return object itself, allowing user to chain calls like this:
// `object.SetNumber("num1", 1).SetNumber("num2", 2);`.
/**
* Sets the number value (as `float`) of a property by the name `name`,
* erasing previous value (if it was recorded).
*
* Property in question will have `JSON_Number` type.
*
* Forms a pair with `SetInteger()` method.
* While JSON standard allows to store numbers with arbitrary precision,
* UnrealScript's types have a limited range.
* To alleviate this problem we store numbers in both `float`- and
* `int`-type variables to extended supported range of values.
* So if you need to store a number with fractional part, you should
* prefer `SetNumber()` and for integer values `SetInteger()` is preferable.
* Both will create a property of type `JSON_Number`.
*
* @param name Name of the property to set a value of.
* @param value Value to set to a property under a given name `name`.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject SetNumber(string name, float value)
{
local JProperty property;
@ -336,6 +459,25 @@ public final function JObject SetNumber(string name, float value)
return self;
}
/**
* Sets the number value (as `int`) of a property by the name `name`,
* erasing previous value (if it was recorded).
*
* Property in question will have `JSON_Number` type.
*
* Forms a pair with `SetNumber()` method.
* While JSON standard allows to store numbers with arbitrary precision,
* UnrealScript's types have a limited range.
* To alleviate this problem we store numbers in both `float`- and
* `int`-type variables to extended supported range of values.
* So if you need to store a number with fractional part, you should
* prefer `SetNumber()` and for integer values `SetInteger()` is preferable.
* Both will create a property of type `JSON_Number`.
*
* @param name Name of the property to set a value of.
* @param value Value to set to a property under a given name `name`.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject SetInteger(string name, int value)
{
local JProperty property;
@ -349,6 +491,18 @@ public final function JObject SetInteger(string name, int value)
return self;
}
/**
* Sets the string value of a property by the name `name`,
* erasing previous value (if it was recorded).
*
* Property in question will have `JSON_String` type.
*
* Also see `SetClass()` method.
*
* @param name Name of the property to set a value of.
* @param value Value to set to a property under a given name `name`.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject SetString(string name, string value)
{
local JProperty property;
@ -362,6 +516,22 @@ public final function JObject SetString(string name, string value)
return self;
}
/**
* Sets the string value, corresponding to a given class `value`,
* of a property by the name `name`, erasing previous value
* (if it was recorded).
*
* Property in question will have `JSON_String` type.
*
* We want to allow storing `class` data in our JSON containers, but JSON
* standard does not support such a type, so we have to use string type
* to store `class`' name instead.
* Also see `GetClass()` method`.
*
* @param name Name of the property to set a value of.
* @param value Value to set to a property under a given name `name`.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject SetClass(string name, class<Object> value)
{
local JProperty property;
@ -375,6 +545,16 @@ public final function JObject SetClass(string name, class<Object> value)
return self;
}
/**
* Sets the boolean value of a property by the name `name`,
* erasing previous value (if it was recorded).
*
* Property in question will have `JSON_Boolean` type.
*
* @param name Name of the property to set a value of.
* @param value Value to set to a property under a given name `name`.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject SetBoolean(string name, bool value)
{
local JProperty property;
@ -386,6 +566,15 @@ public final function JObject SetBoolean(string name, bool value)
return self;
}
/**
* Sets the value of a property by the name `name` to be "null" (`JSON_Null`),
* erasing previous value (if it was recorded).
*
* Property in question will have `JSON_Null` type.
*
* @param name Name of the property to set a "null" value to.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject SetNull(string name)
{
local JProperty property;
@ -396,6 +585,20 @@ public final function JObject SetNull(string name)
return self;
}
/**
* Sets the value of a property by the name `name` to store `JArray` object
* (JSON array type).
*
* NOTE: This method DOES NOT make caller `JObject` store a
* given reference, instead it clones it (see `Clone()`) into a new copy and
* stores that. This is made this way to ensure you can not, say, store
* an object in itself or it's children.
* See also `CreateArray()` method.
*
* @param name Name of the property to return a value of.
* @param template Template `JArray` to clone into property.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject SetArray(string name, JArray template)
{
local JProperty property;
@ -410,6 +613,20 @@ public final function JObject SetArray(string name, JArray template)
return self;
}
/**
* Sets the value of a property by the name `name` to store `JObject` object
* (JSON object type).
*
* NOTE: This method DOES NOT make caller `JObject` store a
* given reference, instead it clones it (see `Clone()`) into a new copy and
* stores that. This is made this way to ensure you can not, say, store
* an object in itself or it's children.
* See also `CreateArray()` method.
*
* @param name Name of the property to return a value of.
* @param template Template `JObject` to clone into property.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject SetObject(string name, JObject template)
{
local JProperty property;
@ -424,10 +641,15 @@ public final function JObject SetObject(string name, JObject template)
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:
// `object.CreateObject("folded object").CreateArray("names list");`.
/**
* Sets the value of a property by the name `name` to store a new
* `JArray` object (JSON array type).
*
* See also `SetArray()` method.
*
* @param name Name of the property to store the new `JArray` value.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject CreateArray(string name)
{
local JProperty property;
@ -441,6 +663,15 @@ public final function JObject CreateArray(string name)
return self;
}
/**
* Sets the value of a property by the name `name` to store a new
* `JObject` object (JSON array type).
*
* See also `SetArray()` method.
*
* @param name Name of the property to store the new `JObject` value.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject CreateObject(string name)
{
local JProperty property;
@ -454,15 +685,44 @@ public final function JObject CreateObject(string name)
return self;
}
// Removes values with a given name.
// Returns `true` if value was actually removed and `false` if it didn't exist.
/**
* Removes a property with a given name.
*
* Does nothing if property with a given name does not exist.
*
* @param name Name of the property to remove.
* @return Reference to the caller object, to allow for function chaining.
*/
public final function JObject RemoveValue(string name)
{
RemoveProperty(name);
return self;
}
public final function array<string> GetKeys()
/**
* Completely clears caller `JObject` of all stored properties.
*/
public function Clear()
{
local int i, j;
local array<JProperty> nextProperties;
for (i = 0; i < hashTable.length; i += 1)
{
nextProperties = hashTable[i].properties;
for (j = 0; j < nextProperties.length; j += 1)
{
if (nextProperties[j].value.complexValue == none) continue;
nextProperties[j].value.complexValue.Destroy();
}
}
}
/**
* Returns names of all properties inside caller `JObject`.
*
* @return Array of all the caller object's property names as `string`s.
*/
public final function array<string> GetPropertyNames()
{
local int i, j;
local array<string> result;
@ -477,6 +737,13 @@ public final function array<string> GetKeys()
return result;
}
/**
* Checks if caller JSON container's values form a subset of
* `rightJSON`'s values.
*
* @return `true` if caller ("left") object is a subset of `rightJSON`
* and `false` otherwise.
*/
public function bool IsSubsetOf(JSON rightJSON)
{
local int i, j;
@ -501,6 +768,12 @@ public function bool IsSubsetOf(JSON rightJSON)
return true;
}
/**
* Makes an exact copy of the caller `JObject`
*
* @return Copy of the caller `JObject`. Guaranteed to be `JObject`
* (or `none`, if appropriate object could not be created).
*/
public function JSON Clone()
{
local int i, j;
@ -534,6 +807,32 @@ public function JSON Clone()
return clonedObject;
}
/**
* Uses given parser to parse a new set of properties inside
* the caller `JObject`.
*
* Only adds new properties if parsing the whole object was successful,
* otherwise even successfully parsed properties will be discarded.
*
* `parser` must point at the text describing a JSON object in
* a valid notation. Then it parses that container inside memory, but
* instead of creating it as a separate entity, adds it's values to
* the caller container.
* Everything that comes after parsed `JObject` is discarded.
*
* This method does not try to validate passed JSON and can accept invalid
* JSON by making some assumptions, but it is an undefined behavior and
* one should not expect it.
* Method is only guaranteed to work on valid JSON.
*
* @param parser Parser that method would use to parse `JObject` from
* wherever it left. It's confirmed will not be changed, but if parsing
* was successful, - it will point at the next available character.
* Do not treat `parser` being in a non-failed state as a confirmation of
* successful parsing: JSON parsing might fail regardless.
* Check return value for that.
* @return `true` if parsing was successful and `false` otherwise.
*/
public function bool ParseIntoSelfWith(Parser parser)
{
local bool parsingSucceeded;
@ -542,6 +841,7 @@ public function bool ParseIntoSelfWith(Parser parser)
local array<JProperty> parsedProperties;
if (parser == none) return false;
initState = parser.GetCurrentState();
// Ensure that parser starts pointing at what looks like a JSON object
confirmedState = parser.Skip().Match("{").GetCurrentState();
if (!parser.Ok())
{
@ -551,6 +851,8 @@ public function bool ParseIntoSelfWith(Parser parser)
while (parser.Ok() && !parser.HasFinished())
{
confirmedState = parser.Skip().GetCurrentState();
// Check for JSON object ending and ONLY THEN declare parsing
// is successful, not encountering '}' implies bad JSON format.
if (parser.Match("}").Ok())
{
parsingSucceeded = true;
@ -560,9 +862,12 @@ public function bool ParseIntoSelfWith(Parser parser)
&& !parser.RestoreState(confirmedState).Match(",").Skip().Ok()) {
break;
}
// Recover after failed `Match("}")` on the first cycle
// (`parsedProperties.length == 0`)
else if (parser.Ok()) {
confirmedState = parser.GetCurrentState();
}
// Parse property
parser.RestoreState(confirmedState).Skip();
parser.MStringLiteral(nextProperty.name).Skip().Match(":");
nextProperty.value = ParseAtom(parser.Skip());
@ -578,6 +883,8 @@ public function bool ParseIntoSelfWith(Parser parser)
return parsingSucceeded;
}
// Either cleans up or adds a list of parsed properties,
// depending on whether parsing was successful or not.
private function HandleParsedProperties(
array<JProperty> parsedProperties,
bool parsingSucceeded)
@ -598,6 +905,16 @@ private function HandleParsedProperties(
}
}
/**
* Displays caller `JObject` with a provided preset.
*
* See `Display()` for a simpler to use method.
*
* @param displaySettings Struct that describes precisely how to display
* caller `JObject`. Can be used to emulate `Display()` call.
* @return String representation of caller JSON container in format defined by
* `displaySettings`.
*/
public function string DisplayWith(JSONDisplaySettings displaySettings)
{
local int i, j;
@ -613,19 +930,7 @@ public function string DisplayWith(JSONDisplaySettings displaySettings)
else {
innerSettings = displaySettings;
}
// Prepare delimiters using appropriate indentation rules
// We only use inner settings for the part right after '{',
// as the rest is supposed to be aligned with outer objects
openingBraces = "{";
closingBraces = "}";
if (innerSettings.colored) {
openingBraces = "&" $ openingBraces;
closingBraces = "&" $ closingBraces;
}
openingBraces = displaySettings.beforeObjectOpening
$ openingBraces $ innerSettings.afterObjectOpening;
closingBraces = displaySettings.beforeObjectEnding
$ closingBraces $ displaySettings.afterObjectEnding;
GetBraces(openingBraces, closingBraces, displaySettings, innerSettings);
propertiesSeparator = "," $ innerSettings.afterObjectComma;
if (innerSettings.colored) {
propertiesSeparator = "{$json_comma" @ propertiesSeparator $ "}";
@ -648,6 +953,49 @@ public function string DisplayWith(JSONDisplaySettings displaySettings)
return openingBraces $ contents $ closingBraces;
}
/**
* Helper function that generates `string`s to be used for opening and
* closing braces for text representation of the caller `JObject`.
*
* Cannot fail.
*
* @param openingBraces `string` for opening braces will be recorded here.
* @param closingBraces `string` for closing braces will be recorded here.
* @param outerSettings Settings that were passed to tell us how to display
* a caller object.
* @param innerSettings Settings that were generated from `outerSettings` by
* indenting them (`IndentSettings()`) to use to display it's
* inner properties.
*/
protected function GetBraces(
out string openingBraces,
out string closingBraces,
JSONDisplaySettings outerSettings,
JSONDisplaySettings innerSettings)
{
openingBraces = "{";
closingBraces = "}";
if (innerSettings.colored) {
openingBraces = "&" $ openingBraces;
closingBraces = "&" $ closingBraces;
}
// We only use inner settings for the part right after '{',
// as the rest is supposed to be aligned with outer objects
openingBraces = outerSettings.beforeObjectOpening
$ openingBraces $ innerSettings.afterObjectOpening;
closingBraces = outerSettings.beforeObjectEnding
$ closingBraces $ outerSettings.afterObjectEnding;
}
/**
* Helper method to convert a JSON object's property into it's
* text representation.
*
* @param toDisplay Property to display as a `string`.
* @param displaySettings Settings that tells us how to display it.
* @return `string` representation of a given property `toDisplay`,
* created according to the settings `displaySettings`.
*/
protected function string DisplayProperty(
JProperty toDisplay,
JSONDisplaySettings displaySettings)
@ -667,21 +1015,6 @@ protected function string DisplayProperty(
$ displaySettings.afterPropertyValue);
}
public function Clear()
{
local int i, j;
local array<JProperty> nextProperties;
for (i = 0; i < hashTable.length; i += 1)
{
nextProperties = hashTable[i].properties;
for (j = 0; j < nextProperties.length; j += 1)
{
if (nextProperties[j].value.complexValue == none) continue;
nextProperties[j].value.complexValue.Destroy();
}
}
}
defaultproperties
{
ABSOLUTE_LOWER_CAPACITY_LIMIT = 10

485
sources/Data/JSON/JSON.uc

@ -3,9 +3,8 @@
* that uses human-readable text to store and transmit data objects
* consisting of name–value pairs and array data types.
* For more information refer to https://en.wikipedia.org/wiki/JSON
* This is a base class for implementation of JSON data storage for Acedia.
* It does not implement parsing and printing from/into human-readable
* text representation, just provides means to store such information.
* This is a base class for implementation of JSON objects and arrays
* for Acedia.
*
* JSON data is stored as an object (represented via `JSONObject`) that
* contains a set of name-value pairs, where value can be
@ -29,9 +28,12 @@
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class JSON extends AcediaActor
abstract;
abstract
config(AcediaSystem);
// Enumeration for possible types of JSON values.
/**
* Enumeration for possible types of JSON values.
*/
enum JType
{
// Technical type, used to indicate that requested value is missing.
@ -53,7 +55,9 @@ enum JType
JSON_Object
};
// Stores a single JSON value
/**
* Represents a single JSON value.
*/
struct JStorageAtom
{
// What type is stored exactly?
@ -77,44 +81,96 @@ struct JStorageAtom
var bool classLoadingWasAttempted;
};
/**
* Enumeration of possible result of comparing two JSON containers
* (objects or arrays).
* Containers are compared as sets of stored variables.
*/
enum JComparisonResult
{
// Containers contain different sets of values and
// neither can be considered a subset of another.
JCR_Incomparable,
// "Left" container is a subset of the "right" one.
JCR_SubSet,
// "Right" container is a subset of the "left" one.
JCR_Overset,
// Both objects are identical.
JCR_Equal
};
/**
* Describes how JSON containers are supposed to be displayed.
*/
struct JSONDisplaySettings
{
// Should it be displayed as a formatted string, with added color tags?
var bool colored;
// Should we "stack" indentation of folded objects?
var bool stackIndentation;
// Indentation for elements in object/array
var string subObjectIndentation, subArrayIndentation;
// Strings to put immediately before and after object opening: '{'
var string beforeObjectOpening, afterObjectOpening;
// Strings to put immediately before and after object closing: '}'
var string beforeObjectEnding, afterObjectEnding;
// {<beforePropertyName>"name"<afterPropertyName>:value}
var string beforePropertyName, afterPropertyName;
// {"name":<beforePropertyValue>value<afterPropertyValue>}
var string beforePropertyValue, afterPropertyValue;
// String to put immediately after comma inside object,
// can be used to break line after each property record
var string afterObjectComma;
// Strings to put immediately before and after array opening: '['
var string beforeArrayOpening, afterArrayOpening;
// Strings to put immediately before and after array closing: ']'
var string beforeArrayEnding, afterArrayEnding;
// [<beforeElement>element1<afterElement>,<afterArrayComma>...]
var string beforeElement, afterElement;
// Can be used to break line after each property record
var string afterArrayComma;
};
var private const int MAX_FLOAT_PRECISION;
// Max precision that will be used when outputting JSON values as a string.
// Hardcoded to force this value between 0 and 10, inclusively.
var private const config int MAX_FLOAT_PRECISION;
/**
* Completely clears caller JSON container of all stored data.
*/
public function Clear(){}
/**
* Makes an exact copy of the caller JSON container.
*
* @return Copy of the caller JSON container object.
*/
public function JSON Clone()
{
return none;
}
/**
* Checks if caller JSON container's values form a subset of
* `rightJSON`'s values.
*
* @return `true` if caller ("left") object is a subset of `rightJSON`
* and `false` otherwise.
*/
public function bool IsSubsetOf(JSON rightJSON)
{
return false;
}
/**
* Compares caller JSON container ("left container")
* to `rightJSON` ("right container").
*
* @param rightJSON Value to compare caller object to.
* @return `JComparisonResult` describing comparison of caller `JSON` container
* to `rightJSON`.
* Always returns `false` if compared objects are of different types.
*/
public final function JComparisonResult Compare(JSON rightJSON)
{
local bool firstIsSubset, secondIsSubset;
@ -140,11 +196,201 @@ public final function JComparisonResult Compare(JSON rightJSON)
}
}
/**
* Checks if two objects are equal.
*
* A shortcut for `Compare(rightJSON) == JCR_Equal`.
*
* @param rightJSON Value to compare caller object to.
* @return `true` if caller and `rightJSON` store exactly same set of values
* (under the same names for `JObject`) and `false` otherwise.
*/
public final function bool IsEqual(JSON rightJSON)
{
return (Compare(rightJSON) == JCR_Equal);
}
/**
* Displays caller JSON container with one of the presets.
*
* Default compact preset displays JSON in as little characters as possible,
* fancy preset tries to make it human-readable with appropriate spacing and
* indentation for sub objects.
*
* See `DisplayWith()` for a more tweakable method.
*
* @param fancyPrinting Leave empty of `false` for a compact display and
* `true` to display it with a fancy preset.
* @param colorSettings Display JSON container as a formatted string,
* adding color tags to JSON syntax.
* @return String representation of caller JSON container,
* in plain format if `colorSettings == false` and
* as a formatted string if `colorSettings == true`.
*/
public final function string Display(
optional bool fancyPrinting,
optional bool colorSettings)
{
local JSONDisplaySettings settingsToUse;
// Settings are minimal by default
if (fancyPrinting) {
settingsToUse = GetFancySettings();
}
if (colorSettings) {
settingsToUse.colored = true;
}
return DisplayWith(settingsToUse);
}
/**
* Displays caller JSON container with a provided preset.
*
* See `Display()` for a simpler to use method.
*
* @param displaySettings Struct that describes precisely how to display
* caller JSON container. Can be used to emulate `Display()` call.
* @return String representation of caller JSON container in format defined by
* `displaySettings`.
*/
public function string DisplayWith(JSONDisplaySettings displaySettings)
{
return "";
}
/**
* Uses given parser to parse a new set of properties inside
* the caller JSON container.
*
* Only adds new properties if parsing the whole object was successful,
* otherwise even successfully parsed properties will be discarded.
*
* `parser` must point at the text describing a JSON object or an array
* (depending on whether a caller object is `JObject` or `JArray`) in
* a valid notation. Then it parses that container inside memory, but
* instead of creating it as a separate entity, adds it's values to
* the caller container.
* Everything that comes after parsed JSON container is discarded.
*
* This method does not try to validate passed JSON and can accept invalid
* JSON by making some assumptions, but it is an undefined behavior and
* one should not expect it.
* Method is only guaranteed to work on valid JSON.
*
* @param parser Parser that method would use to parse JSON container from
* wherever it left. It's confirmed will not be changed, but if parsing
* was successful, - it will point at the next available character.
* Do not treat `parser` being in a non-failed state as a confirmation of
* successful parsing: JSON parsing might fail regardless.
* Check return value for that.
* @return `true` if parsing was successful and `false` otherwise.
*/
public function bool ParseIntoSelfWith(Parser parser)
{
return false;
}
/**
* Parse a new set of properties inside the caller JSON container from
* a given `Text`.
*
* Only adds new properties if parsing the whole object was successful,
* otherwise even successfully parsed properties will be discarded.
*
* JSON container is parsed from a given `Text`, but instead of creating
* new object as a separate entity, method adds it's values to
* the caller container.
* Everything that comes after parsed JSON container is discarded.
*
* This method does not try to validate passed JSON and can accept invalid
* JSON by making some assumptions, but it is an undefined behavior and
* one should not expect it.
* Method is only guaranteed to work on valid JSON.
*
* @param source `Text` to get JSON container definition from.
* @return `true` if parsing was successful and `false` otherwise.
*/
public final function bool ParseIntoSelf(Text source)
{
local bool successfullyParsed;
local Parser jsonParser;
jsonParser = _.text.Parse(source);
successfullyParsed = ParseIntoSelfWith(jsonParser);
_.memory.Free(jsonParser);
return successfullyParsed;
}
/**
* Parse a new set of properties inside the caller JSON container from
* a given `string`.
*
* Only adds new properties if parsing the whole object was successful,
* otherwise even successfully parsed properties will be discarded.
*
* JSON container is parsed from a given `string`, but instead of creating
* new object as a separate entity, method adds it's values to
* the caller container.
* Everything that comes after parsed JSON container is discarded.
*
* This method does not try to validate passed JSON and can accept invalid
* JSON by making some assumptions, but it is an undefined behavior and
* one should not expect it.
* Method is only guaranteed to work on valid JSON.
*
* @param source `string` to get JSON container definition from.
* @return `true` if parsing was successful and `false` otherwise.
*/
public final function bool ParseIntoSelfString(
string source,
optional Text.StringType stringType)
{
local bool successfullyParsed;
local Parser jsonParser;
jsonParser = _.text.ParseString(source, stringType);
successfullyParsed = ParseIntoSelfWith(jsonParser);
_.memory.Free(jsonParser);
return successfullyParsed;
}
/**
* Parse a new set of properties inside the caller JSON container from
* a given raw data.
*
* Only adds new properties if parsing the whole object was successful,
* otherwise even successfully parsed properties will be discarded.
*
* JSON container is parsed from a given raw data, but instead of creating
* new object as a separate entity, method adds it's values to
* the caller container.
* Everything that comes after parsed JSON container is discarded.
*
* This method does not try to validate passed JSON and can accept invalid
* JSON by making some assumptions, but it is an undefined behavior and
* one should not expect it.
* Method is only guaranteed to work on valid JSON.
*
* @param source Raw data *array of `Text.Character`) to get JSON container
* definition from.
* @return `true` if parsing was successful and `false` otherwise.
*/
public final function bool ParseIntoSelfRaw(array<Text.Character> rawSource)
{
local bool successfullyParsed;
local Parser jsonParser;
jsonParser = _.text.ParseRaw(rawSource);
successfullyParsed = ParseIntoSelfWith(jsonParser);
_.memory.Free(jsonParser);
return successfullyParsed;
}
/**
* Checks if two `JStorageAtom` values represent the same values.
*
* Atoms storing the same value does not necessarily mean that they are equal
* as structs because they contain several different container members and
* unused ones can differ.
*
* @return `true` if atoms stores the same value, `false` otherwise.
*/
protected final function bool AreAtomsEqual(
JStorageAtom atom1,
JStorageAtom atom2)
@ -171,6 +417,11 @@ protected final function bool AreAtomsEqual(
return atom1.complexValue.IsEqual(atom2.complexValue);
}
/**
* Tries to load class variable into an atom, based on it's `stringValue`.
*
* @param atom Result will be recorded in the field of the argument itself.
*/
protected final function TryLoadingStringAsClass(out JStorageAtom atom)
{
if (atom.classLoadingWasAttempted) return;
@ -179,26 +430,18 @@ protected final function TryLoadingStringAsClass(out JStorageAtom atom)
class<Object>(DynamicLoadObject(atom.stringValue, class'Class', true));
}
public final function string Display(
optional bool fancyPrinting,
optional bool colorSettings)
{
local JSONDisplaySettings settingsToUse;
// Settings are minimal by default
if (fancyPrinting) {
settingsToUse = GetFancySettings();
}
if (colorSettings) {
settingsToUse.colored = true;
}
return DisplayWith(settingsToUse);
}
public function string DisplayWith(JSONDisplaySettings displaySettings)
{
return "";
}
/**
* Displays a `JStorageAtom` in it's appropriate text JSON representation.
*
* That's a representation that can be pasted inside JSON array as-is
* (with different values separated by commas).
*
* @param atom Atom to display.
* @param displaySettings Display settings, according to which to
* display the atom.
* @return Text representation of the passed `atom`, empty if it's of
* the type `JSON_Undefined`.
*/
protected final function string DisplayAtom(
JStorageAtom atom,
JSONDisplaySettings displaySettings)
@ -241,7 +484,9 @@ protected final function string DisplayAtom(
return result;
}
protected final function string DisplayFloat(float number)
// Helper function for printing float with a given max precision
// (`MAX_FLOAT_PRECISION`).
private final function string DisplayFloat(float number)
{
local int integerPart, fractionalPart;
local int precision;
@ -256,23 +501,29 @@ protected final function string DisplayFloat(float number)
integerPart = number;
result $= string(integerPart);
number = (number - integerPart);
// We try to perform minimal amount of operations to extract fractional
// part as integer in order to avoid accumulating too much of an error.
fractionalPart = Round(number * (10 ** precision));
if (fractionalPart <= 0) {
return result;
}
result $= ".";
// Pad necessary zeroes in front
howManyZeroes = precision - CountDigits(fractionalPart);
while (fractionalPart > 0 && fractionalPart % 10 == 0) {
fractionalPart /= 10;
}
while (howManyZeroes > 0) {
zeroes $= "0";
howManyZeroes -= 1;
}
// Cut off trailing zeroes and
while (fractionalPart > 0 && fractionalPart % 10 == 0) {
fractionalPart /= 10;
}
return result $ zeroes $ string(fractionalPart);
}
protected final function int CountDigits(int number)
// Helper function that counts amount of digits in decimal representation
// of `number`.
private final function int CountDigits(int number)
{
local int digitCounter;
while (number > 0)
@ -284,23 +535,15 @@ protected final function int CountDigits(int number)
return digitCounter;
}
protected final function JSONDisplaySettings GetFancySettings()
{
local string lineFeed;
local JSONDisplaySettings fancySettings;
lineFeed = Chr(10);
fancySettings.stackIndentation = true;
fancySettings.subObjectIndentation = " ";
fancySettings.subArrayIndentation = "";
fancySettings.afterObjectOpening = lineFeed;
fancySettings.beforeObjectEnding = lineFeed;
fancySettings.beforePropertyValue = " ";
fancySettings.afterObjectComma = lineFeed;
fancySettings.beforeElement = " ";
fancySettings.afterArrayComma = " ";
return fancySettings;
}
/**
* Prepares a `string` to be displayed as textual JSON representation by
* replacing certain characters with their escaped sequences.
*
* @param input String value to display inside a text representation of
* a JSON data.
* @result Representation of an `input` that can be included in text form of
* JSON data.
*/
protected final function string DisplayJSONString(string input)
{
// Convert control characters (+ other, specified by JSON)
@ -317,6 +560,36 @@ protected final function string DisplayJSONString(string input)
return ("\"" $ input $ "\"");
}
// helper function to prepare fancy display settings, because it is a bitch to
// include a `string` with new line symbol in `defaultproperties`.
private final function JSONDisplaySettings GetFancySettings()
{
local string lineFeed;
local JSONDisplaySettings fancySettings;
lineFeed = Chr(10);
fancySettings.stackIndentation = true;
fancySettings.subObjectIndentation = " ";
fancySettings.subArrayIndentation = "";
fancySettings.afterObjectOpening = lineFeed;
fancySettings.beforeObjectEnding = lineFeed;
fancySettings.beforePropertyValue = " ";
fancySettings.afterObjectComma = lineFeed;
fancySettings.beforeElement = " ";
fancySettings.afterArrayComma = " ";
return fancySettings;
}
/**
* Helper function that prepares `JSONDisplaySettings` to be used for
* a folded object / array to make it more human-readable thanks to
* sub-object/-arrays indentation.
*
* @param inputSettings Settings to modify, passed variable will
* remain unchanged.
* @param indentingArray True if we need to modify settings for
* a folded array and `false` if for the object.
* @return Modified `inputSettings`, with added indentation.
*/
protected final function JSONDisplaySettings IndentSettings(
JSONDisplaySettings inputSettings,
optional bool indentingArray)
@ -354,41 +627,19 @@ protected final function JSONDisplaySettings IndentSettings(
return indentedSettings;
}
public function bool ParseIntoSelfWith(Parser parser)
{
return false;
}
public final function bool ParseIntoSelf(Text source)
{
local bool successfullyParsed;
local Parser jsonParser;
jsonParser = _.text.Parse(source);
successfullyParsed = ParseIntoSelfWith(jsonParser);
_.memory.Free(jsonParser);
return successfullyParsed;
}
public final function bool ParseIntoSelfString(string source)
{
local bool successfullyParsed;
local Parser jsonParser;
jsonParser = _.text.ParseString(source);
successfullyParsed = ParseIntoSelfWith(jsonParser);
_.memory.Free(jsonParser);
return successfullyParsed;
}
public final function bool ParseIntoSelfRaw(array<Text.Character> rawSource)
{
local bool successfullyParsed;
local Parser jsonParser;
jsonParser = _.text.ParseRaw(rawSource);
successfullyParsed = ParseIntoSelfWith(jsonParser);
_.memory.Free(jsonParser);
return successfullyParsed;
}
/**
* Uses given parser to parse a single (possibly complex like JSON object
* or array) JSON value.
*
* @param parser Parser that method would use to parse JSON value from
* wherever it left. It's confirmed will not be changed, but if parsing
* was successful, - it will point at the next available character.
* Do not treat `parser` being in a non-failed state as a confirmation of
* successful parsing: JSON parsing might fail regardless.
* Check return value for that.
* @return Parsed JSON value as `JStorageAtom`.
* If parsing has failed it will have the `JSON_Undefined` type.
*/
protected final function JStorageAtom ParseAtom(Parser parser)
{
local Parser.ParserState initState;
@ -396,41 +647,55 @@ protected final function JStorageAtom ParseAtom(Parser parser)
if (parser == none) return newAtom;
if (!parser.Ok()) return newAtom;
initState = parser.GetCurrentState();
parser.Skip().Confirm();
if (parser.MStringLiteral(newAtom.stringValue).Ok())
{
newAtom.type = JSON_String;
return newAtom;
}
newAtom = ParseLiteral(parser.R());
newAtom = ParseLiteral(parser.RestoreState(initState));
if (newAtom.type != JSON_Undefined) {
return newAtom;
}
newAtom = ParseComplex(parser.R());
newAtom = ParseComplex(parser.RestoreState(initState));
if (newAtom.type != JSON_Undefined) {
return newAtom;
}
newAtom = ParseNumber(parser.R());
newAtom = ParseNumber(parser.RestoreState(initState));
if (newAtom.type == JSON_Undefined) {
parser.RestoreState(initState);
}
return newAtom;
}
/**
* Uses given parser to parse a "literal" JSON value:
* "true", "false" or "null".
*
* @param parser Parser that method would use to parse JSON value from
* wherever it left. It's confirmed will not be changed, but if parsing
* was successful, - it will point at the next available character.
* Do not treat `parser` being in a non-failed state as a confirmation of
* successful parsing: JSON parsing might fail regardless.
* Check return value for that.
* @return Parsed JSON value as `JStorageAtom`.
* If parsing has failed it will have the `JSON_Undefined` type.
*/
protected final function JStorageAtom ParseLiteral(Parser parser)
{
local JStorageAtom newAtom;
local JStorageAtom newAtom;
local Parser.ParserState initState;
initState = parser.GetCurrentState();
if (parser.Match("null", true).Ok())
{
newAtom.type = JSON_Null;
return newAtom;
}
if (parser.R().Match("false", true).Ok())
if (parser.RestoreState(initState).Match("false", true).Ok())
{
newAtom.type = JSON_Boolean;
return newAtom;
}
if (parser.R().Match("true", true).Ok())
if (parser.RestoreState(initState).Match("true", true).Ok())
{
newAtom.type = JSON_Boolean;
newAtom.booleanValue = true;
@ -438,21 +703,36 @@ protected final function JStorageAtom ParseLiteral(Parser parser)
}
}
/**
* Uses given parser to parse a complex JSON value: JSON object or array.
*
* @param parser Parser that method would use to parse JSON value from
* wherever it left. It's confirmed will not be changed, but if parsing
* was successful, - it will point at the next available character.
* Do not treat `parser` being in a non-failed state as a confirmation of
* successful parsing: JSON parsing might fail regardless.
* Check return value for that.
* @return Parsed JSON value as `JStorageAtom`.
* If parsing has failed it will have the `JSON_Undefined` type.
*/
protected final function JStorageAtom ParseComplex(Parser parser)
{
local JStorageAtom newAtom;
local JStorageAtom newAtom;
local Parser.ParserState initState;
initState = parser.GetCurrentState();
if (parser.Match("{").Ok())
{
newAtom.complexValue = _.json.NewObject();
newAtom.type = JSON_Object;
}
else if (parser.R().Match("[").Ok())
else if (parser.RestoreState(initState).Match("[").Ok())
{
newAtom.complexValue = _.json.NewArray();
newAtom.type = JSON_Array;
}
parser.RestoreState(initState);
if ( newAtom.complexValue != none
&& newAtom.complexValue.ParseIntoSelfWith(parser.R())) {
&& newAtom.complexValue.ParseIntoSelfWith(parser)) {
return newAtom;
}
newAtom.type = JSON_Undefined;
@ -460,10 +740,23 @@ protected final function JStorageAtom ParseComplex(Parser parser)
return newAtom;
}
/**
* Uses given parser to parse a numeric JSON value.
*
* @param parser Parser that method would use to parse JSON value from
* wherever it left. It's confirmed will not be changed, but if parsing
* was successful, - it will point at the next available character.
* Do not treat `parser` being in a non-failed state as a confirmation of
* successful parsing: JSON parsing might fail regardless.
* Check return value for that.
* @return Parsed JSON value as `JStorageAtom`.
* If parsing has failed it will have the `JSON_Undefined` type.
*/
protected final function JStorageAtom ParseNumber(Parser parser)
{
local JStorageAtom newAtom;
local Parser.ParserState integerParsedState;
local Parser.ParserState initState, integerParsedState;
initState = parser.GetCurrentState();
if (!parser.MInteger(newAtom.numberValueAsInt).Ok()) {
return newAtom;
}
@ -474,7 +767,7 @@ protected final function JStorageAtom ParseNumber(Parser parser)
if ( parser.Match(".").Ok()
|| parser.RestoreState(integerParsedState).Match("e", true).Ok())
{
parser.R().MNumber(newAtom.numberValue);
parser.RestoreState(initState).MNumber(newAtom.numberValue);
return newAtom;
}
parser.RestoreState(integerParsedState);

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

@ -28,7 +28,7 @@ protected static function TESTS()
local JObject jsonData;
jsonData = _().json.newObject();
Test_ObjectGetSetRemove();
Test_ObjectKeys();
Test_ObjectPropertyNames();
Test_ArrayGetSetRemove();
Test_JSONComparison();
Test_JSONCloning();
@ -53,7 +53,7 @@ protected static function Test_ObjectGetSetRemove()
protected static function Test_ArrayGetSetRemove()
{
Context("Testing get/set/remove functions for JSON arrays");
Context("Testing get/set/remove functions for JSON arrays.");
SubTest_ArrayUndefined();
SubTest_ArrayStringGetSetRemove();
SubTest_ArrayClassGetSetRemove();
@ -74,7 +74,7 @@ protected static function SubTest_Undefined()
local JObject testJSON;
testJSON = _().json.newObject();
Context("Testing how `JObject` handles undefined values");
Context("Testing how `JObject` handles undefined values.");
Issue("Undefined variable doesn't have proper type.");
TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Undefined);
@ -96,27 +96,27 @@ protected static function SubTest_BooleanGetSetRemove()
testJSON.SetBoolean("some_boolean", true);
Context("Testing `JObject`'s get/set/remove functions for" @
"boolean variables");
Issue("Boolean type isn't properly set by `SetBoolean`");
"boolean variables.");
Issue("Boolean type isn't properly set by `SetBoolean()`.");
TEST_ExpectTrue(testJSON.GetTypeOf("some_boolean") == JSON_Boolean);
Issue("Variable value is incorrectly assigned by `SetBoolean`");
Issue("Variable value is incorrectly assigned by `SetBoolean()`.");
TEST_ExpectTrue(testJSON.GetBoolean("some_boolean") == true);
Issue("Variable value isn't correctly reassigned by `SetBoolean`");
Issue("Variable value isn't correctly reassigned by `SetBoolean()`.");
testJSON.SetBoolean("some_boolean", false);
TEST_ExpectTrue(testJSON.GetBoolean("some_boolean") == false);
Issue( "Getting boolean variable as a wrong type" @
"doesn't yield default value");
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetNumber("some_boolean", 7) == 7);
Issue("Boolean variable isn't being properly removed");
Issue("Boolean variable isn't being properly removed.");
testJSON.RemoveValue("some_boolean");
TEST_ExpectTrue(testJSON.GetTypeOf("some_boolean") == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored boolean value, that got removed");
"previously stored boolean value, that got removed.");
TEST_ExpectTrue(testJSON.GetBoolean("some_boolean", true) == true);
}
@ -127,32 +127,32 @@ protected static function SubTest_StringGetSetRemove()
testJSON.SetString("some_string", "first string");
Context("Testing `JObject`'s get/set/remove functions for" @
"string variables");
Issue("String type isn't properly set by `SetString`");
"string variables.");
Issue("String type isn't properly set by `SetString()`.");
TEST_ExpectTrue(testJSON.GetTypeOf("some_string") == JSON_String);
Issue("Value is incorrectly assigned by `SetString`");
Issue("Value is incorrectly assigned by `SetString()`.");
TEST_ExpectTrue(testJSON.GetString("some_string") == "first string");
Issue( "Providing default variable value makes 'GetString'" @
"return wrong value");
"return wrong value.");
TEST_ExpectTrue( testJSON.GetString("some_string", "alternative")
== "first string");
Issue("Variable value isn't correctly reassigned by `SetString`");
Issue("Variable value isn't correctly reassigned by `SetString()`.");
testJSON.SetString("some_string", "new string!~");
TEST_ExpectTrue(testJSON.GetString("some_string") == "new string!~");
Issue( "Getting string variable as a wrong type" @
"doesn't yield default value");
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetBoolean("some_string", true) == true);
Issue("String variable isn't being properly removed");
Issue("String variable isn't being properly removed.");
testJSON.RemoveValue("some_string");
TEST_ExpectTrue(testJSON.GetTypeOf("some_string") == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored string value, but got removed");
"previously stored string value, but got removed.");
TEST_ExpectTrue(testJSON.GetString("some_string", "other") == "other");
}
@ -163,32 +163,32 @@ protected static function SubTest_ClassGetSetRemove()
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`");
"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`");
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");
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`");
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");
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetBoolean("info_class", true) == true);
Issue("Class variable isn't being properly removed");
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");
"previously stored class value, but got removed.");
TEST_ExpectTrue( testJSON.GetClass("info_class", class'Actor')
== class'Actor');
}
@ -229,18 +229,18 @@ protected static function SubTest_NumberGetSetRemove()
testJSON.SetNumber("some_number", 3.5);
Context("Testing `JObject`'s get/set/remove functions for" @
"number variables as floats");
Issue("Number type isn't properly set by `SetNumber`");
"number variables as floats.");
Issue("Number type isn't properly set by `SetNumber()`.");
TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Number);
Issue("Value is incorrectly assigned by `SetNumber`");
Issue("Value is incorrectly assigned by `SetNumber()`.");
TEST_ExpectTrue(testJSON.GetNumber("some_number") == 3.5);
Issue( "Providing default variable value makes 'GetNumber'" @
"return wrong value");
Issue( "Providing default variable value makes 'GetNumber()'" @
"return wrong value.");
TEST_ExpectTrue(testJSON.GetNumber("some_number", 5) == 3.5);
Issue("Variable value isn't correctly reassigned by `SetNumber`");
Issue("Variable value isn't correctly reassigned by `SetNumber()`.");
testJSON.SetNumber("some_number", 7);
TEST_ExpectTrue(testJSON.GetNumber("some_number") == 7);
@ -248,12 +248,12 @@ protected static function SubTest_NumberGetSetRemove()
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetString("some_number", "default") == "default");
Issue("Number type isn't being properly removed");
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");
"previously stored number value, that got removed.");
TEST_ExpectTrue(testJSON.GetNumber("some_number", 13) == 13);
}
@ -264,18 +264,18 @@ protected static function SubTest_IntegerGetSetRemove()
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`");
"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`");
Issue("Value is incorrectly assigned by `SetInteger()`.");
TEST_ExpectTrue(testJSON.GetInteger("some_number") == 33653);
Issue( "Providing default variable value makes 'GetInteger'" @
"return wrong value");
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`");
Issue("Variable value isn't correctly reassigned by `SetInteger()`.");
testJSON.SetInteger("some_number", MaxInt);
TEST_ExpectTrue(testJSON.GetInteger("some_number") == MaxInt);
@ -283,12 +283,12 @@ protected static function SubTest_IntegerGetSetRemove()
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetString("some_number", "default") == "default");
Issue("Number type isn't being properly removed");
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");
"previously stored number value, that got removed.");
TEST_ExpectTrue(testJSON.GetInteger("some_number", -235) == -235);
}
@ -320,27 +320,27 @@ protected static function SubTest_NullGetSetRemove()
testJSON = _().json.newObject();
Context("Testing `JObject`'s get/set/remove functions for" @
"null values");
Issue("Undefined variable is incorrectly considered `null`");
"null values.");
Issue("Undefined variable is incorrectly considered null.");
TEST_ExpectFalse(testJSON.IsNull("some_var"));
Issue("Number variable is incorrectly considered `null`");
Issue("Number variable is incorrectly considered null.");
testJSON.SetNumber("some_var", 4);
TEST_ExpectFalse(testJSON.IsNull("some_var"));
Issue("Boolean variable is incorrectly considered `null`");
Issue("Boolean variable is incorrectly considered null.");
testJSON.SetBoolean("some_var", true);
TEST_ExpectFalse(testJSON.IsNull("some_var"));
Issue("String variable is incorrectly considered `null`");
Issue("String variable is incorrectly considered null.");
testJSON.SetString("some_var", "string");
TEST_ExpectFalse(testJSON.IsNull("some_var"));
Issue("Null value is incorrectly assigned");
Issue("Null value is incorrectly assigned.");
testJSON.SetNull("some_var");
TEST_ExpectTrue(testJSON.IsNull("some_var"));
Issue("Null type isn't properly set by `SetNumber`");
Issue("Null type isn't properly set by `SetNumber`.");
TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Null);
Issue("Null value isn't being properly removed.");
@ -355,17 +355,14 @@ protected static function SubTest_MultipleVariablesGetSet()
local JObject testJSON;
testJSON = _().json.newObject();
Context("Testing how `JObject` handles addition, change and removal" @
"of relatively large (hundreds) number of variables");
for (i = 0; i < 2000; i += 1)
{
"of relatively large (hundreds) number of variables.");
for (i = 0; i < 2000; i += 1) {
testJSON.SetNumber("num" $ string(i), 4 * i*i - 2.6 * i + 0.75);
}
for (i = 0; i < 500; i += 1)
{
for (i = 0; i < 500; i += 1) {
testJSON.SetString("num" $ string(i), "str" $ string(Sin(i)));
}
for (i = 1500; i < 2000; i += 1)
{
for (i = 1500; i < 2000; i += 1) {
testJSON.RemoveValue("num" $ string(i));
}
allValuesCorrect = true;
@ -375,19 +372,19 @@ protected static function SubTest_MultipleVariablesGetSet()
{
correctValue = ( testJSON.GetString("num" $ string(i))
== ("str" $ string(Sin(i))) );
Issue("Variables are incorrectly overwritten");
Issue("Variables are incorrectly overwritten.");
}
else if(i < 1500)
{
correctValue = ( testJSON.GetNumber("num" $ string(i))
== 4 * i*i - 2.6 * i + 0.75);
Issue("Variables are lost");
Issue("Variables are lost.");
}
else
{
correctValue = ( testJSON.GetTypeOf("num" $ string(i))
== JSON_Undefined);
Issue("Variables aren't removed");
Issue("Variables aren't removed.");
}
if (!correctValue)
{
@ -401,7 +398,7 @@ protected static function SubTest_MultipleVariablesGetSet()
protected static function SubTest_Object()
{
local JObject testObject;
Context("Testing setters and getters for folded objects");
Context("Testing setters and getters for folded objects.");
testObject = _().json.newObject();
testObject.CreateObject("folded");
testObject.GetObject("folded").CreateObject("folded");
@ -411,72 +408,73 @@ protected static function SubTest_Object()
.GetObject("folded")
.SetString("in", "string inside");
Issue("Addressing variables in root object doesn't work");
Issue("Addressing variables in root object doesn't work.");
TEST_ExpectTrue(testObject.GetString("out", "default") == "string outside");
Issue("Addressing variables in folded object doesn't work");
Issue("Addressing variables in folded object doesn't work.");
TEST_ExpectTrue(testObject.GetObject("folded").GetNumber("mid", 1) == 8);
Issue("Addressing plain variables in folded (twice) object doesn't work");
Issue("Addressing plain variables in folded (twice) object doesn't work.");
TEST_ExpectTrue(testObject.GetObject("folded").GetObject("folded")
.GetString("in", "default") == "string inside");
}
protected static function Test_ObjectKeys()
protected static function Test_ObjectPropertynames()
{
local int i;
local bool varFound, clsFound, objFound;
local JObject testObject;
local array<string> keys;
local array<string> names;
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)
Context("Testing getting list of property names from the `JObject`.");
Issue("Just created `JObject` returns non-empty names list.");
TEST_ExpectTrue(testObject.GetPropertyNames().length == 0);
Issue("`JObject` returns incorrect names list.");
names = testObject.SetInteger("var", 7).SetClass("cls", class'Actor')
.CreateObject("obj").GetPropertyNames();
TEST_ExpectTrue(names.length == 3);
for (i = 0; i < names.length; i += 1)
{
if (keys[i] == "var") { varFound = true; }
if (keys[i] == "cls") { clsFound = true; }
if (keys[i] == "obj") { objFound = true; }
if (names[i] == "var") { varFound = true; }
if (names[i] == "cls") { clsFound = true; }
if (names[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);
Issue("`JObject` returns incorrect names list after removing an element.");
names = testObject.RemoveValue("cls").GetPropertyNames();
TEST_ExpectTrue(names.length == 2);
varFound = false;
objFound = false;
for (i = 0; i < keys.length; i += 1)
for (i = 0; i < names.length; i += 1)
{
if (keys[i] == "var") { varFound = true; }
if (keys[i] == "obj") { objFound = true; }
if (names[i] == "var") { varFound = true; }
if (names[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);
Issue("`JObject` returns incorrect names list after removing"
@ "all elements.");
names = testObject.RemoveValue("var").RemoveValue("obj").GetPropertyNames();
TEST_ExpectTrue(names.length == 0);
}
protected static function SubTest_ArrayUndefined()
{
local JArray testJSON;
testJSON = _().json.newArray();
Context("Testing how `JArray` handles undefined values");
Issue("Undefined variable doesn't have `JSON_Undefined` type");
Context("Testing how `JArray` handles undefined values.");
Issue("Undefined variable doesn't have `JSON_Undefined` type.");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
Issue("There is a variable in an empty object after `GetTypeOf` call");
Issue("There is a variable in an empty object after `GetTypeOf` call.");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
Issue("Negative index refers to a defined value");
Issue("Negative index refers to a defined value.");
TEST_ExpectTrue(testJSON.GetTypeOf(-1) == JSON_Undefined);
Issue("Getters don't return default values for undefined variables");
Issue("Getters don't return default values for undefined variables.");
TEST_ExpectTrue(testJSON.GetNumber(0, 0) == 0);
TEST_ExpectTrue(testJSON.GetString(0, "") == "");
TEST_ExpectTrue(testJSON.GetBoolean(0, false) == false);
@ -484,7 +482,7 @@ protected static function SubTest_ArrayUndefined()
TEST_ExpectNone(testJSON.GetArray(0));
Issue( "Getters don't return user-defined default values for" @
"undefined variables");
"undefined variables.");
TEST_ExpectTrue(testJSON.GetNumber(0, 10) == 10);
TEST_ExpectTrue(testJSON.GetString(0, "test") == "test");
TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true);
@ -497,27 +495,27 @@ protected static function SubTest_ArrayBooleanGetSetRemove()
testJSON.SetBoolean(0, true);
Context("Testing `JArray`'s get/set/remove functions for" @
"boolean variables");
Issue("Boolean type isn't properly set by `SetBoolean`");
"boolean variables.");
Issue("Boolean type isn't properly set by `SetBoolean()`.");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Boolean);
Issue("Value is incorrectly assigned by `SetBoolean`");
Issue("Value is incorrectly assigned by `SetBoolean()`.");
TEST_ExpectTrue(testJSON.GetBoolean(0) == true);
testJSON.SetBoolean(0, false);
Issue("Variable value isn't correctly reassigned by `SetBoolean`");
Issue("Variable value isn't correctly reassigned by `SetBoolean()`.");
TEST_ExpectTrue(testJSON.GetBoolean(0) == false);
Issue( "Getting boolean variable as a wrong type" @
"doesn't yield default value");
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetNumber(0, 7) == 7);
Issue("Boolean variable isn't being properly removed");
Issue("Boolean variable isn't being properly removed.");
testJSON.RemoveValue(0);
TEST_ExpectTrue( testJSON.GetTypeOf(0) == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored boolean value, but got removed");
"previously stored boolean value, but got removed.");
TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true);
}
@ -528,31 +526,31 @@ protected static function SubTest_ArrayStringGetSetRemove()
testJSON.SetString(0, "first string");
Context("Testing `JArray`'s get/set/remove functions for" @
"string variables");
Issue("String type isn't properly set by `SetString`");
"string variables.");
Issue("String type isn't properly set by `SetString()`.");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_String);
Issue("Value is incorrectly assigned by `SetString`");
Issue("Value is incorrectly assigned by `SetString()`.");
TEST_ExpectTrue(testJSON.GetString(0) == "first string");
Issue( "Providing default variable value makes 'GetString'" @
"return incorrect value");
Issue( "Providing default variable value makes 'GetString()'" @
"return incorrect value.");
TEST_ExpectTrue(testJSON.GetString(0, "alternative") == "first string");
Issue("Variable value isn't correctly reassigned by `SetString`");
Issue("Variable value isn't correctly reassigned by `SetString()`.");
testJSON.SetString(0, "new string!~");
TEST_ExpectTrue(testJSON.GetString(0) == "new string!~");
Issue( "Getting string variable as a wrong type" @
"doesn't yield default value");
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true);
Issue("Boolean variable isn't being properly removed");
Issue("Boolean variable isn't being properly removed.");
testJSON.RemoveValue(0);
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored string value, but got removed");
"previously stored string value, but got removed.");
TEST_ExpectTrue(testJSON.GetString(0, "other") == "other");
}
@ -563,31 +561,31 @@ protected static function SubTest_ArrayClassGetSetRemove()
testJSON.SetClass(0, class'Actor');
Context("Testing `JArray`'s get/set/remove functions for" @
"class variables");
Issue("Class type isn't properly set by `SetClass`");
"class variables.");
Issue("Class type isn't properly set by `SetClass()`.");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_String);
Issue("Value is incorrectly assigned by `SetClass`");
Issue("Value is incorrectly assigned by `SetClass()`.");
TEST_ExpectTrue(testJSON.GetClass(0) == class'Actor');
Issue( "Providing default variable value makes `GetClass`" @
"return incorrect value");
Issue( "Providing default variable value makes `GetClass()`" @
"return incorrect value.");
TEST_ExpectTrue(testJSON.GetClass(0, class'GameInfo') == class'Actor');
Issue("Variable value isn't correctly reassigned by `SetClass`");
Issue("Variable value isn't correctly reassigned by `SetClass()`.");
testJSON.SetClass(0, class'Info');
TEST_ExpectTrue(testJSON.GetClass(0) == class'Info');
Issue( "Getting class variable as a wrong type" @
"doesn't yield default value");
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true);
Issue("Boolean variable isn't being properly removed");
Issue("Boolean variable isn't being properly removed.");
testJSON.RemoveValue(0);
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored string value, but got removed");
"previously stored string value, but got removed.");
TEST_ExpectTrue(testJSON.GetClass(0, class'Mutator') == class'Mutator');
}
@ -625,31 +623,31 @@ protected static function SubTest_ArrayNumberGetSetRemove()
testJSON.SetNumber(0, 3.5);
Context("Testing `JArray`'s get/set/remove functions for" @
"number variables");
Issue("Number type isn't properly set by `SetNumber`");
"number variables.");
Issue("Number type isn't properly set by `SetNumber()`.");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Number);
Issue("Value is incorrectly assigned by `SetNumber`");
Issue("Value is incorrectly assigned by `SetNumber()`.");
TEST_ExpectTrue(testJSON.GetNumber(0) == 3.5);
Issue( "Providing default variable value makes 'GetNumber'" @
"return incorrect value");
Issue( "Providing default variable value makes 'GetNumber()'" @
"return incorrect value.");
TEST_ExpectTrue(testJSON.GetNumber(0, 5) == 3.5);
Issue("Variable value isn't correctly reassigned by `SetNumber`");
Issue("Variable value isn't correctly reassigned by `SetNumber()`.");
testJSON.SetNumber(0, 7);
TEST_ExpectTrue(testJSON.GetNumber(0) == 7);
Issue( "Getting number variable as a wrong type" @
"doesn't yield default value");
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetString(0, "default") == "default");
Issue("Number type isn't being properly removed");
Issue("Number type isn't being properly removed.");
testJSON.RemoveValue(0);
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored number value, but got removed");
"previously stored number value, but got removed.");
TEST_ExpectTrue(testJSON.GetNumber(0, 13) == 13);
}
@ -660,31 +658,31 @@ protected static function SubTest_ArrayIntegerGetSetRemove()
testJSON.SetInteger(0, 19);
Context("Testing `JArray`'s get/set/remove functions for" @
"integer variables");
Issue("Integer type isn't properly set by `SetInteger`");
"integer variables.");
Issue("Integer type isn't properly set by `SetInteger()`.");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Number);
Issue("Value is incorrectly assigned by `SetInteger`");
Issue("Value is incorrectly assigned by `SetInteger()`.");
TEST_ExpectTrue(testJSON.GetInteger(0) == 19);
Issue( "Providing default variable value makes `GetInteger`" @
"return incorrect value");
Issue( "Providing default variable value makes `GetInteger()`" @
"return incorrect value.");
TEST_ExpectTrue(testJSON.GetInteger(0, 5) == 19);
Issue("Variable value isn't correctly reassigned by `SetInteger`");
Issue("Variable value isn't correctly reassigned by `SetInteger()`.");
testJSON.SetInteger(0, MaxInt);
TEST_ExpectTrue(testJSON.GetInteger(0) == MaxInt);
Issue( "Getting integer variable as a wrong type" @
"doesn't yield default value");
"doesn't yield default value.");
TEST_ExpectTrue(testJSON.GetString(0, "default") == "default");
Issue("Integer type isn't being properly removed");
Issue("Integer type isn't being properly removed.");
testJSON.RemoveValue(0);
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
Issue( "Getters don't return default value for missing key that" @
"previously stored integer value, but got removed");
"previously stored integer value, but got removed.");
TEST_ExpectTrue(testJSON.GetInteger(0, 13) == 13);
}
@ -716,22 +714,22 @@ protected static function SubTest_ArrayNullGetSetRemove()
testJSON = _().json.newArray();
Context("Testing `JArray`'s get/set/remove functions for" @
"null values");
"null values.");
Issue("Undefined variable is incorrectly considered `null`");
Issue("Undefined variable is incorrectly considered null.");
TEST_ExpectFalse(testJSON.IsNull(0));
TEST_ExpectFalse(testJSON.IsNull(2));
TEST_ExpectFalse(testJSON.IsNull(-1));
Issue("Number variable is incorrectly considered `null`");
Issue("Number variable is incorrectly considered null.");
testJSON.SetNumber(0, 4);
TEST_ExpectFalse(testJSON.IsNull(0));
Issue("Boolean variable is incorrectly considered `null`");
Issue("Boolean variable is incorrectly considered null.");
testJSON.SetBoolean(0, true);
TEST_ExpectFalse(testJSON.IsNull(0));
Issue("String variable is incorrectly considered `null`");
Issue("String variable is incorrectly considered null.");
testJSON.SetString(0, "string");
TEST_ExpectFalse(testJSON.IsNull(0));
@ -739,10 +737,10 @@ protected static function SubTest_ArrayNullGetSetRemove()
testJSON.SetNull(0);
TEST_ExpectTrue(testJSON.IsNull(0));
Issue("Null type isn't properly set by `SetNumber`");
Issue("Null type isn't properly set by `SetNumber`.");
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Null);
Issue("Null value isn't being properly removed");
Issue("Null value isn't being properly removed.");
testJSON.RemoveValue(0);
TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined);
}
@ -769,7 +767,7 @@ protected static function SubTest_ArrayMultipleVariablesStorage()
testArray = Prepare_Array();
Context("Testing how `JArray` handles adding and" @
"changing several variables");
"changing several variables.");
Issue("Stored values are compromised.");
TEST_ExpectTrue(testArray.GetNumber(0) == 10.0f);
TEST_ExpectTrue(testArray.GetString(1) == "test string");
@ -784,7 +782,7 @@ protected static function SubTest_ArrayMultipleVariablesStorage()
Issue( "After overwriting boolean value with a different type," @
"attempting go get it as a boolean gives old value," @
"instead of default");
"instead of default.");
TEST_ExpectTrue(testArray.GetBoolean(3, false) == false);
Issue("Type of the variable is incorrectly changed.");
@ -800,18 +798,18 @@ protected static function SubTest_ArrayMultipleVariablesRemoval()
// [10.0, "test string", "another string", true, 0.0, {"var": 7.0}]
Context("Testing how `JArray` handles adding and" @
"removing several variables");
Issue("Values are incorrectly removed");
"removing several variables.");
Issue("Values are incorrectly removed.");
testArray.RemoveValue(2);
// [10.0, "test string", true, 0.0, {"var": 7.0}]
Issue("Values are incorrectly removed");
Issue("Values are incorrectly removed.");
TEST_ExpectTrue(testArray.GetNumber(0) == 10.0);
TEST_ExpectTrue(testArray.GetString(1) == "test string");
TEST_ExpectTrue(testArray.GetBoolean(2) == true);
TEST_ExpectTrue(testArray.GetNumber(3) == 0.0f);
TEST_ExpectTrue(testArray.GetTypeOf(4) == JSON_Object);
Issue("First element incorrectly removed");
Issue("First element incorrectly removed.");
testArray.RemoveValue(0);
// ["test string", true, 0.0, {"var": 7.0}]
TEST_ExpectTrue(testArray.GetString(0) == "test string");
@ -820,7 +818,7 @@ protected static function SubTest_ArrayMultipleVariablesRemoval()
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Object);
TEST_ExpectTrue(testArray.GetObject(3).GetNumber("var") == 7.0);
Issue("Last element incorrectly removed");
Issue("Last element incorrectly removed.");
testArray.RemoveValue(3);
// ["test string", true, 0.0]
TEST_ExpectTrue(testArray.GetLength() == 3);
@ -828,7 +826,7 @@ protected static function SubTest_ArrayMultipleVariablesRemoval()
TEST_ExpectTrue(testArray.GetBoolean(1) == true);
TEST_ExpectTrue(testArray.GetNumber(2) == 0.0f);
Issue("Removing all elements is handled incorrectly");
Issue("Removing all elements is handled incorrectly.");
testArray.RemoveValue(0);
testArray.RemoveValue(0);
testArray.RemoveValue(0);
@ -846,8 +844,8 @@ protected static function SubTest_ArrayRemovingMultipleVariablesAtOnce()
.AddNumber(7.0);
Context("Testing how `JArray`' handles removing" @
"multiple elements at once");
Issue("Multiple values are incorrectly removed");
"multiple elements at once.");
Issue("Multiple values are incorrectly removed.");
testArray.RemoveValue(1, 2);
TEST_ExpectTrue(testArray.GetLength() == 2);
TEST_ExpectTrue(testArray.GetNumber(1) == 7.0);
@ -859,20 +857,20 @@ protected static function SubTest_ArrayRemovingMultipleVariablesAtOnce()
// Current array:
// [10.0, 7.0, 4.0, "test string", "another string", 8.0]
Issue("Last value is incorrectly removed");
Issue("Last value is incorrectly removed.");
testArray.RemoveValue(5, 1);
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetString(4) == "another string");
// Current array:
// [10.0, 7.0, 4.0, "test string", "another string"]
Issue("Tail elements are incorrectly removed");
Issue("Tail elements are incorrectly removed.");
testArray.RemoveValue(3, 4);
TEST_ExpectTrue(testArray.GetLength() == 3);
TEST_ExpectTrue(testArray.GetNumber(0) == 10.0);
TEST_ExpectTrue(testArray.GetNumber(2) == 4.0);
Issue("Array empties incorrectly");
Issue("Array empties incorrectly.");
testArray.RemoveValue(0, testArray.GetLength());
TEST_ExpectTrue(testArray.GetLength() == 0);
TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Undefined);
@ -884,15 +882,14 @@ protected static function SubTest_ArrayExpansions()
local JArray testArray;
testArray = _().json.newArray();
Context("Testing how `JArray`' handles expansions/shrinking " @
"via `SetLength()`");
Issue("`SetLength()` doesn't properly expand empty array");
Context("Testing how `JArray`' handles expansions/shrinking.");
Issue("`SetLength()` doesn't properly expand empty array.");
testArray.SetLength(2);
TEST_ExpectTrue(testArray.GetLength() == 2);
TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null);
Issue("`SetLength()` doesn't properly expand non-empty array");
Issue("`SetLength()` doesn't properly expand non-empty array.");
testArray.AddNumber(1);
testArray.SetLength(4);
TEST_ExpectTrue(testArray.GetLength() == 4);
@ -912,14 +909,14 @@ protected static function SubSubTest_ArraySetNumberExpansions()
testArray = _().json.newArray();
Context("Testing how `JArray`' handles expansions via" @
"`SetNumber()` function");
Issue("Setters don't create correct first element");
"`SetNumber()` method.");
Issue("Setters don't create correct first element.");
testArray.SetNumber(0, 1);
TEST_ExpectTrue(testArray.GetLength() == 1);
TEST_ExpectTrue(testArray.GetNumber(0) == 1);
Issue( "`SetNumber()` doesn't properly define array when setting" @
"value out-of-bounds");
"value out-of-bounds.");
testArray = _().json.newArray();
testArray.AddNumber(1);
testArray.SetNumber(4, 2);
@ -929,13 +926,6 @@ protected static function SubSubTest_ArraySetNumberExpansions()
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null);
TEST_ExpectTrue(testArray.GetNumber(4) == 2);
Issue("`SetNumber()` expands array even when it told not to");
testArray.SetNumber(6, 7, true);
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetNumber(6) == 0);
TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined);
TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined);
}
protected static function SubSubTest_ArraySetStringExpansions()
@ -944,14 +934,14 @@ protected static function SubSubTest_ArraySetStringExpansions()
testArray = _().json.newArray();
Context("Testing how `JArray`' handles expansions via" @
"`SetString()` function");
Issue("Setters don't create correct first element");
"`SetString()` method.");
Issue("Setters don't create correct first element.");
testArray.SetString(0, "str");
TEST_ExpectTrue(testArray.GetLength() == 1);
TEST_ExpectTrue(testArray.GetString(0) == "str");
Issue( "`SetString()` doesn't properly define array when setting" @
"value out-of-bounds");
"value out-of-bounds.");
testArray = _().json.newArray();
testArray.AddString("str");
testArray.SetString(4, "str2");
@ -961,13 +951,6 @@ protected static function SubSubTest_ArraySetStringExpansions()
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null);
TEST_ExpectTrue(testArray.GetString(4) == "str2");
Issue("`SetString()` expands array even when it told not to");
testArray.SetString(6, "new string", true);
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetString(6) == "");
TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined);
TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined);
}
protected static function SubSubTest_ArraySetBooleanExpansions()
@ -976,14 +959,14 @@ protected static function SubSubTest_ArraySetBooleanExpansions()
testArray = _().json.newArray();
Context("Testing how `JArray`' handles expansions via" @
"`SetBoolean()` function");
Issue("Setters don't create correct first element");
"`SetBoolean()` method.");
Issue("Setters don't create correct first element.");
testArray.SetBoolean(0, false);
TEST_ExpectTrue(testArray.GetLength() == 1);
TEST_ExpectTrue(testArray.GetBoolean(0) == false);
Issue( "`SetBoolean()` doesn't properly define array when setting" @
"value out-of-bounds");
"value out-of-bounds.");
testArray = _().json.newArray();
testArray.AddBoolean(true);
testArray.SetBoolean(4, true);
@ -993,13 +976,6 @@ protected static function SubSubTest_ArraySetBooleanExpansions()
TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null);
TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null);
TEST_ExpectTrue(testArray.GetBoolean(4) == true);
Issue("`SetBoolean()` expands array even when it told not to");
testArray.SetBoolean(6, true, true);
TEST_ExpectTrue(testArray.GetLength() == 5);
TEST_ExpectTrue(testArray.GetBoolean(6) == false);
TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined);
TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined);
}
protected static function JObject Prepare_FoldedObject()
@ -1024,7 +1000,7 @@ protected static function JObject Prepare_FoldedObject()
protected static function Test_JSONComparison()
{
Context("Testing comparison of JSON objects");
Context("Testing comparison of JSON objects.");
SubTest_JSONIsEqual();
SubTest_JSONIsSubsetOf();
SubTest_JSONCompare();
@ -1083,7 +1059,8 @@ protected static function SubTest_JSONIsSubsetOf()
TEST_ExpectTrue(empty.IsSubsetOf(test1));
TEST_ExpectFalse(test1.IsSubsetOf(empty));
Issue("`IsSubsetOf()` incorrectly handles objects that cannot be compared.");
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));
@ -1193,7 +1170,7 @@ protected static function SubTest_JSONObjectParsingWithParser()
Issue("`ParseObjectWith()` cannot parse empty JSON object.");
parsedObject = _().json.ParseObjectWith(_().text.ParseString("{}"));
TEST_ExpectNotNone(parsedObject);
TEST_ExpectTrue(parsedObject.GetKeys().length == 0);
TEST_ExpectTrue(parsedObject.GetPropertyNames().length == 0);
Issue("`ParseObjectWith()` doesn't report error when parsing an incorrect"
@ "object.");
@ -1243,7 +1220,7 @@ protected static function SubTest_JSONArrayParsingWithParser()
TEST_ExpectTrue(parsedArray.IsNull(0));
TEST_ExpectTrue(parsedArray.GetNumber(1) == 6734.9);
TEST_ExpectTrue(parsedArray.GetString(2) == "what");
TEST_ExpectTrue(parsedArray.GetObject(3).GetKeys().length == 0);
TEST_ExpectTrue(parsedArray.GetObject(3).GetPropertyNames().length == 0);
Issue("`JArray.ParseIntoSelfWith()` cannot add new elements.");
TEST_ExpectTrue(parsedArray.ParseIntoSelfWith(
@ -1258,7 +1235,7 @@ protected static function SubTest_JSONObjectParsingText()
Issue("`ParseObject()` cannot parse empty JSON object.");
parsedObject = _().json.ParseObject(_().text.FromString("{}"));
TEST_ExpectNotNone(parsedObject);
TEST_ExpectTrue(parsedObject.GetKeys().length == 0);
TEST_ExpectTrue(parsedObject.GetPropertyNames().length == 0);
Issue("`ParseObject()` doesn't report error when parsing an incorrect"
@ "object.");
@ -1304,7 +1281,7 @@ protected static function SubTest_JSONArrayParsingText()
TEST_ExpectTrue(parsedArray.IsNull(0));
TEST_ExpectTrue(parsedArray.GetNumber(1) == 6734.9);
TEST_ExpectTrue(parsedArray.GetString(2) == "what");
TEST_ExpectTrue(parsedArray.GetObject(3).GetKeys().length == 0);
TEST_ExpectTrue(parsedArray.GetObject(3).GetPropertyNames().length == 0);
Issue("`JArray.ParseIntoSelf()` cannot add new elements.");
TEST_ExpectTrue(parsedArray.ParseIntoSelf(
@ -1319,7 +1296,7 @@ protected static function SubTest_JSONObjectParsingRaw()
Issue("`ParseObjectRaw()` cannot parse empty JSON object.");
parsedObject = _().json.ParseObjectRaw(_().text.StringToRaw("{}"));
TEST_ExpectNotNone(parsedObject);
TEST_ExpectTrue(parsedObject.GetKeys().length == 0);
TEST_ExpectTrue(parsedObject.GetPropertyNames().length == 0);
Issue("`ParseObjectRaw()` doesn't report error when parsing an incorrect"
@ "object.");
@ -1369,7 +1346,7 @@ protected static function SubTest_JSONArrayParsingRaw()
TEST_ExpectTrue(parsedArray.IsNull(0));
TEST_ExpectTrue(parsedArray.GetNumber(1) == 6734.9);
TEST_ExpectTrue(parsedArray.GetString(2) == "what");
TEST_ExpectTrue(parsedArray.GetObject(3).GetKeys().length == 0);
TEST_ExpectTrue(parsedArray.GetObject(3).GetPropertyNames().length == 0);
Issue("`JArray.ParseIntoSelfRaw()` cannot add new elements.");
TEST_ExpectTrue(parsedArray.ParseIntoSelfRaw(
@ -1384,7 +1361,7 @@ protected static function SubTest_JSONObjectParsingString()
Issue("`ParseObjectString()` cannot parse empty JSON object.");
parsedObject = _().json.ParseObjectString("{}");
TEST_ExpectNotNone(parsedObject);
TEST_ExpectTrue(parsedObject.GetKeys().length == 0);
TEST_ExpectTrue(parsedObject.GetPropertyNames().length == 0);
Issue("`ParseObjectString()` doesn't report error when parsing an incorrect"
@ "object.");
@ -1427,7 +1404,7 @@ protected static function SubTest_JSONArrayParsingString()
TEST_ExpectTrue(parsedArray.IsNull(0));
TEST_ExpectTrue(parsedArray.GetNumber(1) == 6734.9);
TEST_ExpectTrue(parsedArray.GetString(2) == "what");
TEST_ExpectTrue(parsedArray.GetObject(3).GetKeys().length == 0);
TEST_ExpectTrue(parsedArray.GetObject(3).GetPropertyNames().length == 0);
Issue("`JArray.ParseIntoSelfString()` cannot add new elements.");
TEST_ExpectTrue(parsedArray.ParseIntoSelfString("[\"huh\", Null]"));

Loading…
Cancel
Save