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. * 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, * 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 * 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 * Copyright 2020 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
@ -23,11 +36,16 @@
*/ */
class JArray extends JSON; 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; 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) public final function JType GetTypeOf(int index)
{ {
if (index < 0) return JSON_Undefined; if (index < 0) return JSON_Undefined;
@ -36,22 +54,37 @@ public final function JType GetTypeOf(int index)
return data[index].type; 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() public final function int GetLength()
{ {
return data.length; 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) public final function SetLength(int newLength)
{ {
local int i; local int i;
local int oldLength; local int oldLength;
newLength = Max(0, newLength);
oldLength = data.length; oldLength = data.length;
data.length = newLength; data.length = newLength;
if (oldLength >= newLength) if (oldLength >= newLength) {
{
return; return;
} }
i = oldLength; 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 * Gets the value (as a `float`) at the index `index`, assuming it has
// and returns true/false as a result. * `JSON_Number` type.
// Getters for simple types (number, string, boolean) can have optional *
// default value specified, that will be returned if requested variable * Forms a pair with `GetInteger()` method. JSON allows to specify
// doesn't exist or has a different type. * arbitrary precision for the number variables, but UnrealScript can only
// Getters for object and array types don't take default values and * store a limited range of numeric value.
// will simply return `none`. * 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) public final function float GetNumber(int index, optional float defaultValue)
{ {
if (index < 0) return defaultValue; if (index < 0) return defaultValue;
@ -79,6 +122,24 @@ public final function float GetNumber(int index, optional float defaultValue)
return data[index].numberValue; 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) public final function float GetInteger(int index, optional float defaultValue)
{ {
if (index < 0) return defaultValue; if (index < 0) return defaultValue;
@ -88,6 +149,19 @@ public final function float GetInteger(int index, optional float defaultValue)
return data[index].numberValueAsInt; 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) public final function string GetString(int index, optional string defaultValue)
{ {
if (index < 0) return defaultValue; if (index < 0) return defaultValue;
@ -97,6 +171,24 @@ public final function string GetString(int index, optional string defaultValue)
return data[index].stringValue; 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( public final function class<Object> GetClass(
int index, int index,
optional class<Object> defaultValue) optional class<Object> defaultValue)
@ -112,6 +204,19 @@ public final function class<Object> GetClass(
return defaultValue; 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) public final function bool GetBoolean(int index, optional bool defaultValue)
{ {
if (index < 0) return defaultValue; if (index < 0) return defaultValue;
@ -121,6 +226,15 @@ public final function bool GetBoolean(int index, optional bool defaultValue)
return data[index].booleanValue; 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) public final function bool IsNull(int index)
{ {
if (index < 0) return false; if (index < 0) return false;
@ -129,6 +243,15 @@ public final function bool IsNull(int index)
return (data[index].type == JSON_Null); 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) public final function JArray GetArray(int index)
{ {
if (index < 0) return none; if (index < 0) return none;
@ -138,6 +261,15 @@ public final function JArray GetArray(int index)
return JArray(data[index].complexValue); 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) public final function JObject GetObject(int index)
{ {
if (index < 0) return none; if (index < 0) return none;
@ -147,34 +279,35 @@ public final function JObject GetObject(int index)
return JObject(data[index].complexValue); return JObject(data[index].complexValue);
} }
// Following functions provide simple setters for boolean, string, number /**
// and null values. * Sets the number value (as `float`) at the index `index`, erasing previous
// If passed index is negative - does nothing. * value (if it was recorded).
// If index lies beyond array length (`>= GetLength()`), - *
// these functions will expand array in the same way as `GetLength()` function. * If negative index is given - does nothing.
// This can be prevented by setting optional parameter `preventExpansion` to * If given index is too large (`>= GetLength()`) then array will be
// `false` (nothing will be done in this case). * extended, setting values at new indices (except specified `index`)
// They return object itself, allowing user to chain calls like this: * to "null" value (`JSON_Null`).
// `array.SetNumber("num1", 1).SetNumber("num2", 2);`. *
public final function JArray SetNumber( * Forms a pair with `SetInteger()` method.
int index, * While JSON standard allows to store numbers with arbitrary precision,
float value, * UnrealScript's types have a limited range.
optional bool preventExpansion * 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; local JStorageAtom newStorageValue;
if (index < 0) return self; if (index < 0) return self;
if (index >= data.length) if (index >= data.length) {
{ SetLength(index + 1);
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
} }
newStorageValue.type = JSON_Number; newStorageValue.type = JSON_Number;
newStorageValue.numberValue = value; newStorageValue.numberValue = value;
@ -184,25 +317,35 @@ public final function JArray SetNumber(
return self; return self;
} }
public final function JArray SetInteger( /**
int index, * Sets the number value (as `int`) at the index `index`, erasing previous
int value, * value (if it was recorded).
optional bool preventExpansion *
) * 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; local JStorageAtom newStorageValue;
if (index < 0) return self; if (index < 0) return self;
if (index >= data.length) if (index >= data.length) {
{ SetLength(index + 1);
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
} }
newStorageValue.type = JSON_Number; newStorageValue.type = JSON_Number;
newStorageValue.numberValue = float(value); newStorageValue.numberValue = float(value);
@ -212,26 +355,28 @@ public final function JArray SetInteger(
return self; return self;
} }
public final function JArray SetString /**
( * Sets the string value at the given index `index`, erasing previous value
int index, * (if it was recorded).
string value, *
optional bool preventExpansion * 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; local JStorageAtom newStorageValue;
if (index < 0) return self; if (index < 0) return self;
if (index >= data.length) if (index >= data.length) {
{ SetLength(index + 1);
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
} }
newStorageValue.type = JSON_String; newStorageValue.type = JSON_String;
newStorageValue.stringValue = value; newStorageValue.stringValue = value;
@ -239,25 +384,33 @@ public final function JArray SetString
return self; return self;
} }
public final function JArray SetClass( /**
int index, * Sets the string value, corresponding to a given class `value`,
class<Object> value, * at the index `index`, erasing previous value (if it was recorded).
optional bool preventExpansion *
) * 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; local JStorageAtom newStorageValue;
if (index < 0) return self; if (index < 0) return self;
if (index >= data.length) if (index >= data.length) {
{ SetLength(index + 1);
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
} }
newStorageValue.type = JSON_String; newStorageValue.type = JSON_String;
newStorageValue.stringValue = string(value); newStorageValue.stringValue = string(value);
@ -266,26 +419,26 @@ public final function JArray SetClass(
return self; return self;
} }
public final function JArray SetBoolean /**
( * Sets the boolean value at the given index `index`, erasing previous value
int index, * (if it was recorded).
bool value, *
optional bool preventExpansion * 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; local JStorageAtom newStorageValue;
if (index < 0) return self; if (index < 0) return self;
if (index >= data.length) if (index >= data.length) {
{ SetLength(index + 1);
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
} }
newStorageValue.type = JSON_Boolean; newStorageValue.type = JSON_Boolean;
newStorageValue.booleanValue = value; newStorageValue.booleanValue = value;
@ -293,51 +446,58 @@ public final function JArray SetBoolean
return self; return self;
} }
public final function JArray SetNull /**
( * Sets the value at the given index `index` to be "null" (`JSON_Null`),
int index, * erasing previous value (if it was recorded).
optional bool preventExpansion *
) * 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; local JStorageAtom newStorageValue;
if (index < 0) return self; if (index < 0) return self;
if (index >= data.length) if (index >= data.length) {
{ SetLength(index + 1);
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
} }
newStorageValue.type = JSON_Null; newStorageValue.type = JSON_Null;
data[index] = newStorageValue; data[index] = newStorageValue;
return self; return self;
} }
public final function JArray SetArray( /**
int index, * Sets the value at the given index `index` to store `JArray` object
JArray template, * (JSON array type).
optional bool preventExpansion *
) * 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; local JStorageAtom newStorageValue;
if (index < 0) return self; if (index < 0) return self;
if (template == none) return self; if (template == none) return self;
if (index >= data.length) if (index >= data.length) {
{ SetLength(index + 1);
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
} }
newStorageValue.type = JSON_Array; newStorageValue.type = JSON_Array;
newStorageValue.complexValue = template.Clone(); newStorageValue.complexValue = template.Clone();
@ -345,26 +505,33 @@ public final function JArray SetArray(
return self; return self;
} }
public final function JArray SetObject( /**
int index, * Sets the value at the given index `index` to store `JObject` object
JObject template, * (JSON object type).
optional bool preventExpansion *
) * 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; local JStorageAtom newStorageValue;
if (index < 0) return self; if (index < 0) return self;
if (template == none) return self; if (template == none) return self;
if (index >= data.length) if (index >= data.length) {
{ SetLength(index + 1);
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
} }
newStorageValue.type = JSON_Object; newStorageValue.type = JSON_Object;
newStorageValue.complexValue = template.Clone(); newStorageValue.complexValue = template.Clone();
@ -372,34 +539,27 @@ public final function JArray SetObject(
return self; 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. * Sets the value oat the given index `index` to store a new
// If passed index is negative - does nothing. * `JArray` object (JSON array type).
// If index lies beyond array length (`>= GetLength()`), - *
// these functions will expand array in the same way as `GetLength()` function. * See also `SetArray()` method.
// This can be prevented by setting optional parameter `preventExpansion` to *
// `false` (nothing will be done in this case). * If negative index is given - does nothing.
// They return object itself, allowing user to chain calls like this: * If given index is too large (`>= GetLength()`) then array will be
// `array.CreateObject("sub object").CreateArray("sub array");`. * extended, setting values at new indices (except specified `index`)
public final function JArray CreateArray * to "null" value (`JSON_Null`).
( *
int index, * @param index Index at which to create a new `JArray`.
optional bool preventExpansion * @return Reference to the caller object, to allow for function chaining.
) */
public final function JArray CreateArray(int index)
{ {
local JStorageAtom newStorageValue; local JStorageAtom newStorageValue;
if (index < 0) return self; if (index < 0) return self;
if (index >= data.length) if (index >= data.length) {
{ SetLength(index + 1);
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
} }
newStorageValue.type = JSON_Array; newStorageValue.type = JSON_Array;
newStorageValue.complexValue = _.json.newArray(); newStorageValue.complexValue = _.json.newArray();
@ -407,25 +567,27 @@ public final function JArray CreateArray
return self; return self;
} }
public final function JArray CreateObject /**
( * Sets the value oat the given index `index` to store a new
int index, * `JObject` object (JSON object type).
optional bool preventExpansion *
) * 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; local JStorageAtom newStorageValue;
if (index < 0) return self; if (index < 0) return self;
if (index >= data.length) if (index >= data.length) {
{ SetLength(index + 1);
if (preventExpansion)
{
return self;
}
else
{
SetLength(index + 1);
}
} }
newStorageValue.type = JSON_Object; newStorageValue.type = JSON_Object;
newStorageValue.complexValue = _.json.newObject(); newStorageValue.complexValue = _.json.newObject();
@ -433,43 +595,116 @@ public final function JArray CreateObject
return self; 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) public final function JArray AddNumber(float value)
{ {
return SetNumber(data.length, 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) public final function JArray AddInteger(int value)
{ {
return SetInteger(data.length, 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) public final function JArray AddString(string value)
{ {
return SetString(data.length, 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) public final function JArray AddClass(class<Object> value)
{ {
return SetClass(data.length, 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) public final function JArray AddBoolean(bool value)
{ {
return SetBoolean(data.length, 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() public final function JArray AddNull()
{ {
return SetNull(data.length); 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() public final function JArray AddArray()
{ {
return CreateArray(data.length); 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() public final function JArray AddObject()
{ {
return CreateObject(data.length); return CreateObject(data.length);
@ -479,6 +714,16 @@ public final function JArray AddObject()
// a given index. // a given index.
// If `index` falls outside array boundaries - nothing will be done. // If `index` falls outside array boundaries - nothing will be done.
// Returns `true` if value was actually removed and `false` if it didn't exist. // Returns `true` if value was actually removed and `false` if it didn't exist.
/**
* 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) public final function bool RemoveValue(int index, optional int amount)
{ {
local int i; local int i;
@ -497,11 +742,21 @@ public final function bool RemoveValue(int index, optional int amount)
return true; return true;
} }
/**
* Completely clears caller `JObject` of all values.
*/
public function Clear() public function Clear()
{ {
RemoveValue(0, data.length); 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) public function bool IsSubsetOf(JSON rightValue)
{ {
local int i; local int i;
@ -520,6 +775,12 @@ public function bool IsSubsetOf(JSON rightValue)
return true; 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() public function JSON Clone()
{ {
local int i; local int i;
@ -545,6 +806,31 @@ public function JSON Clone()
return clonedArray; 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) public function bool ParseIntoSelfWith(Parser parser)
{ {
local bool parsingSucceeded; local bool parsingSucceeded;
@ -586,6 +872,8 @@ public function bool ParseIntoSelfWith(Parser parser)
return parsingSucceeded; return parsingSucceeded;
} }
// Either cleans up or adds a list of parsed values,
// depending on whether parsing was successful or not.
private function HandleParsedAtoms( private function HandleParsedAtoms(
array<JStorageAtom> parsedAtoms, array<JStorageAtom> parsedAtoms,
bool parsingSucceeded) 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) public function string DisplayWith(JSONDisplaySettings displaySettings)
{ {
local int i; local int i;

457
sources/Data/JSON/JObject.uc

@ -1,22 +1,19 @@
/** /**
* This class implements JSON object storage capabilities. * 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: * It stores name-value pairs, where names are strings and values can be:
* ~ Boolean, string, null or number (float in this implementation) data; * ~ Boolean, string, null or number (float in this implementation) data;
* ~ Other JSON objects; * ~ Other JSON objects;
* ~ JSON Arrays (see `JArray` class). * ~ JSON Arrays (see `JArray` class).
* *
* This implementation provides getters and setters for boolean, string, * This implementation provides a variety of functionality,
* null or number types that allow to freely set and fetch their values * including parsing, displaying, getters and setters for JSON types that
* by name. * allow to freely set and fetch their values by name.
* JSON objects and arrays can be fetched by getters, but you cannot * 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 * add existing object or array to another object. Instead one has to either
* a new, empty object with a certain name and then fill it with data. * clone existing object or create an empty one and then manually fill
* This allows to avoid loop situations, where object is contained in itself. * with data.
* Functions to remove existing values are also provided and are applicable * This allows to avoid loop situations, where object is
* to all variable types. * contained in itself.
* Setters can also be used to overwrite any value by a different value,
* even of a different type.
* Copyright 2020 Anton Tarasenko * Copyright 2020 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * 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 // that will be enforced if user requires something outside those bounds
var private config const int MINIMUM_CAPACITY; var private config const int MINIMUM_CAPACITY;
var private config const int MAXIMUM_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 MINIMUM_DENSITY;
var private config const float MAXIMUM_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; 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; var private config const int ABSOLUTE_LOWER_CAPACITY_LIMIT;
// Helper method that is needed as a replacement for `%`, since it is // 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 * Returns `JType` of a property with a given name in our collection.
// in this object, since if such variable does not exist - *
// function will return `JSON_Undefined`. * 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) public final function JType GetTypeOf(string name)
{ {
local JProperty property; local JProperty property;
@ -226,14 +240,23 @@ public final function JType GetTypeOf(string name)
return property.value.type; return property.value.type;
} }
// Following functions are getters for various types of variables. /**
// Getter for null value simply checks if it's null * Gets the value (as a `float`) of a property by the name `name`,
// and returns true/false as a result. * assuming it has `JSON_Number` type.
// Getters for simple types (number, string, boolean) can have optional *
// default value specified, that will be returned if requested variable * Forms a pair with `GetInteger()` method. JSON allows to specify
// doesn't exist or has a different type. * arbitrary precision for the number variables, but UnrealScript can only
// Getters for object and array types don't take default values and * store a limited range of numeric value.
// will simply return `none`. * 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) public final function float GetNumber(string name, optional float defaultValue)
{ {
local JProperty property; local JProperty property;
@ -244,6 +267,23 @@ public final function float GetNumber(string name, optional float defaultValue)
return property.value.numberValue; 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) public final function int GetInteger(string name, optional int defaultValue)
{ {
local JProperty property; local JProperty property;
@ -254,6 +294,19 @@ public final function int GetInteger(string name, optional int defaultValue)
return property.value.numberValueAsInt; 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( public final function string GetString(
string name, string name,
optional string defaultValue optional string defaultValue
@ -267,6 +320,23 @@ public final function string GetString(
return property.value.stringValue; 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( public final function class<Object> GetClass(
string name, string name,
optional class<Object> defaultValue optional class<Object> defaultValue
@ -284,6 +354,17 @@ public final function class<Object> GetClass(
return defaultValue; 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) public final function bool GetBoolean(string name, optional bool defaultValue)
{ {
local JProperty property; local JProperty property;
@ -294,6 +375,15 @@ public final function bool GetBoolean(string name, optional bool defaultValue)
return property.value.booleanValue; 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) public final function bool IsNull(string name)
{ {
local JProperty property; local JProperty property;
@ -301,6 +391,15 @@ public final function bool IsNull(string name)
return (property.value.type == JSON_Null); 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) public final function JArray GetArray(string name)
{ {
local JProperty property; local JProperty property;
@ -311,6 +410,15 @@ public final function JArray GetArray(string name)
return JArray(property.value.complexValue); 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) public final function JObject GetObject(string name)
{ {
local JProperty property; local JProperty property;
@ -319,10 +427,25 @@ public final function JObject GetObject(string name)
return JObject(property.value.complexValue); return JObject(property.value.complexValue);
} }
// Following functions provide simple setters for boolean, string, number /**
// and null values. * Sets the number value (as `float`) of a property by the name `name`,
// They return object itself, allowing user to chain calls like this: * erasing previous value (if it was recorded).
// `object.SetNumber("num1", 1).SetNumber("num2", 2);`. *
* 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) public final function JObject SetNumber(string name, float value)
{ {
local JProperty property; local JProperty property;
@ -336,6 +459,25 @@ public final function JObject SetNumber(string name, float value)
return self; 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) public final function JObject SetInteger(string name, int value)
{ {
local JProperty property; local JProperty property;
@ -349,6 +491,18 @@ public final function JObject SetInteger(string name, int value)
return self; 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) public final function JObject SetString(string name, string value)
{ {
local JProperty property; local JProperty property;
@ -362,6 +516,22 @@ public final function JObject SetString(string name, string value)
return self; 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) public final function JObject SetClass(string name, class<Object> value)
{ {
local JProperty property; local JProperty property;
@ -375,6 +545,16 @@ public final function JObject SetClass(string name, class<Object> value)
return self; 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) public final function JObject SetBoolean(string name, bool value)
{ {
local JProperty property; local JProperty property;
@ -386,6 +566,15 @@ public final function JObject SetBoolean(string name, bool value)
return self; 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) public final function JObject SetNull(string name)
{ {
local JProperty property; local JProperty property;
@ -396,6 +585,20 @@ public final function JObject SetNull(string name)
return self; 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) public final function JObject SetArray(string name, JArray template)
{ {
local JProperty property; local JProperty property;
@ -410,6 +613,20 @@ public final function JObject SetArray(string name, JArray template)
return self; 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) public final function JObject SetObject(string name, JObject template)
{ {
local JProperty property; local JProperty property;
@ -424,10 +641,15 @@ public final function JObject SetObject(string name, JObject template)
return self; 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. * Sets the value of a property by the name `name` to store a new
// They return object itself, allowing user to chain calls like this: * `JArray` object (JSON array type).
// `object.CreateObject("folded object").CreateArray("names list");`. *
* 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) public final function JObject CreateArray(string name)
{ {
local JProperty property; local JProperty property;
@ -441,6 +663,15 @@ public final function JObject CreateArray(string name)
return self; 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) public final function JObject CreateObject(string name)
{ {
local JProperty property; local JProperty property;
@ -454,15 +685,44 @@ public final function JObject CreateObject(string name)
return self; 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) public final function JObject RemoveValue(string name)
{ {
RemoveProperty(name); RemoveProperty(name);
return self; 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 int i, j;
local array<string> result; local array<string> result;
@ -477,6 +737,13 @@ public final function array<string> GetKeys()
return result; 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) public function bool IsSubsetOf(JSON rightJSON)
{ {
local int i, j; local int i, j;
@ -501,6 +768,12 @@ public function bool IsSubsetOf(JSON rightJSON)
return true; 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() public function JSON Clone()
{ {
local int i, j; local int i, j;
@ -534,6 +807,32 @@ public function JSON Clone()
return clonedObject; 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) public function bool ParseIntoSelfWith(Parser parser)
{ {
local bool parsingSucceeded; local bool parsingSucceeded;
@ -542,6 +841,7 @@ public function bool ParseIntoSelfWith(Parser parser)
local array<JProperty> parsedProperties; local array<JProperty> parsedProperties;
if (parser == none) return false; if (parser == none) return false;
initState = parser.GetCurrentState(); initState = parser.GetCurrentState();
// Ensure that parser starts pointing at what looks like a JSON object
confirmedState = parser.Skip().Match("{").GetCurrentState(); confirmedState = parser.Skip().Match("{").GetCurrentState();
if (!parser.Ok()) if (!parser.Ok())
{ {
@ -551,6 +851,8 @@ public function bool ParseIntoSelfWith(Parser parser)
while (parser.Ok() && !parser.HasFinished()) while (parser.Ok() && !parser.HasFinished())
{ {
confirmedState = parser.Skip().GetCurrentState(); 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()) if (parser.Match("}").Ok())
{ {
parsingSucceeded = true; parsingSucceeded = true;
@ -560,9 +862,12 @@ public function bool ParseIntoSelfWith(Parser parser)
&& !parser.RestoreState(confirmedState).Match(",").Skip().Ok()) { && !parser.RestoreState(confirmedState).Match(",").Skip().Ok()) {
break; break;
} }
// Recover after failed `Match("}")` on the first cycle
// (`parsedProperties.length == 0`)
else if (parser.Ok()) { else if (parser.Ok()) {
confirmedState = parser.GetCurrentState(); confirmedState = parser.GetCurrentState();
} }
// Parse property
parser.RestoreState(confirmedState).Skip(); parser.RestoreState(confirmedState).Skip();
parser.MStringLiteral(nextProperty.name).Skip().Match(":"); parser.MStringLiteral(nextProperty.name).Skip().Match(":");
nextProperty.value = ParseAtom(parser.Skip()); nextProperty.value = ParseAtom(parser.Skip());
@ -578,6 +883,8 @@ public function bool ParseIntoSelfWith(Parser parser)
return parsingSucceeded; return parsingSucceeded;
} }
// Either cleans up or adds a list of parsed properties,
// depending on whether parsing was successful or not.
private function HandleParsedProperties( private function HandleParsedProperties(
array<JProperty> parsedProperties, array<JProperty> parsedProperties,
bool parsingSucceeded) 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) public function string DisplayWith(JSONDisplaySettings displaySettings)
{ {
local int i, j; local int i, j;
@ -613,19 +930,7 @@ public function string DisplayWith(JSONDisplaySettings displaySettings)
else { else {
innerSettings = displaySettings; innerSettings = displaySettings;
} }
// Prepare delimiters using appropriate indentation rules GetBraces(openingBraces, closingBraces, displaySettings, innerSettings);
// 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;
propertiesSeparator = "," $ innerSettings.afterObjectComma; propertiesSeparator = "," $ innerSettings.afterObjectComma;
if (innerSettings.colored) { if (innerSettings.colored) {
propertiesSeparator = "{$json_comma" @ propertiesSeparator $ "}"; propertiesSeparator = "{$json_comma" @ propertiesSeparator $ "}";
@ -648,6 +953,49 @@ public function string DisplayWith(JSONDisplaySettings displaySettings)
return openingBraces $ contents $ closingBraces; 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( protected function string DisplayProperty(
JProperty toDisplay, JProperty toDisplay,
JSONDisplaySettings displaySettings) JSONDisplaySettings displaySettings)
@ -667,21 +1015,6 @@ protected function string DisplayProperty(
$ displaySettings.afterPropertyValue); $ 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 defaultproperties
{ {
ABSOLUTE_LOWER_CAPACITY_LIMIT = 10 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 * that uses human-readable text to store and transmit data objects
* consisting of name–value pairs and array data types. * consisting of name–value pairs and array data types.
* For more information refer to https://en.wikipedia.org/wiki/JSON * For more information refer to https://en.wikipedia.org/wiki/JSON
* This is a base class for implementation of JSON data storage for Acedia. * This is a base class for implementation of JSON objects and arrays
* It does not implement parsing and printing from/into human-readable * for Acedia.
* text representation, just provides means to store such information.
* *
* JSON data is stored as an object (represented via `JSONObject`) that * JSON data is stored as an object (represented via `JSONObject`) that
* contains a set of name-value pairs, where value can be * 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/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
class JSON extends AcediaActor class JSON extends AcediaActor
abstract; abstract
config(AcediaSystem);
// Enumeration for possible types of JSON values. /**
* Enumeration for possible types of JSON values.
*/
enum JType enum JType
{ {
// Technical type, used to indicate that requested value is missing. // Technical type, used to indicate that requested value is missing.
@ -53,7 +55,9 @@ enum JType
JSON_Object JSON_Object
}; };
// Stores a single JSON value /**
* Represents a single JSON value.
*/
struct JStorageAtom struct JStorageAtom
{ {
// What type is stored exactly? // What type is stored exactly?
@ -77,44 +81,96 @@ struct JStorageAtom
var bool classLoadingWasAttempted; 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 enum JComparisonResult
{ {
// Containers contain different sets of values and
// neither can be considered a subset of another.
JCR_Incomparable, JCR_Incomparable,
// "Left" container is a subset of the "right" one.
JCR_SubSet, JCR_SubSet,
// "Right" container is a subset of the "left" one.
JCR_Overset, JCR_Overset,
// Both objects are identical.
JCR_Equal JCR_Equal
}; };
/**
* Describes how JSON containers are supposed to be displayed.
*/
struct JSONDisplaySettings struct JSONDisplaySettings
{ {
// Should it be displayed as a formatted string, with added color tags?
var bool colored; var bool colored;
// Should we "stack" indentation of folded objects?
var bool stackIndentation; var bool stackIndentation;
// Indentation for elements in object/array
var string subObjectIndentation, subArrayIndentation; var string subObjectIndentation, subArrayIndentation;
// Strings to put immediately before and after object opening: '{'
var string beforeObjectOpening, afterObjectOpening; var string beforeObjectOpening, afterObjectOpening;
// Strings to put immediately before and after object closing: '}'
var string beforeObjectEnding, afterObjectEnding; var string beforeObjectEnding, afterObjectEnding;
// {<beforePropertyName>"name"<afterPropertyName>:value}
var string beforePropertyName, afterPropertyName; var string beforePropertyName, afterPropertyName;
// {"name":<beforePropertyValue>value<afterPropertyValue>}
var string beforePropertyValue, 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; var string afterObjectComma;
// Strings to put immediately before and after array opening: '['
var string beforeArrayOpening, afterArrayOpening; var string beforeArrayOpening, afterArrayOpening;
// Strings to put immediately before and after array closing: ']'
var string beforeArrayEnding, afterArrayEnding; var string beforeArrayEnding, afterArrayEnding;
// [<beforeElement>element1<afterElement>,<afterArrayComma>...]
var string beforeElement, afterElement; var string beforeElement, afterElement;
// Can be used to break line after each property record
var string afterArrayComma; 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(){} public function Clear(){}
/**
* Makes an exact copy of the caller JSON container.
*
* @return Copy of the caller JSON container object.
*/
public function JSON Clone() public function JSON Clone()
{ {
return none; 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) public function bool IsSubsetOf(JSON rightJSON)
{ {
return false; 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) public final function JComparisonResult Compare(JSON rightJSON)
{ {
local bool firstIsSubset, secondIsSubset; 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) public final function bool IsEqual(JSON rightJSON)
{ {
return (Compare(rightJSON) == JCR_Equal); 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( protected final function bool AreAtomsEqual(
JStorageAtom atom1, JStorageAtom atom1,
JStorageAtom atom2) JStorageAtom atom2)
@ -171,6 +417,11 @@ protected final function bool AreAtomsEqual(
return atom1.complexValue.IsEqual(atom2.complexValue); 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) protected final function TryLoadingStringAsClass(out JStorageAtom atom)
{ {
if (atom.classLoadingWasAttempted) return; if (atom.classLoadingWasAttempted) return;
@ -179,26 +430,18 @@ protected final function TryLoadingStringAsClass(out JStorageAtom atom)
class<Object>(DynamicLoadObject(atom.stringValue, class'Class', true)); class<Object>(DynamicLoadObject(atom.stringValue, class'Class', true));
} }
public final function string Display( /**
optional bool fancyPrinting, * Displays a `JStorageAtom` in it's appropriate text JSON representation.
optional bool colorSettings) *
{ * That's a representation that can be pasted inside JSON array as-is
local JSONDisplaySettings settingsToUse; * (with different values separated by commas).
// Settings are minimal by default *
if (fancyPrinting) { * @param atom Atom to display.
settingsToUse = GetFancySettings(); * @param displaySettings Display settings, according to which to
} * display the atom.
if (colorSettings) { * @return Text representation of the passed `atom`, empty if it's of
settingsToUse.colored = true; * the type `JSON_Undefined`.
} */
return DisplayWith(settingsToUse);
}
public function string DisplayWith(JSONDisplaySettings displaySettings)
{
return "";
}
protected final function string DisplayAtom( protected final function string DisplayAtom(
JStorageAtom atom, JStorageAtom atom,
JSONDisplaySettings displaySettings) JSONDisplaySettings displaySettings)
@ -241,7 +484,9 @@ protected final function string DisplayAtom(
return result; 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 integerPart, fractionalPart;
local int precision; local int precision;
@ -256,23 +501,29 @@ protected final function string DisplayFloat(float number)
integerPart = number; integerPart = number;
result $= string(integerPart); result $= string(integerPart);
number = (number - 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)); fractionalPart = Round(number * (10 ** precision));
if (fractionalPart <= 0) { if (fractionalPart <= 0) {
return result; return result;
} }
result $= "."; result $= ".";
// Pad necessary zeroes in front
howManyZeroes = precision - CountDigits(fractionalPart); howManyZeroes = precision - CountDigits(fractionalPart);
while (fractionalPart > 0 && fractionalPart % 10 == 0) {
fractionalPart /= 10;
}
while (howManyZeroes > 0) { while (howManyZeroes > 0) {
zeroes $= "0"; zeroes $= "0";
howManyZeroes -= 1; howManyZeroes -= 1;
} }
// Cut off trailing zeroes and
while (fractionalPart > 0 && fractionalPart % 10 == 0) {
fractionalPart /= 10;
}
return result $ zeroes $ string(fractionalPart); 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; local int digitCounter;
while (number > 0) while (number > 0)
@ -284,23 +535,15 @@ protected final function int CountDigits(int number)
return digitCounter; return digitCounter;
} }
protected final function JSONDisplaySettings GetFancySettings() /**
{ * Prepares a `string` to be displayed as textual JSON representation by
local string lineFeed; * replacing certain characters with their escaped sequences.
local JSONDisplaySettings fancySettings; *
lineFeed = Chr(10); * @param input String value to display inside a text representation of
fancySettings.stackIndentation = true; * a JSON data.
fancySettings.subObjectIndentation = " "; * @result Representation of an `input` that can be included in text form of
fancySettings.subArrayIndentation = ""; * JSON data.
fancySettings.afterObjectOpening = lineFeed; */
fancySettings.beforeObjectEnding = lineFeed;
fancySettings.beforePropertyValue = " ";
fancySettings.afterObjectComma = lineFeed;
fancySettings.beforeElement = " ";
fancySettings.afterArrayComma = " ";
return fancySettings;
}
protected final function string DisplayJSONString(string input) protected final function string DisplayJSONString(string input)
{ {
// Convert control characters (+ other, specified by JSON) // Convert control characters (+ other, specified by JSON)
@ -317,6 +560,36 @@ protected final function string DisplayJSONString(string input)
return ("\"" $ 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( protected final function JSONDisplaySettings IndentSettings(
JSONDisplaySettings inputSettings, JSONDisplaySettings inputSettings,
optional bool indentingArray) optional bool indentingArray)
@ -354,41 +627,19 @@ protected final function JSONDisplaySettings IndentSettings(
return indentedSettings; return indentedSettings;
} }
public function bool ParseIntoSelfWith(Parser parser) /**
{ * Uses given parser to parse a single (possibly complex like JSON object
return false; * or array) JSON value.
} *
* @param parser Parser that method would use to parse JSON value from
public final function bool ParseIntoSelf(Text source) * wherever it left. It's confirmed will not be changed, but if parsing
{ * was successful, - it will point at the next available character.
local bool successfullyParsed; * Do not treat `parser` being in a non-failed state as a confirmation of
local Parser jsonParser; * successful parsing: JSON parsing might fail regardless.
jsonParser = _.text.Parse(source); * Check return value for that.
successfullyParsed = ParseIntoSelfWith(jsonParser); * @return Parsed JSON value as `JStorageAtom`.
_.memory.Free(jsonParser); * If parsing has failed it will have the `JSON_Undefined` type.
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;
}
protected final function JStorageAtom ParseAtom(Parser parser) protected final function JStorageAtom ParseAtom(Parser parser)
{ {
local Parser.ParserState initState; local Parser.ParserState initState;
@ -396,41 +647,55 @@ protected final function JStorageAtom ParseAtom(Parser parser)
if (parser == none) return newAtom; if (parser == none) return newAtom;
if (!parser.Ok()) return newAtom; if (!parser.Ok()) return newAtom;
initState = parser.GetCurrentState(); initState = parser.GetCurrentState();
parser.Skip().Confirm();
if (parser.MStringLiteral(newAtom.stringValue).Ok()) if (parser.MStringLiteral(newAtom.stringValue).Ok())
{ {
newAtom.type = JSON_String; newAtom.type = JSON_String;
return newAtom; return newAtom;
} }
newAtom = ParseLiteral(parser.R()); newAtom = ParseLiteral(parser.RestoreState(initState));
if (newAtom.type != JSON_Undefined) { if (newAtom.type != JSON_Undefined) {
return newAtom; return newAtom;
} }
newAtom = ParseComplex(parser.R()); newAtom = ParseComplex(parser.RestoreState(initState));
if (newAtom.type != JSON_Undefined) { if (newAtom.type != JSON_Undefined) {
return newAtom; return newAtom;
} }
newAtom = ParseNumber(parser.R()); newAtom = ParseNumber(parser.RestoreState(initState));
if (newAtom.type == JSON_Undefined) { if (newAtom.type == JSON_Undefined) {
parser.RestoreState(initState); parser.RestoreState(initState);
} }
return newAtom; 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) 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()) if (parser.Match("null", true).Ok())
{ {
newAtom.type = JSON_Null; newAtom.type = JSON_Null;
return newAtom; return newAtom;
} }
if (parser.R().Match("false", true).Ok()) if (parser.RestoreState(initState).Match("false", true).Ok())
{ {
newAtom.type = JSON_Boolean; newAtom.type = JSON_Boolean;
return newAtom; return newAtom;
} }
if (parser.R().Match("true", true).Ok()) if (parser.RestoreState(initState).Match("true", true).Ok())
{ {
newAtom.type = JSON_Boolean; newAtom.type = JSON_Boolean;
newAtom.booleanValue = true; 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) protected final function JStorageAtom ParseComplex(Parser parser)
{ {
local JStorageAtom newAtom; local JStorageAtom newAtom;
local Parser.ParserState initState;
initState = parser.GetCurrentState();
if (parser.Match("{").Ok()) if (parser.Match("{").Ok())
{ {
newAtom.complexValue = _.json.NewObject(); newAtom.complexValue = _.json.NewObject();
newAtom.type = JSON_Object; newAtom.type = JSON_Object;
} }
else if (parser.R().Match("[").Ok()) else if (parser.RestoreState(initState).Match("[").Ok())
{ {
newAtom.complexValue = _.json.NewArray(); newAtom.complexValue = _.json.NewArray();
newAtom.type = JSON_Array; newAtom.type = JSON_Array;
} }
parser.RestoreState(initState);
if ( newAtom.complexValue != none if ( newAtom.complexValue != none
&& newAtom.complexValue.ParseIntoSelfWith(parser.R())) { && newAtom.complexValue.ParseIntoSelfWith(parser)) {
return newAtom; return newAtom;
} }
newAtom.type = JSON_Undefined; newAtom.type = JSON_Undefined;
@ -460,10 +740,23 @@ protected final function JStorageAtom ParseComplex(Parser parser)
return newAtom; 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) protected final function JStorageAtom ParseNumber(Parser parser)
{ {
local JStorageAtom newAtom; local JStorageAtom newAtom;
local Parser.ParserState integerParsedState; local Parser.ParserState initState, integerParsedState;
initState = parser.GetCurrentState();
if (!parser.MInteger(newAtom.numberValueAsInt).Ok()) { if (!parser.MInteger(newAtom.numberValueAsInt).Ok()) {
return newAtom; return newAtom;
} }
@ -474,7 +767,7 @@ protected final function JStorageAtom ParseNumber(Parser parser)
if ( parser.Match(".").Ok() if ( parser.Match(".").Ok()
|| parser.RestoreState(integerParsedState).Match("e", true).Ok()) || parser.RestoreState(integerParsedState).Match("e", true).Ok())
{ {
parser.R().MNumber(newAtom.numberValue); parser.RestoreState(initState).MNumber(newAtom.numberValue);
return newAtom; return newAtom;
} }
parser.RestoreState(integerParsedState); parser.RestoreState(integerParsedState);

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

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

Loading…
Cancel
Save