diff --git a/sources/Data/JSON/JArray.uc b/sources/Data/JSON/JArray.uc index 3108913..b113ca8 100644 --- a/sources/Data/JSON/JArray.uc +++ b/sources/Data/JSON/JArray.uc @@ -3,7 +3,20 @@ * Array stores ordered JSON values that can be referred by their index. * It can contain any mix of JSON value types and cannot have any gaps, * i.e. in array of length N, there must be a valid value for all indices - * from 0 to N-1. + * from 0 to N-1. Values of the array can beL + * ~ Boolean, string, null or number (float in this implementation) data; + * ~ Other JSON Arrays; + * ~ Other JSON objects (see `JObject` class). + * + * This implementation provides a variety of functionality, + * including parsing, displaying, getters and setters for JSON types that + * allow to freely set and fetch their values by index. + * JSON objects and arrays can be fetched by getters, but you cannot + * add existing object or array to another object. Instead one has to either + * clone existing object or create an empty one and then manually fill + * with data. + * This allows to avoid loop situations, where object is + * contained in itself. * Copyright 2020 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. @@ -23,11 +36,16 @@ */ class JArray extends JSON; -// Data will simply be stored as an array of JSON values +// Data will be stored as an array of JSON values var private array data; -// Return type of value stored at a given index. -// Returns `JSON_Undefined` if and only if given index is out of bounds. +/** + * Returns type (`JType`) of a property with a index in our collection. + * + * @param index Index of the JSON value to get the type of. + * @return Type of the property at the index `index`. + * `JSON_Undefined` iff element at that index does not exist. + */ public final function JType GetTypeOf(int index) { if (index < 0) return JSON_Undefined; @@ -36,22 +54,37 @@ public final function JType GetTypeOf(int index) return data[index].type; } -// Returns current length of this array. +/** + * Returns current length of the caller array. + * + * @return Length (amount of elements) in the caller array. + * Means that max index with recorded value is `GetLength() - 1` + * (min index is `0`). + */ public final function int GetLength() { return data.length; } -// Changes length of this array. -// In case of the increase - fills new indices with `null` values. +/** + * Changes length of the caller `JArray`. + * + * If length is decreased - variables that fit into new length will be + * preserved, others - erased. + * In case of the increase - sets values at new indices to "null". + * + * @param newLength New length of the caller `JArray`. + * Negative values will be treated as zero. + * @return Reference to the caller object, to allow for function chaining. + */ public final function SetLength(int newLength) { local int i; local int oldLength; + newLength = Max(0, newLength); oldLength = data.length; data.length = newLength; - if (oldLength >= newLength) - { + if (oldLength >= newLength) { return; } i = oldLength; @@ -62,14 +95,24 @@ public final function SetLength(int newLength) } } -// Following functions are getters for various types of variables. -// Getter for null value simply checks if it's null -// and returns true/false as a result. -// Getters for simple types (number, string, boolean) can have optional -// default value specified, that will be returned if requested variable -// doesn't exist or has a different type. -// Getters for object and array types don't take default values and -// will simply return `none`. +/** + * Gets the value (as a `float`) at the index `index`, assuming it has + * `JSON_Number` type. + * + * Forms a pair with `GetInteger()` method. JSON allows to specify + * arbitrary precision for the number variables, but UnrealScript can only + * store a limited range of numeric value. + * To alleviate this problem we store numeric JSON values as both + * `float` and `int` and can return either of the requested versions. + * + * @param index Index of the value to get; + * must be between 0 and `GetLength() - 1` inclusively. + * @param defaultValue Value to return if element does not exist or + * has a different type (can be checked by `GetTypeOf()`). + * @return Number value of the element at the index `index`, + * if it exists and has `JSON_Number` type. + * Otherwise returns passed `defaultValue`. + */ public final function float GetNumber(int index, optional float defaultValue) { if (index < 0) return defaultValue; @@ -79,6 +122,24 @@ public final function float GetNumber(int index, optional float defaultValue) return data[index].numberValue; } +/** + * Gets the value (as an `int`) at the index `index`, assuming it has + * `JSON_Number` type. + * + * Forms a pair with `GetInteger()` method. JSON allows to specify + * arbitrary precision for the number variables, but UnrealScript can only + * store a limited range of numeric value. + * To alleviate this problem we store numeric JSON values as both + * `float` and `int` and can return either of the requested versions. + * + * @param index Index of the value to get; + * must be between 0 and `GetLength() - 1` inclusively. + * @param defaultValue Value to return if element does not exist or + * has a different type (can be checked by `GetTypeOf()`). + * @return Number value of the element at the index `index`, + * if it exists and has `JSON_Number` type. + * Otherwise returns passed `defaultValue`. + */ public final function float GetInteger(int index, optional float defaultValue) { if (index < 0) return defaultValue; @@ -88,6 +149,19 @@ public final function float GetInteger(int index, optional float defaultValue) return data[index].numberValueAsInt; } +/** + * Gets the value at the index `index`, assuming it has `JSON_String` type. + * + * See also `GetClass()` method. + * + * @param index Index of the value to get; + * must be between 0 and `GetLength() - 1` inclusively. + * @param defaultValue Value to return if element does not exist or + * has a different type (can be checked by `GetTypeOf()`). + * @return String value of the element at the index `index`, + * if it exists and has `JSON_String` type. + * Otherwise returns passed `defaultValue`. + */ public final function string GetString(int index, optional string defaultValue) { if (index < 0) return defaultValue; @@ -97,6 +171,24 @@ public final function string GetString(int index, optional string defaultValue) return data[index].stringValue; } +/** + * Gets the value at the index `index` as a `class`, assuming it has + * `JSON_String` type. + * + * JSON does not support to store class data type, but we can use string type + * for that. This method attempts to load a class object from it's full name, + * (like `Engine.Actor`) recorded inside an appropriate string value. + * + * @param index Index of the value to get; + * must be between 0 and `GetLength() - 1` inclusively. + * @param defaultValue Value to return if element does not exist, + * has a different type (can be checked by `GetTypeOf()`) or not + * a valid class name. + * @return Class value of the element at the index `index`, + * if it exists, has `JSON_String` type and it represents + * a full name of some class. + * Otherwise returns passed `defaultValue`. + */ public final function class GetClass( int index, optional class defaultValue) @@ -112,6 +204,19 @@ public final function class GetClass( return defaultValue; } +/** + * Gets the value at the index `index`, assuming it has `JSON_Boolean` type. + * + * See also `GetClass()` method. + * + * @param index Index of the value to get; + * must be between 0 and `GetLength() - 1` inclusively. + * @param defaultValue Value to return if property does not exist or + * has a different type (can be checked by `GetTypeOf()`). + * @return String value of the element at the index `index`, + * if it exists and has `JSON_Boolean` type. + * Otherwise returns passed `defaultValue`. + */ public final function bool GetBoolean(int index, optional bool defaultValue) { if (index < 0) return defaultValue; @@ -121,6 +226,15 @@ public final function bool GetBoolean(int index, optional bool defaultValue) return data[index].booleanValue; } +/** + * Checks if an array element at the index `index` has `JSON_Null` type. + * + * Alternatively consider using `GetType()` method. + * + * @param index Index of the element to check for being `null`. + * @return `true` if element at the given index exists and + * has type `JSON_Null`; `false` otherwise. + */ public final function bool IsNull(int index) { if (index < 0) return false; @@ -129,6 +243,15 @@ public final function bool IsNull(int index) return (data[index].type == JSON_Null); } +/** + * Gets the value at the index `index`, assuming it has `JSON_Array` type. + * + * @param index Index of the value to check for being "null"; + * must be between 0 and `GetLength() - 1` inclusively. + * @return `JArray` object value at the given index, if it exists and + * has `JSON_Array` type. + * Otherwise returns `none`. + */ public final function JArray GetArray(int index) { if (index < 0) return none; @@ -138,6 +261,15 @@ public final function JArray GetArray(int index) return JArray(data[index].complexValue); } +/** + * Gets the value at the index `index`, assuming it has `JSON_Object` type. + * + * @param index Index of the value to check for being "null"; + * must be between 0 and `GetLength() - 1` inclusively. + * @return `JObject` object value at the given index, if it exists and + * has `JSON_Array` type. + * Otherwise returns `none`. + */ public final function JObject GetObject(int index) { if (index < 0) return none; @@ -147,34 +279,35 @@ public final function JObject GetObject(int index) return JObject(data[index].complexValue); } -// Following functions provide simple setters for boolean, string, number -// and null values. -// If passed index is negative - does nothing. -// If index lies beyond array length (`>= GetLength()`), - -// these functions will expand array in the same way as `GetLength()` function. -// This can be prevented by setting optional parameter `preventExpansion` to -// `false` (nothing will be done in this case). -// They return object itself, allowing user to chain calls like this: -// `array.SetNumber("num1", 1).SetNumber("num2", 2);`. -public final function JArray SetNumber( - int index, - float value, - optional bool preventExpansion -) +/** + * Sets the number value (as `float`) at the index `index`, erasing previous + * value (if it was recorded). + * + * If negative index is given - does nothing. + * If given index is too large (`>= GetLength()`) then array will be + * extended, setting values at new indices (except specified `index`) + * to "null" value (`JSON_Null`). + * + * Forms a pair with `SetInteger()` method. + * While JSON standard allows to store numbers with arbitrary precision, + * UnrealScript's types have a limited range. + * To alleviate this problem we store numbers in both `float`- and + * `int`-type variables to extended supported range of values. + * So if you need to store a number with fractional part, you should + * prefer `SetNumber()` and for integer values `SetInteger()` is preferable. + * Both will record a value of type `JSON_Number`. + * + * @param index Index at which to set given numeric value. + * @param value Value to set at given index. + * @return Reference to the caller object, to allow for function chaining. + */ +public final function JArray SetNumber(int index, float value) { local JStorageAtom newStorageValue; if (index < 0) return self; - if (index >= data.length) - { - if (preventExpansion) - { - return self; - } - else - { - SetLength(index + 1); - } + if (index >= data.length) { + SetLength(index + 1); } newStorageValue.type = JSON_Number; newStorageValue.numberValue = value; @@ -184,25 +317,35 @@ public final function JArray SetNumber( return self; } -public final function JArray SetInteger( - int index, - int value, - optional bool preventExpansion -) +/** + * Sets the number value (as `int`) at the index `index`, erasing previous + * value (if it was recorded). + * + * If negative index is given - does nothing. + * If given index is too large (`>= GetLength()`) then array will be + * extended, setting values at new indices (except specified `index`) + * to "null" value (`JSON_Null`). + * + * Forms a pair with `SetNumber()` method. + * While JSON standard allows to store numbers with arbitrary precision, + * UnrealScript's types have a limited range. + * To alleviate this problem we store numbers in both `float`- and + * `int`-type variables to extended supported range of values. + * So if you need to store a number with fractional part, you should + * prefer `SetNumber()` and for integer values `SetInteger()` is preferable. + * Both will record a value of type `JSON_Number`. + * + * @param index Index at which to set given numeric value. + * @param value Value to set at given index. + * @return Reference to the caller object, to allow for function chaining. + */ +public final function JArray SetInteger(int index, int value) { local JStorageAtom newStorageValue; if (index < 0) return self; - if (index >= data.length) - { - if (preventExpansion) - { - return self; - } - else - { - SetLength(index + 1); - } + if (index >= data.length) { + SetLength(index + 1); } newStorageValue.type = JSON_Number; newStorageValue.numberValue = float(value); @@ -212,26 +355,28 @@ public final function JArray SetInteger( return self; } -public final function JArray SetString -( - int index, - string value, - optional bool preventExpansion -) +/** + * Sets the string value at the given index `index`, erasing previous value + * (if it was recorded). + * + * If negative index is given - does nothing. + * If given index is too large (`>= GetLength()`) then array will be + * extended, setting values at new indices (except specified `index`) + * to "null" value (`JSON_Null`). + * + * Also see `SetClass()` method. + * + * @param index Index at which to set given string value. + * @param value Value to set at given index. + * @return Reference to the caller object, to allow for function chaining. + */ +public final function JArray SetString(int index, string value) { local JStorageAtom newStorageValue; if (index < 0) return self; - if (index >= data.length) - { - if (preventExpansion) - { - return self; - } - else - { - SetLength(index + 1); - } + if (index >= data.length) { + SetLength(index + 1); } newStorageValue.type = JSON_String; newStorageValue.stringValue = value; @@ -239,25 +384,33 @@ public final function JArray SetString return self; } -public final function JArray SetClass( - int index, - class value, - optional bool preventExpansion -) +/** + * Sets the string value, corresponding to a given class `value`, + * at the index `index`, erasing previous value (if it was recorded). + * + * Value in question will have `JSON_String` type. + * + * If negative index is given - does nothing. + * If given index is too large (`>= GetLength()`) then array will be + * extended, setting values at new indices (except specified `index`) + * to "null" value (`JSON_Null`). + * + * We want to allow storing `class` data in our JSON containers, but JSON + * standard does not support such a type, so we have to use string type + * to store `class`' name instead. + * Also see `GetClass()` method`. + * + * @param index Index at which to set given value. + * @param value Value to set at given index. + * @return Reference to the caller object, to allow for function chaining. + */ +public final function JArray SetClass(int index, class value) { local JStorageAtom newStorageValue; if (index < 0) return self; - if (index >= data.length) - { - if (preventExpansion) - { - return self; - } - else - { - SetLength(index + 1); - } + if (index >= data.length) { + SetLength(index + 1); } newStorageValue.type = JSON_String; newStorageValue.stringValue = string(value); @@ -266,26 +419,26 @@ public final function JArray SetClass( return self; } -public final function JArray SetBoolean -( - int index, - bool value, - optional bool preventExpansion -) +/** + * Sets the boolean value at the given index `index`, erasing previous value + * (if it was recorded). + * + * If negative index is given - does nothing. + * If given index is too large (`>= GetLength()`) then array will be + * extended, setting values at new indices (except specified `index`) + * to "null" value (`JSON_Null`). + * + * @param index Index at which to set given boolean value. + * @param value Value to set at given index. + * @return Reference to the caller object, to allow for function chaining. + */ +public final function JArray SetBoolean(int index, bool value) { local JStorageAtom newStorageValue; if (index < 0) return self; - if (index >= data.length) - { - if (preventExpansion) - { - return self; - } - else - { - SetLength(index + 1); - } + if (index >= data.length) { + SetLength(index + 1); } newStorageValue.type = JSON_Boolean; newStorageValue.booleanValue = value; @@ -293,51 +446,58 @@ public final function JArray SetBoolean return self; } -public final function JArray SetNull -( - int index, - optional bool preventExpansion -) +/** + * Sets the value at the given index `index` to be "null" (`JSON_Null`), + * erasing previous value (if it was recorded). + * + * If negative index is given - does nothing. + * If given index is too large (`>= GetLength()`) then array will be + * extended, setting values at new indices (except specified `index`) + * to "null" value (`JSON_Null`). + * + * @param index Index at which to set "null" value to. + * @return Reference to the caller object, to allow for function chaining. + */ +public final function JArray SetNull(int index) { local JStorageAtom newStorageValue; if (index < 0) return self; - if (index >= data.length) - { - if (preventExpansion) - { - return self; - } - else - { - SetLength(index + 1); - } + if (index >= data.length) { + SetLength(index + 1); } newStorageValue.type = JSON_Null; data[index] = newStorageValue; return self; } -public final function JArray SetArray( - int index, - JArray template, - optional bool preventExpansion -) +/** + * Sets the value at the given index `index` to store `JArray` object + * (JSON array type). + * + * If negative index is given - does nothing. + * If given index is too large (`>= GetLength()`) then array will be + * extended, setting values at new indices (except specified `index`) + * to "null" value (`JSON_Null`). + * + * NOTE: This method DOES NOT make caller `JArray` store a + * given reference, instead it clones it (see `Clone()`) into a new copy and + * stores that. This is made this way to ensure you can not, say, store + * an object in itself or it's children. + * See also `CreateArray()` method. + * + * @param index Index at which to set given array value. + * @param template Template `JArray` to clone. + * @return Reference to the caller object, to allow for function chaining. + */ +public final function JArray SetArray(int index, JArray template) { local JStorageAtom newStorageValue; - if (index < 0) return self; - if (template == none) return self; + if (index < 0) return self; + if (template == none) return self; - if (index >= data.length) - { - if (preventExpansion) - { - return self; - } - else - { - SetLength(index + 1); - } + if (index >= data.length) { + SetLength(index + 1); } newStorageValue.type = JSON_Array; newStorageValue.complexValue = template.Clone(); @@ -345,26 +505,33 @@ public final function JArray SetArray( return self; } -public final function JArray SetObject( - int index, - JObject template, - optional bool preventExpansion -) +/** + * Sets the value at the given index `index` to store `JObject` object + * (JSON object type). + * + * If negative index is given - does nothing. + * If given index is too large (`>= GetLength()`) then array will be + * extended, setting values at new indices (except specified `index`) + * to "null" value (`JSON_Null`). + * + * NOTE: This method DOES NOT make caller `JArray` store a + * given reference, instead it clones it (see `Clone()`) into a new copy and + * stores that. This is made this way to ensure you can not, say, store + * an object in itself or it's children. + * See also `CreateObject()` method. + * + * @param index Index at which to set given array value. + * @param template Template `JObject` to clone. + * @return Reference to the caller object, to allow for function chaining. + */ +public final function JArray SetObject(int index, JObject template) { local JStorageAtom newStorageValue; - if (index < 0) return self; - if (template == none) return self; + if (index < 0) return self; + if (template == none) return self; - if (index >= data.length) - { - if (preventExpansion) - { - return self; - } - else - { - SetLength(index + 1); - } + if (index >= data.length) { + SetLength(index + 1); } newStorageValue.type = JSON_Object; newStorageValue.complexValue = template.Clone(); @@ -372,34 +539,27 @@ public final function JArray SetObject( return self; } -// JSON array and object types don't have setters, but instead have -// functions to create a new, empty array/object under a certain name. -// If passed index is negative - does nothing. -// If index lies beyond array length (`>= GetLength()`), - -// these functions will expand array in the same way as `GetLength()` function. -// This can be prevented by setting optional parameter `preventExpansion` to -// `false` (nothing will be done in this case). -// They return object itself, allowing user to chain calls like this: -// `array.CreateObject("sub object").CreateArray("sub array");`. -public final function JArray CreateArray -( - int index, - optional bool preventExpansion -) +/** + * Sets the value oat the given index `index` to store a new + * `JArray` object (JSON array type). + * + * See also `SetArray()` method. + * + * If negative index is given - does nothing. + * If given index is too large (`>= GetLength()`) then array will be + * extended, setting values at new indices (except specified `index`) + * to "null" value (`JSON_Null`). + * + * @param index Index at which to create a new `JArray`. + * @return Reference to the caller object, to allow for function chaining. + */ +public final function JArray CreateArray(int index) { local JStorageAtom newStorageValue; if (index < 0) return self; - if (index >= data.length) - { - if (preventExpansion) - { - return self; - } - else - { - SetLength(index + 1); - } + if (index >= data.length) { + SetLength(index + 1); } newStorageValue.type = JSON_Array; newStorageValue.complexValue = _.json.newArray(); @@ -407,25 +567,27 @@ public final function JArray CreateArray return self; } -public final function JArray CreateObject -( - int index, - optional bool preventExpansion -) +/** + * Sets the value oat the given index `index` to store a new + * `JObject` object (JSON object type). + * + * See also `SetObject()` method. + * + * If negative index is given - does nothing. + * If given index is too large (`>= GetLength()`) then array will be + * extended, setting values at new indices (except specified `index`) + * to "null" value (`JSON_Null`). + * + * @param index Index at which to create a new `JObject`. + * @return Reference to the caller object, to allow for function chaining. + */ +public final function JArray CreateObject(int index) { local JStorageAtom newStorageValue; if (index < 0) return self; - if (index >= data.length) - { - if (preventExpansion) - { - return self; - } - else - { - SetLength(index + 1); - } + if (index >= data.length) { + SetLength(index + 1); } newStorageValue.type = JSON_Object; newStorageValue.complexValue = _.json.newObject(); @@ -433,43 +595,116 @@ public final function JArray CreateObject return self; } -// Wrappers for setter functions that don't take index or -// `preventExpansion` parameters and add/create value at the end of the array. +/** + * Appends numeric value (as `float`) at the end of the caller `JArray`. + * + * Forms a pair with `AddInteger()` method. + * While JSON standard allows to store numbers with arbitrary precision, + * UnrealScript's types have a limited range. + * To alleviate this problem we store numbers in both `float`- and + * `int`-type variables to extended supported range of values. + * So if you need to store a number with fractional part, you should + * prefer `AddNumber()` and for integer values `AddInteger()` is preferable. + * Both will record a value of type `JSON_Number`. + * + * @param value Numeric value to append. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JArray AddNumber(float value) { return SetNumber(data.length, value); } +/** + * Appends numeric value (as `int`) at the end of the caller `JArray`. + * + * Forms a pair with `AddNumber()` method. + * While JSON standard allows to store numbers with arbitrary precision, + * UnrealScript's types have a limited range. + * To alleviate this problem we store numbers in both `float`- and + * `int`-type variables to extended supported range of values. + * So if you need to store a number with fractional part, you should + * prefer `AddNumber()` and for integer values `AddInteger()` is preferable. + * Both will record a value of type `JSON_Number`. + * + * @param value Numeric value to append. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JArray AddInteger(int value) { return SetInteger(data.length, value); } +/** + * Appends string value at the end of the caller `JArray`. + * + * Also see `AddClass()` method. + * + * @param value String value to append. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JArray AddString(string value) { return SetString(data.length, value); } +/** + * Appends string value, corresponding to a given class `value`, at the end of + * the caller `JArray`. + * + * Value in question will have `JSON_String` type. + * + * We want to allow storing `class` data in our JSON containers, but JSON + * standard does not support such a type, so we have to use string type + * to store `class`' name instead. + * Also see `GetClass()` method`. + * + * @param value Class value to append. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JArray AddClass(class value) { return SetClass(data.length, value); } +/** + * Appends boolean value at the end of the caller `JArray`. + * + * @param value Boolean value to append. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JArray AddBoolean(bool value) { return SetBoolean(data.length, value); } +/** + * Appends "null" value at the end of the caller `JArray`. + * + * @return Reference to the caller object, to allow for function chaining. + */ public final function JArray AddNull() { return SetNull(data.length); } +/** + * Appends new empty `JArray` (JSON array type) at the end of + * the caller `JArray`. + * + * @return Reference to the caller object, to allow for function chaining. + */ public final function JArray AddArray() { return CreateArray(data.length); } +/** + * Appends new empty `JObject` (JSON object type) at the end of + * the caller `JArray`. + * + * @return Reference to the caller object, to allow for function chaining. + */ public final function JArray AddObject() { return CreateObject(data.length); @@ -479,6 +714,16 @@ public final function JArray AddObject() // a given index. // If `index` falls outside array boundaries - nothing will be done. // Returns `true` if value was actually removed and `false` if it didn't exist. +/** + * Removes up to `amount` (minimum of `1`) of values, starting from + * a given index `index`. + * + * If `index` falls outside array boundaries - nothing will be done. + * + * @param index Index of first value to remove. + * @param amount Amount of values to remove. + * @return Reference to the caller object, to allow for function chaining. + */ public final function bool RemoveValue(int index, optional int amount) { local int i; @@ -497,11 +742,21 @@ public final function bool RemoveValue(int index, optional int amount) return true; } +/** + * Completely clears caller `JObject` of all values. + */ public function Clear() { RemoveValue(0, data.length); } +/** + * Checks if caller JSON container's values form a subset of + * `rightJSON`'s values. + * + * @return `true` if caller ("left") object is a subset of `rightJSON` + * and `false` otherwise. + */ public function bool IsSubsetOf(JSON rightValue) { local int i; @@ -520,6 +775,12 @@ public function bool IsSubsetOf(JSON rightValue) return true; } +/** + * Makes an exact copy of the caller `JArray` + * + * @return Copy of the caller `JArray`. Guaranteed to be `JArray` + * (or `none`, if appropriate object could not be created). + */ public function JSON Clone() { local int i; @@ -545,6 +806,31 @@ public function JSON Clone() return clonedArray; } +/** + * Uses given parser to parse a new array of values (append them to the end of + * the caller array) inside the caller `JArray`. + * + * Only adds new values if parsing the whole array was successful, + * otherwise even successfully parsed properties will be discarded. + * + * `parser` must point at the text describing a JSON array in + * a valid notation. Then it parses that container inside memory, but + * instead of creating it as a separate entity, adds it's values to + * the caller `JArray`. + * + * This method does not try to validate passed JSON and can accept invalid + * JSON by making some assumptions, but it is an undefined behavior and + * one should not expect it. + * Method is only guaranteed to work on valid JSON. + * + * @param parser Parser that method would use to parse `JArray` from + * wherever it left. It's confirmed will not be changed, but if parsing + * was successful, - it will point at the next available character. + * Do not treat `parser` being in a non-failed state as a confirmation of + * successful parsing: JSON parsing might fail regardless. + * Check return value for that. + * @return `true` if parsing was successful and `false` otherwise. + */ public function bool ParseIntoSelfWith(Parser parser) { local bool parsingSucceeded; @@ -586,6 +872,8 @@ public function bool ParseIntoSelfWith(Parser parser) return parsingSucceeded; } +// Either cleans up or adds a list of parsed values, +// depending on whether parsing was successful or not. private function HandleParsedAtoms( array parsedAtoms, bool parsingSucceeded) @@ -606,6 +894,16 @@ private function HandleParsedAtoms( } } +/** + * Displays caller `JArray` with a provided preset. + * + * See `Display()` for a simpler to use method. + * + * @param displaySettings Struct that describes precisely how to display + * caller `JArray`. Can be used to emulate `Display()` call. + * @return String representation of caller JSON container in format defined by + * `displaySettings`. + */ public function string DisplayWith(JSONDisplaySettings displaySettings) { local int i; diff --git a/sources/Data/JSON/JObject.uc b/sources/Data/JSON/JObject.uc index a6bdc12..a5237dd 100644 --- a/sources/Data/JSON/JObject.uc +++ b/sources/Data/JSON/JObject.uc @@ -1,22 +1,19 @@ /** * This class implements JSON object storage capabilities. - * Whenever one wants to store JSON data, they need to define such object. * It stores name-value pairs, where names are strings and values can be: * ~ Boolean, string, null or number (float in this implementation) data; * ~ Other JSON objects; * ~ JSON Arrays (see `JArray` class). * - * This implementation provides getters and setters for boolean, string, - * null or number types that allow to freely set and fetch their values - * by name. + * This implementation provides a variety of functionality, + * including parsing, displaying, getters and setters for JSON types that + * allow to freely set and fetch their values by name. * JSON objects and arrays can be fetched by getters, but you cannot - * add existing object or array to another object. Instead one has to create - * a new, empty object with a certain name and then fill it with data. - * This allows to avoid loop situations, where object is contained in itself. - * Functions to remove existing values are also provided and are applicable - * to all variable types. - * Setters can also be used to overwrite any value by a different value, - * even of a different type. + * add existing object or array to another object. Instead one has to either + * clone existing object or create an empty one and then manually fill + * with data. + * This allows to avoid loop situations, where object is + * contained in itself. * Copyright 2020 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. @@ -54,9 +51,19 @@ var private int storedElementCount; // that will be enforced if user requires something outside those bounds var private config const int MINIMUM_CAPACITY; var private config const int MAXIMUM_CAPACITY; +// Minimum and maximum allowed density of elements +// (`storedElementCount / hashTable.length`). +// If density falls outside this range, - we have to resize hash table to +// get into (MINIMUM_DENSITY; MAXIMUM_DENSITY) bounds, +// as long as it does not violate other restrictions. var private config const float MINIMUM_DENSITY; var private config const float MAXIMUM_DENSITY; +// Only ever reallocate hash table if new size will differ by +// at least that much, regardless of other restrictions. var private config const int MINIMUM_DIFFERENCE_FOR_REALLOCATION; +// Never use any hash table capacity below this limit, +// regardless of other variables +// (like `MINIMUM_CAPACITY` or `MINIMUM_DENSITY`). var private config const int ABSOLUTE_LOWER_CAPACITY_LIMIT; // Helper method that is needed as a replacement for `%`, since it is @@ -213,10 +220,17 @@ private final function ResizeHashTable(int newCapacity) } } -// Returns `JType` of a variable with a given name in our properties. -// This function can be used to check if certain variable exists -// in this object, since if such variable does not exist - -// function will return `JSON_Undefined`. +/** + * Returns `JType` of a property with a given name in our collection. + * + * This function can be used to check if certain variable exists + * in this object, since if such variable does not exist - + * function will return `JSON_Undefined`. + * + * @param name Name of the property to get the type of. + * @return Type of the property by the name `name`. + * `JSON_Undefined` iff property with that name does not exist. + */ public final function JType GetTypeOf(string name) { local JProperty property; @@ -226,14 +240,23 @@ public final function JType GetTypeOf(string name) return property.value.type; } -// Following functions are getters for various types of variables. -// Getter for null value simply checks if it's null -// and returns true/false as a result. -// Getters for simple types (number, string, boolean) can have optional -// default value specified, that will be returned if requested variable -// doesn't exist or has a different type. -// Getters for object and array types don't take default values and -// will simply return `none`. +/** + * Gets the value (as a `float`) of a property by the name `name`, + * assuming it has `JSON_Number` type. + * + * Forms a pair with `GetInteger()` method. JSON allows to specify + * arbitrary precision for the number variables, but UnrealScript can only + * store a limited range of numeric value. + * To alleviate this problem we store numeric JSON values as both + * `float` and `int` and can return either of the requested versions. + * + * @param name Name of the property to return a value of. + * @param defaultValue Value to return if property does not exist or + * has a different type (can be checked by `GetTypeOf()`). + * @return Number value of the property under name `name`, + * if it exists and has `JSON_Number` type. + * Otherwise returns passed `defaultValue`. + */ public final function float GetNumber(string name, optional float defaultValue) { local JProperty property; @@ -244,6 +267,23 @@ public final function float GetNumber(string name, optional float defaultValue) return property.value.numberValue; } +/** + * Gets the value (as an `int`) of a property by the name `name`, + * assuming it has `JSON_Number` type. + * + * Forms a pair with `GetNumber()` method. JSON allows to specify + * arbitrary precision for the number variables, but UnrealScript can only + * store a limited range of numeric value. + * To alleviate this problem we store numeric JSON values as both + * `float` and `int` and can return either of the requested versions. + * + * @param name Name of the property to return a value of. + * @param defaultValue Value to return if property does not exist or + * has a different type (can be checked by `GetTypeOf()`). + * @return Number value of the property under name `name`, + * if it exists and has `JSON_Number` type. + * Otherwise returns passed `defaultValue`. + */ public final function int GetInteger(string name, optional int defaultValue) { local JProperty property; @@ -254,6 +294,19 @@ public final function int GetInteger(string name, optional int defaultValue) return property.value.numberValueAsInt; } +/** + * Gets the value of a property by the name `name`, + * assuming it has `JSON_String` type. + * + * See also `GetClass()` method. + * + * @param name Name of the property to return a value of. + * @param defaultValue Value to return if property does not exist or + * has a different type (can be checked by `GetTypeOf()`). + * @return String value of the property under name `name`, + * if it exists and has `JSON_String` type. + * Otherwise returns passed `defaultValue`. + */ public final function string GetString( string name, optional string defaultValue @@ -267,6 +320,23 @@ public final function string GetString( return property.value.stringValue; } +/** + * Gets the value of a property by the name `name` as a `class`, + * assuming it has `JSON_String` type. + * + * JSON does not support to store class data type, but we can use string type + * for that. This method attempts to load a class object from it's full name, + * (like `Engine.Actor`) recorded inside an appropriate string value. + * + * @param name Name of the property to return a value of. + * @param defaultValue Value to return if property does not exist, + * has a different type (can be checked by `GetTypeOf()`) or not + * a valid class name. + * @return Class value of the property under name `name`, + * if it exists, has `JSON_String` type and it represents + * a full name of some class. + * Otherwise returns passed `defaultValue`. + */ public final function class GetClass( string name, optional class defaultValue @@ -284,6 +354,17 @@ public final function class GetClass( return defaultValue; } +/** + * Gets the value of a property by the name `name`, + * assuming it has `JSON_Boolean` type. + * + * @param name Name of the property to return a value of. + * @param defaultValue Value to return if property does not exist or + * has a different type (can be checked by `GetTypeOf()`). + * @return Boolean value of the property under name `name`, + * if it exists and has `JSON_Boolean` type. + * Otherwise returns passed `defaultValue`. + */ public final function bool GetBoolean(string name, optional bool defaultValue) { local JProperty property; @@ -294,6 +375,15 @@ public final function bool GetBoolean(string name, optional bool defaultValue) return property.value.booleanValue; } +/** + * Checks if a property by the name `name` has `JSON_Null` type. + * + * Alternatively consider using `GetType()` method. + * + * @param name Name of the property to check for being `null`. + * @return `true` if property under given name `name` exists and + * has type `JSON_Null`; `false` otherwise. + */ public final function bool IsNull(string name) { local JProperty property; @@ -301,6 +391,15 @@ public final function bool IsNull(string name) return (property.value.type == JSON_Null); } +/** + * Gets the value of a property by the name `name`, + * assuming it has `JSON_Array` type. + * + * @param name Name of the property to return a value of. + * @return `JArray` object value of the property under name `name`, + * if it exists and has `JSON_Array` type. + * Otherwise returns `none`. + */ public final function JArray GetArray(string name) { local JProperty property; @@ -311,6 +410,15 @@ public final function JArray GetArray(string name) return JArray(property.value.complexValue); } +/** + * Gets the value of a property by the name `name`, + * assuming it has `JSON_Object` type. + * + * @param name Name of the property to return a value of. + * @return `JObject` object value of the property under name `name`, + * if it exists and has `JSON_Object` type. + * Otherwise returns `none`. + */ public final function JObject GetObject(string name) { local JProperty property; @@ -319,10 +427,25 @@ public final function JObject GetObject(string name) return JObject(property.value.complexValue); } -// Following functions provide simple setters for boolean, string, number -// and null values. -// They return object itself, allowing user to chain calls like this: -// `object.SetNumber("num1", 1).SetNumber("num2", 2);`. +/** + * Sets the number value (as `float`) of a property by the name `name`, + * erasing previous value (if it was recorded). + * + * Property in question will have `JSON_Number` type. + * + * Forms a pair with `SetInteger()` method. + * While JSON standard allows to store numbers with arbitrary precision, + * UnrealScript's types have a limited range. + * To alleviate this problem we store numbers in both `float`- and + * `int`-type variables to extended supported range of values. + * So if you need to store a number with fractional part, you should + * prefer `SetNumber()` and for integer values `SetInteger()` is preferable. + * Both will create a property of type `JSON_Number`. + * + * @param name Name of the property to set a value of. + * @param value Value to set to a property under a given name `name`. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject SetNumber(string name, float value) { local JProperty property; @@ -336,6 +459,25 @@ public final function JObject SetNumber(string name, float value) return self; } +/** + * Sets the number value (as `int`) of a property by the name `name`, + * erasing previous value (if it was recorded). + * + * Property in question will have `JSON_Number` type. + * + * Forms a pair with `SetNumber()` method. + * While JSON standard allows to store numbers with arbitrary precision, + * UnrealScript's types have a limited range. + * To alleviate this problem we store numbers in both `float`- and + * `int`-type variables to extended supported range of values. + * So if you need to store a number with fractional part, you should + * prefer `SetNumber()` and for integer values `SetInteger()` is preferable. + * Both will create a property of type `JSON_Number`. + * + * @param name Name of the property to set a value of. + * @param value Value to set to a property under a given name `name`. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject SetInteger(string name, int value) { local JProperty property; @@ -349,6 +491,18 @@ public final function JObject SetInteger(string name, int value) return self; } +/** + * Sets the string value of a property by the name `name`, + * erasing previous value (if it was recorded). + * + * Property in question will have `JSON_String` type. + * + * Also see `SetClass()` method. + * + * @param name Name of the property to set a value of. + * @param value Value to set to a property under a given name `name`. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject SetString(string name, string value) { local JProperty property; @@ -362,6 +516,22 @@ public final function JObject SetString(string name, string value) return self; } +/** + * Sets the string value, corresponding to a given class `value`, + * of a property by the name `name`, erasing previous value + * (if it was recorded). + * + * Property in question will have `JSON_String` type. + * + * We want to allow storing `class` data in our JSON containers, but JSON + * standard does not support such a type, so we have to use string type + * to store `class`' name instead. + * Also see `GetClass()` method`. + * + * @param name Name of the property to set a value of. + * @param value Value to set to a property under a given name `name`. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject SetClass(string name, class value) { local JProperty property; @@ -375,6 +545,16 @@ public final function JObject SetClass(string name, class value) return self; } +/** + * Sets the boolean value of a property by the name `name`, + * erasing previous value (if it was recorded). + * + * Property in question will have `JSON_Boolean` type. + * + * @param name Name of the property to set a value of. + * @param value Value to set to a property under a given name `name`. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject SetBoolean(string name, bool value) { local JProperty property; @@ -386,6 +566,15 @@ public final function JObject SetBoolean(string name, bool value) return self; } +/** + * Sets the value of a property by the name `name` to be "null" (`JSON_Null`), + * erasing previous value (if it was recorded). + * + * Property in question will have `JSON_Null` type. + * + * @param name Name of the property to set a "null" value to. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject SetNull(string name) { local JProperty property; @@ -396,6 +585,20 @@ public final function JObject SetNull(string name) return self; } +/** + * Sets the value of a property by the name `name` to store `JArray` object + * (JSON array type). + * + * NOTE: This method DOES NOT make caller `JObject` store a + * given reference, instead it clones it (see `Clone()`) into a new copy and + * stores that. This is made this way to ensure you can not, say, store + * an object in itself or it's children. + * See also `CreateArray()` method. + * + * @param name Name of the property to return a value of. + * @param template Template `JArray` to clone into property. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject SetArray(string name, JArray template) { local JProperty property; @@ -410,6 +613,20 @@ public final function JObject SetArray(string name, JArray template) return self; } +/** + * Sets the value of a property by the name `name` to store `JObject` object + * (JSON object type). + * + * NOTE: This method DOES NOT make caller `JObject` store a + * given reference, instead it clones it (see `Clone()`) into a new copy and + * stores that. This is made this way to ensure you can not, say, store + * an object in itself or it's children. + * See also `CreateArray()` method. + * + * @param name Name of the property to return a value of. + * @param template Template `JObject` to clone into property. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject SetObject(string name, JObject template) { local JProperty property; @@ -424,10 +641,15 @@ public final function JObject SetObject(string name, JObject template) return self; } -// JSON array and object types don't have setters, but instead have -// functions to create a new, empty array/object under a certain name. -// They return object itself, allowing user to chain calls like this: -// `object.CreateObject("folded object").CreateArray("names list");`. +/** + * Sets the value of a property by the name `name` to store a new + * `JArray` object (JSON array type). + * + * See also `SetArray()` method. + * + * @param name Name of the property to store the new `JArray` value. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject CreateArray(string name) { local JProperty property; @@ -441,6 +663,15 @@ public final function JObject CreateArray(string name) return self; } +/** + * Sets the value of a property by the name `name` to store a new + * `JObject` object (JSON array type). + * + * See also `SetArray()` method. + * + * @param name Name of the property to store the new `JObject` value. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject CreateObject(string name) { local JProperty property; @@ -454,15 +685,44 @@ public final function JObject CreateObject(string name) return self; } -// Removes values with a given name. -// Returns `true` if value was actually removed and `false` if it didn't exist. +/** + * Removes a property with a given name. + * + * Does nothing if property with a given name does not exist. + * + * @param name Name of the property to remove. + * @return Reference to the caller object, to allow for function chaining. + */ public final function JObject RemoveValue(string name) { RemoveProperty(name); return self; } -public final function array GetKeys() +/** + * Completely clears caller `JObject` of all stored properties. + */ +public function Clear() +{ + local int i, j; + local array 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 GetPropertyNames() { local int i, j; local array result; @@ -477,6 +737,13 @@ public final function array GetKeys() return result; } +/** + * Checks if caller JSON container's values form a subset of + * `rightJSON`'s values. + * + * @return `true` if caller ("left") object is a subset of `rightJSON` + * and `false` otherwise. + */ public function bool IsSubsetOf(JSON rightJSON) { local int i, j; @@ -501,6 +768,12 @@ public function bool IsSubsetOf(JSON rightJSON) return true; } +/** + * Makes an exact copy of the caller `JObject` + * + * @return Copy of the caller `JObject`. Guaranteed to be `JObject` + * (or `none`, if appropriate object could not be created). + */ public function JSON Clone() { local int i, j; @@ -534,6 +807,32 @@ public function JSON Clone() return clonedObject; } +/** + * Uses given parser to parse a new set of properties inside + * the caller `JObject`. + * + * Only adds new properties if parsing the whole object was successful, + * otherwise even successfully parsed properties will be discarded. + * + * `parser` must point at the text describing a JSON object in + * a valid notation. Then it parses that container inside memory, but + * instead of creating it as a separate entity, adds it's values to + * the caller container. + * Everything that comes after parsed `JObject` is discarded. + * + * This method does not try to validate passed JSON and can accept invalid + * JSON by making some assumptions, but it is an undefined behavior and + * one should not expect it. + * Method is only guaranteed to work on valid JSON. + * + * @param parser Parser that method would use to parse `JObject` from + * wherever it left. It's confirmed will not be changed, but if parsing + * was successful, - it will point at the next available character. + * Do not treat `parser` being in a non-failed state as a confirmation of + * successful parsing: JSON parsing might fail regardless. + * Check return value for that. + * @return `true` if parsing was successful and `false` otherwise. + */ public function bool ParseIntoSelfWith(Parser parser) { local bool parsingSucceeded; @@ -542,6 +841,7 @@ public function bool ParseIntoSelfWith(Parser parser) local array parsedProperties; if (parser == none) return false; initState = parser.GetCurrentState(); + // Ensure that parser starts pointing at what looks like a JSON object confirmedState = parser.Skip().Match("{").GetCurrentState(); if (!parser.Ok()) { @@ -551,6 +851,8 @@ public function bool ParseIntoSelfWith(Parser parser) while (parser.Ok() && !parser.HasFinished()) { confirmedState = parser.Skip().GetCurrentState(); + // Check for JSON object ending and ONLY THEN declare parsing + // is successful, not encountering '}' implies bad JSON format. if (parser.Match("}").Ok()) { parsingSucceeded = true; @@ -560,9 +862,12 @@ public function bool ParseIntoSelfWith(Parser parser) && !parser.RestoreState(confirmedState).Match(",").Skip().Ok()) { break; } + // Recover after failed `Match("}")` on the first cycle + // (`parsedProperties.length == 0`) else if (parser.Ok()) { confirmedState = parser.GetCurrentState(); } + // Parse property parser.RestoreState(confirmedState).Skip(); parser.MStringLiteral(nextProperty.name).Skip().Match(":"); nextProperty.value = ParseAtom(parser.Skip()); @@ -578,6 +883,8 @@ public function bool ParseIntoSelfWith(Parser parser) return parsingSucceeded; } +// Either cleans up or adds a list of parsed properties, +// depending on whether parsing was successful or not. private function HandleParsedProperties( array parsedProperties, bool parsingSucceeded) @@ -598,6 +905,16 @@ private function HandleParsedProperties( } } +/** + * Displays caller `JObject` with a provided preset. + * + * See `Display()` for a simpler to use method. + * + * @param displaySettings Struct that describes precisely how to display + * caller `JObject`. Can be used to emulate `Display()` call. + * @return String representation of caller JSON container in format defined by + * `displaySettings`. + */ public function string DisplayWith(JSONDisplaySettings displaySettings) { local int i, j; @@ -613,19 +930,7 @@ public function string DisplayWith(JSONDisplaySettings displaySettings) else { innerSettings = displaySettings; } - // Prepare delimiters using appropriate indentation rules - // We only use inner settings for the part right after '{', - // as the rest is supposed to be aligned with outer objects - openingBraces = "{"; - closingBraces = "}"; - if (innerSettings.colored) { - openingBraces = "&" $ openingBraces; - closingBraces = "&" $ closingBraces; - } - openingBraces = displaySettings.beforeObjectOpening - $ openingBraces $ innerSettings.afterObjectOpening; - closingBraces = displaySettings.beforeObjectEnding - $ closingBraces $ displaySettings.afterObjectEnding; + GetBraces(openingBraces, closingBraces, displaySettings, innerSettings); propertiesSeparator = "," $ innerSettings.afterObjectComma; if (innerSettings.colored) { propertiesSeparator = "{$json_comma" @ propertiesSeparator $ "}"; @@ -648,6 +953,49 @@ public function string DisplayWith(JSONDisplaySettings displaySettings) return openingBraces $ contents $ closingBraces; } +/** + * Helper function that generates `string`s to be used for opening and + * closing braces for text representation of the caller `JObject`. + * + * Cannot fail. + * + * @param openingBraces `string` for opening braces will be recorded here. + * @param closingBraces `string` for closing braces will be recorded here. + * @param outerSettings Settings that were passed to tell us how to display + * a caller object. + * @param innerSettings Settings that were generated from `outerSettings` by + * indenting them (`IndentSettings()`) to use to display it's + * inner properties. + */ +protected function GetBraces( + out string openingBraces, + out string closingBraces, + JSONDisplaySettings outerSettings, + JSONDisplaySettings innerSettings) +{ + openingBraces = "{"; + closingBraces = "}"; + if (innerSettings.colored) { + openingBraces = "&" $ openingBraces; + closingBraces = "&" $ closingBraces; + } + // We only use inner settings for the part right after '{', + // as the rest is supposed to be aligned with outer objects + openingBraces = outerSettings.beforeObjectOpening + $ openingBraces $ innerSettings.afterObjectOpening; + closingBraces = outerSettings.beforeObjectEnding + $ closingBraces $ outerSettings.afterObjectEnding; +} + +/** + * Helper method to convert a JSON object's property into it's + * text representation. + * + * @param toDisplay Property to display as a `string`. + * @param displaySettings Settings that tells us how to display it. + * @return `string` representation of a given property `toDisplay`, + * created according to the settings `displaySettings`. + */ protected function string DisplayProperty( JProperty toDisplay, JSONDisplaySettings displaySettings) @@ -667,21 +1015,6 @@ protected function string DisplayProperty( $ displaySettings.afterPropertyValue); } -public function Clear() -{ - local int i, j; - local array nextProperties; - for (i = 0; i < hashTable.length; i += 1) - { - nextProperties = hashTable[i].properties; - for (j = 0; j < nextProperties.length; j += 1) - { - if (nextProperties[j].value.complexValue == none) continue; - nextProperties[j].value.complexValue.Destroy(); - } - } -} - defaultproperties { ABSOLUTE_LOWER_CAPACITY_LIMIT = 10 diff --git a/sources/Data/JSON/JSON.uc b/sources/Data/JSON/JSON.uc index bef4dd2..1130b36 100644 --- a/sources/Data/JSON/JSON.uc +++ b/sources/Data/JSON/JSON.uc @@ -3,9 +3,8 @@ * that uses human-readable text to store and transmit data objects * consisting of name–value pairs and array data types. * For more information refer to https://en.wikipedia.org/wiki/JSON - * This is a base class for implementation of JSON data storage for Acedia. - * It does not implement parsing and printing from/into human-readable - * text representation, just provides means to store such information. + * This is a base class for implementation of JSON objects and arrays + * for Acedia. * * JSON data is stored as an object (represented via `JSONObject`) that * contains a set of name-value pairs, where value can be @@ -29,9 +28,12 @@ * along with Acedia. If not, see . */ class JSON extends AcediaActor - abstract; + abstract + config(AcediaSystem); -// Enumeration for possible types of JSON values. +/** + * Enumeration for possible types of JSON values. + */ enum JType { // Technical type, used to indicate that requested value is missing. @@ -53,7 +55,9 @@ enum JType JSON_Object }; -// Stores a single JSON value +/** + * Represents a single JSON value. + */ struct JStorageAtom { // What type is stored exactly? @@ -77,44 +81,96 @@ struct JStorageAtom var bool classLoadingWasAttempted; }; +/** + * Enumeration of possible result of comparing two JSON containers + * (objects or arrays). + * Containers are compared as sets of stored variables. + */ enum JComparisonResult { + // Containers contain different sets of values and + // neither can be considered a subset of another. JCR_Incomparable, + // "Left" container is a subset of the "right" one. JCR_SubSet, + // "Right" container is a subset of the "left" one. JCR_Overset, + // Both objects are identical. JCR_Equal }; +/** + * Describes how JSON containers are supposed to be displayed. + */ struct JSONDisplaySettings { + // Should it be displayed as a formatted string, with added color tags? var bool colored; + // Should we "stack" indentation of folded objects? var bool stackIndentation; + // Indentation for elements in object/array var string subObjectIndentation, subArrayIndentation; + // Strings to put immediately before and after object opening: '{' var string beforeObjectOpening, afterObjectOpening; + // Strings to put immediately before and after object closing: '}' var string beforeObjectEnding, afterObjectEnding; + // {"name":value} var string beforePropertyName, afterPropertyName; + // {"name":value} var string beforePropertyValue, afterPropertyValue; + // String to put immediately after comma inside object, + // can be used to break line after each property record var string afterObjectComma; + // Strings to put immediately before and after array opening: '[' var string beforeArrayOpening, afterArrayOpening; + // Strings to put immediately before and after array closing: ']' var string beforeArrayEnding, afterArrayEnding; + // [element1,...] var string beforeElement, afterElement; + // Can be used to break line after each property record var string afterArrayComma; }; -var private const int MAX_FLOAT_PRECISION; +// Max precision that will be used when outputting JSON values as a string. +// Hardcoded to force this value between 0 and 10, inclusively. +var private const config int MAX_FLOAT_PRECISION; +/** + * Completely clears caller JSON container of all stored data. + */ public function Clear(){} +/** + * Makes an exact copy of the caller JSON container. + * + * @return Copy of the caller JSON container object. + */ public function JSON Clone() { return none; } +/** + * Checks if caller JSON container's values form a subset of + * `rightJSON`'s values. + * + * @return `true` if caller ("left") object is a subset of `rightJSON` + * and `false` otherwise. + */ public function bool IsSubsetOf(JSON rightJSON) { return false; } +/** + * Compares caller JSON container ("left container") + * to `rightJSON` ("right container"). + * + * @param rightJSON Value to compare caller object to. + * @return `JComparisonResult` describing comparison of caller `JSON` container + * to `rightJSON`. + * Always returns `false` if compared objects are of different types. + */ public final function JComparisonResult Compare(JSON rightJSON) { local bool firstIsSubset, secondIsSubset; @@ -140,11 +196,201 @@ public final function JComparisonResult Compare(JSON rightJSON) } } +/** + * Checks if two objects are equal. + * + * A shortcut for `Compare(rightJSON) == JCR_Equal`. + * + * @param rightJSON Value to compare caller object to. + * @return `true` if caller and `rightJSON` store exactly same set of values + * (under the same names for `JObject`) and `false` otherwise. + */ public final function bool IsEqual(JSON rightJSON) { return (Compare(rightJSON) == JCR_Equal); } +/** + * Displays caller JSON container with one of the presets. + * + * Default compact preset displays JSON in as little characters as possible, + * fancy preset tries to make it human-readable with appropriate spacing and + * indentation for sub objects. + * + * See `DisplayWith()` for a more tweakable method. + * + * @param fancyPrinting Leave empty of `false` for a compact display and + * `true` to display it with a fancy preset. + * @param colorSettings Display JSON container as a formatted string, + * adding color tags to JSON syntax. + * @return String representation of caller JSON container, + * in plain format if `colorSettings == false` and + * as a formatted string if `colorSettings == true`. + */ +public final function string Display( + optional bool fancyPrinting, + optional bool colorSettings) +{ + local JSONDisplaySettings settingsToUse; + // Settings are minimal by default + if (fancyPrinting) { + settingsToUse = GetFancySettings(); + } + if (colorSettings) { + settingsToUse.colored = true; + } + return DisplayWith(settingsToUse); +} + +/** + * Displays caller JSON container with a provided preset. + * + * See `Display()` for a simpler to use method. + * + * @param displaySettings Struct that describes precisely how to display + * caller JSON container. Can be used to emulate `Display()` call. + * @return String representation of caller JSON container in format defined by + * `displaySettings`. + */ +public function string DisplayWith(JSONDisplaySettings displaySettings) +{ + return ""; +} + +/** + * Uses given parser to parse a new set of properties inside + * the caller JSON container. + * + * Only adds new properties if parsing the whole object was successful, + * otherwise even successfully parsed properties will be discarded. + * + * `parser` must point at the text describing a JSON object or an array + * (depending on whether a caller object is `JObject` or `JArray`) in + * a valid notation. Then it parses that container inside memory, but + * instead of creating it as a separate entity, adds it's values to + * the caller container. + * Everything that comes after parsed JSON container is discarded. + * + * This method does not try to validate passed JSON and can accept invalid + * JSON by making some assumptions, but it is an undefined behavior and + * one should not expect it. + * Method is only guaranteed to work on valid JSON. + * + * @param parser Parser that method would use to parse JSON container from + * wherever it left. It's confirmed will not be changed, but if parsing + * was successful, - it will point at the next available character. + * Do not treat `parser` being in a non-failed state as a confirmation of + * successful parsing: JSON parsing might fail regardless. + * Check return value for that. + * @return `true` if parsing was successful and `false` otherwise. + */ +public function bool ParseIntoSelfWith(Parser parser) +{ + return false; +} + +/** + * Parse a new set of properties inside the caller JSON container from + * a given `Text`. + * + * Only adds new properties if parsing the whole object was successful, + * otherwise even successfully parsed properties will be discarded. + * + * JSON container is parsed from a given `Text`, but instead of creating + * new object as a separate entity, method adds it's values to + * the caller container. + * Everything that comes after parsed JSON container is discarded. + * + * This method does not try to validate passed JSON and can accept invalid + * JSON by making some assumptions, but it is an undefined behavior and + * one should not expect it. + * Method is only guaranteed to work on valid JSON. + * + * @param source `Text` to get JSON container definition from. + * @return `true` if parsing was successful and `false` otherwise. + */ +public final function bool ParseIntoSelf(Text source) +{ + local bool successfullyParsed; + local Parser jsonParser; + jsonParser = _.text.Parse(source); + successfullyParsed = ParseIntoSelfWith(jsonParser); + _.memory.Free(jsonParser); + return successfullyParsed; +} + +/** + * Parse a new set of properties inside the caller JSON container from + * a given `string`. + * + * Only adds new properties if parsing the whole object was successful, + * otherwise even successfully parsed properties will be discarded. + * + * JSON container is parsed from a given `string`, but instead of creating + * new object as a separate entity, method adds it's values to + * the caller container. + * Everything that comes after parsed JSON container is discarded. + * + * This method does not try to validate passed JSON and can accept invalid + * JSON by making some assumptions, but it is an undefined behavior and + * one should not expect it. + * Method is only guaranteed to work on valid JSON. + * + * @param source `string` to get JSON container definition from. + * @return `true` if parsing was successful and `false` otherwise. + */ +public final function bool ParseIntoSelfString( + string source, + optional Text.StringType stringType) +{ + local bool successfullyParsed; + local Parser jsonParser; + jsonParser = _.text.ParseString(source, stringType); + successfullyParsed = ParseIntoSelfWith(jsonParser); + _.memory.Free(jsonParser); + return successfullyParsed; +} + +/** + * Parse a new set of properties inside the caller JSON container from + * a given raw data. + * + * Only adds new properties if parsing the whole object was successful, + * otherwise even successfully parsed properties will be discarded. + * + * JSON container is parsed from a given raw data, but instead of creating + * new object as a separate entity, method adds it's values to + * the caller container. + * Everything that comes after parsed JSON container is discarded. + * + * This method does not try to validate passed JSON and can accept invalid + * JSON by making some assumptions, but it is an undefined behavior and + * one should not expect it. + * Method is only guaranteed to work on valid JSON. + * + * @param source Raw data *array of `Text.Character`) to get JSON container + * definition from. + * @return `true` if parsing was successful and `false` otherwise. + */ +public final function bool ParseIntoSelfRaw(array rawSource) +{ + local bool successfullyParsed; + local Parser jsonParser; + jsonParser = _.text.ParseRaw(rawSource); + successfullyParsed = ParseIntoSelfWith(jsonParser); + _.memory.Free(jsonParser); + return successfullyParsed; +} + +/** + * Checks if two `JStorageAtom` values represent the same values. + * + * Atoms storing the same value does not necessarily mean that they are equal + * as structs because they contain several different container members and + * unused ones can differ. + * + * @return `true` if atoms stores the same value, `false` otherwise. + */ protected final function bool AreAtomsEqual( JStorageAtom atom1, JStorageAtom atom2) @@ -171,6 +417,11 @@ protected final function bool AreAtomsEqual( return atom1.complexValue.IsEqual(atom2.complexValue); } +/** + * Tries to load class variable into an atom, based on it's `stringValue`. + * + * @param atom Result will be recorded in the field of the argument itself. + */ protected final function TryLoadingStringAsClass(out JStorageAtom atom) { if (atom.classLoadingWasAttempted) return; @@ -179,26 +430,18 @@ protected final function TryLoadingStringAsClass(out JStorageAtom atom) class(DynamicLoadObject(atom.stringValue, class'Class', true)); } -public final function string Display( - optional bool fancyPrinting, - optional bool colorSettings) -{ - local JSONDisplaySettings settingsToUse; - // Settings are minimal by default - if (fancyPrinting) { - settingsToUse = GetFancySettings(); - } - if (colorSettings) { - settingsToUse.colored = true; - } - return DisplayWith(settingsToUse); -} - -public function string DisplayWith(JSONDisplaySettings displaySettings) -{ - return ""; -} - +/** + * Displays a `JStorageAtom` in it's appropriate text JSON representation. + * + * That's a representation that can be pasted inside JSON array as-is + * (with different values separated by commas). + * + * @param atom Atom to display. + * @param displaySettings Display settings, according to which to + * display the atom. + * @return Text representation of the passed `atom`, empty if it's of + * the type `JSON_Undefined`. + */ protected final function string DisplayAtom( JStorageAtom atom, JSONDisplaySettings displaySettings) @@ -241,7 +484,9 @@ protected final function string DisplayAtom( return result; } -protected final function string DisplayFloat(float number) +// Helper function for printing float with a given max precision +// (`MAX_FLOAT_PRECISION`). +private final function string DisplayFloat(float number) { local int integerPart, fractionalPart; local int precision; @@ -256,23 +501,29 @@ protected final function string DisplayFloat(float number) integerPart = number; result $= string(integerPart); number = (number - integerPart); + // We try to perform minimal amount of operations to extract fractional + // part as integer in order to avoid accumulating too much of an error. fractionalPart = Round(number * (10 ** precision)); if (fractionalPart <= 0) { return result; } result $= "."; + // Pad necessary zeroes in front howManyZeroes = precision - CountDigits(fractionalPart); - while (fractionalPart > 0 && fractionalPart % 10 == 0) { - fractionalPart /= 10; - } while (howManyZeroes > 0) { zeroes $= "0"; howManyZeroes -= 1; } + // Cut off trailing zeroes and + while (fractionalPart > 0 && fractionalPart % 10 == 0) { + fractionalPart /= 10; + } return result $ zeroes $ string(fractionalPart); } -protected final function int CountDigits(int number) +// Helper function that counts amount of digits in decimal representation +// of `number`. +private final function int CountDigits(int number) { local int digitCounter; while (number > 0) @@ -284,23 +535,15 @@ protected final function int CountDigits(int number) return digitCounter; } -protected final function JSONDisplaySettings GetFancySettings() -{ - local string lineFeed; - local JSONDisplaySettings fancySettings; - lineFeed = Chr(10); - fancySettings.stackIndentation = true; - fancySettings.subObjectIndentation = " "; - fancySettings.subArrayIndentation = ""; - fancySettings.afterObjectOpening = lineFeed; - fancySettings.beforeObjectEnding = lineFeed; - fancySettings.beforePropertyValue = " "; - fancySettings.afterObjectComma = lineFeed; - fancySettings.beforeElement = " "; - fancySettings.afterArrayComma = " "; - return fancySettings; -} - +/** + * Prepares a `string` to be displayed as textual JSON representation by + * replacing certain characters with their escaped sequences. + * + * @param input String value to display inside a text representation of + * a JSON data. + * @result Representation of an `input` that can be included in text form of + * JSON data. + */ protected final function string DisplayJSONString(string input) { // Convert control characters (+ other, specified by JSON) @@ -317,6 +560,36 @@ protected final function string DisplayJSONString(string input) return ("\"" $ input $ "\""); } +// helper function to prepare fancy display settings, because it is a bitch to +// include a `string` with new line symbol in `defaultproperties`. +private final function JSONDisplaySettings GetFancySettings() +{ + local string lineFeed; + local JSONDisplaySettings fancySettings; + lineFeed = Chr(10); + fancySettings.stackIndentation = true; + fancySettings.subObjectIndentation = " "; + fancySettings.subArrayIndentation = ""; + fancySettings.afterObjectOpening = lineFeed; + fancySettings.beforeObjectEnding = lineFeed; + fancySettings.beforePropertyValue = " "; + fancySettings.afterObjectComma = lineFeed; + fancySettings.beforeElement = " "; + fancySettings.afterArrayComma = " "; + return fancySettings; +} + +/** + * Helper function that prepares `JSONDisplaySettings` to be used for + * a folded object / array to make it more human-readable thanks to + * sub-object/-arrays indentation. + * + * @param inputSettings Settings to modify, passed variable will + * remain unchanged. + * @param indentingArray True if we need to modify settings for + * a folded array and `false` if for the object. + * @return Modified `inputSettings`, with added indentation. + */ protected final function JSONDisplaySettings IndentSettings( JSONDisplaySettings inputSettings, optional bool indentingArray) @@ -354,41 +627,19 @@ protected final function JSONDisplaySettings IndentSettings( return indentedSettings; } -public function bool ParseIntoSelfWith(Parser parser) -{ - return false; -} - -public final function bool ParseIntoSelf(Text source) -{ - local bool successfullyParsed; - local Parser jsonParser; - jsonParser = _.text.Parse(source); - successfullyParsed = ParseIntoSelfWith(jsonParser); - _.memory.Free(jsonParser); - return successfullyParsed; -} - -public final function bool ParseIntoSelfString(string source) -{ - local bool successfullyParsed; - local Parser jsonParser; - jsonParser = _.text.ParseString(source); - successfullyParsed = ParseIntoSelfWith(jsonParser); - _.memory.Free(jsonParser); - return successfullyParsed; -} - -public final function bool ParseIntoSelfRaw(array rawSource) -{ - local bool successfullyParsed; - local Parser jsonParser; - jsonParser = _.text.ParseRaw(rawSource); - successfullyParsed = ParseIntoSelfWith(jsonParser); - _.memory.Free(jsonParser); - return successfullyParsed; -} - +/** + * Uses given parser to parse a single (possibly complex like JSON object + * or array) JSON value. + * + * @param parser Parser that method would use to parse JSON value from + * wherever it left. It's confirmed will not be changed, but if parsing + * was successful, - it will point at the next available character. + * Do not treat `parser` being in a non-failed state as a confirmation of + * successful parsing: JSON parsing might fail regardless. + * Check return value for that. + * @return Parsed JSON value as `JStorageAtom`. + * If parsing has failed it will have the `JSON_Undefined` type. + */ protected final function JStorageAtom ParseAtom(Parser parser) { local Parser.ParserState initState; @@ -396,41 +647,55 @@ protected final function JStorageAtom ParseAtom(Parser parser) if (parser == none) return newAtom; if (!parser.Ok()) return newAtom; initState = parser.GetCurrentState(); - parser.Skip().Confirm(); if (parser.MStringLiteral(newAtom.stringValue).Ok()) { newAtom.type = JSON_String; return newAtom; } - newAtom = ParseLiteral(parser.R()); + newAtom = ParseLiteral(parser.RestoreState(initState)); if (newAtom.type != JSON_Undefined) { return newAtom; } - newAtom = ParseComplex(parser.R()); + newAtom = ParseComplex(parser.RestoreState(initState)); if (newAtom.type != JSON_Undefined) { return newAtom; } - newAtom = ParseNumber(parser.R()); + newAtom = ParseNumber(parser.RestoreState(initState)); if (newAtom.type == JSON_Undefined) { parser.RestoreState(initState); } return newAtom; } +/** + * Uses given parser to parse a "literal" JSON value: + * "true", "false" or "null". + * + * @param parser Parser that method would use to parse JSON value from + * wherever it left. It's confirmed will not be changed, but if parsing + * was successful, - it will point at the next available character. + * Do not treat `parser` being in a non-failed state as a confirmation of + * successful parsing: JSON parsing might fail regardless. + * Check return value for that. + * @return Parsed JSON value as `JStorageAtom`. + * If parsing has failed it will have the `JSON_Undefined` type. + */ protected final function JStorageAtom ParseLiteral(Parser parser) { - local JStorageAtom newAtom; + local JStorageAtom newAtom; + local Parser.ParserState initState; + initState = parser.GetCurrentState(); if (parser.Match("null", true).Ok()) { newAtom.type = JSON_Null; return newAtom; } - if (parser.R().Match("false", true).Ok()) + if (parser.RestoreState(initState).Match("false", true).Ok()) { newAtom.type = JSON_Boolean; return newAtom; } - if (parser.R().Match("true", true).Ok()) + if (parser.RestoreState(initState).Match("true", true).Ok()) { newAtom.type = JSON_Boolean; newAtom.booleanValue = true; @@ -438,21 +703,36 @@ protected final function JStorageAtom ParseLiteral(Parser parser) } } +/** + * Uses given parser to parse a complex JSON value: JSON object or array. + * + * @param parser Parser that method would use to parse JSON value from + * wherever it left. It's confirmed will not be changed, but if parsing + * was successful, - it will point at the next available character. + * Do not treat `parser` being in a non-failed state as a confirmation of + * successful parsing: JSON parsing might fail regardless. + * Check return value for that. + * @return Parsed JSON value as `JStorageAtom`. + * If parsing has failed it will have the `JSON_Undefined` type. + */ protected final function JStorageAtom ParseComplex(Parser parser) { - local JStorageAtom newAtom; + local JStorageAtom newAtom; + local Parser.ParserState initState; + initState = parser.GetCurrentState(); if (parser.Match("{").Ok()) { newAtom.complexValue = _.json.NewObject(); newAtom.type = JSON_Object; } - else if (parser.R().Match("[").Ok()) + else if (parser.RestoreState(initState).Match("[").Ok()) { newAtom.complexValue = _.json.NewArray(); newAtom.type = JSON_Array; } + parser.RestoreState(initState); if ( newAtom.complexValue != none - && newAtom.complexValue.ParseIntoSelfWith(parser.R())) { + && newAtom.complexValue.ParseIntoSelfWith(parser)) { return newAtom; } newAtom.type = JSON_Undefined; @@ -460,10 +740,23 @@ protected final function JStorageAtom ParseComplex(Parser parser) return newAtom; } +/** + * Uses given parser to parse a numeric JSON value. + * + * @param parser Parser that method would use to parse JSON value from + * wherever it left. It's confirmed will not be changed, but if parsing + * was successful, - it will point at the next available character. + * Do not treat `parser` being in a non-failed state as a confirmation of + * successful parsing: JSON parsing might fail regardless. + * Check return value for that. + * @return Parsed JSON value as `JStorageAtom`. + * If parsing has failed it will have the `JSON_Undefined` type. + */ protected final function JStorageAtom ParseNumber(Parser parser) { local JStorageAtom newAtom; - local Parser.ParserState integerParsedState; + local Parser.ParserState initState, integerParsedState; + initState = parser.GetCurrentState(); if (!parser.MInteger(newAtom.numberValueAsInt).Ok()) { return newAtom; } @@ -474,7 +767,7 @@ protected final function JStorageAtom ParseNumber(Parser parser) if ( parser.Match(".").Ok() || parser.RestoreState(integerParsedState).Match("e", true).Ok()) { - parser.R().MNumber(newAtom.numberValue); + parser.RestoreState(initState).MNumber(newAtom.numberValue); return newAtom; } parser.RestoreState(integerParsedState); diff --git a/sources/Data/JSON/Tests/TEST_JSON.uc b/sources/Data/JSON/Tests/TEST_JSON.uc index ec581f4..9798343 100644 --- a/sources/Data/JSON/Tests/TEST_JSON.uc +++ b/sources/Data/JSON/Tests/TEST_JSON.uc @@ -28,7 +28,7 @@ protected static function TESTS() local JObject jsonData; jsonData = _().json.newObject(); Test_ObjectGetSetRemove(); - Test_ObjectKeys(); + Test_ObjectPropertyNames(); Test_ArrayGetSetRemove(); Test_JSONComparison(); Test_JSONCloning(); @@ -53,7 +53,7 @@ protected static function Test_ObjectGetSetRemove() protected static function Test_ArrayGetSetRemove() { - Context("Testing get/set/remove functions for JSON arrays"); + Context("Testing get/set/remove functions for JSON arrays."); SubTest_ArrayUndefined(); SubTest_ArrayStringGetSetRemove(); SubTest_ArrayClassGetSetRemove(); @@ -74,7 +74,7 @@ protected static function SubTest_Undefined() local JObject testJSON; testJSON = _().json.newObject(); - Context("Testing how `JObject` handles undefined values"); + Context("Testing how `JObject` handles undefined values."); Issue("Undefined variable doesn't have proper type."); TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Undefined); @@ -96,27 +96,27 @@ protected static function SubTest_BooleanGetSetRemove() testJSON.SetBoolean("some_boolean", true); Context("Testing `JObject`'s get/set/remove functions for" @ - "boolean variables"); - Issue("Boolean type isn't properly set by `SetBoolean`"); + "boolean variables."); + Issue("Boolean type isn't properly set by `SetBoolean()`."); TEST_ExpectTrue(testJSON.GetTypeOf("some_boolean") == JSON_Boolean); - Issue("Variable value is incorrectly assigned by `SetBoolean`"); + Issue("Variable value is incorrectly assigned by `SetBoolean()`."); TEST_ExpectTrue(testJSON.GetBoolean("some_boolean") == true); - Issue("Variable value isn't correctly reassigned by `SetBoolean`"); + Issue("Variable value isn't correctly reassigned by `SetBoolean()`."); testJSON.SetBoolean("some_boolean", false); TEST_ExpectTrue(testJSON.GetBoolean("some_boolean") == false); Issue( "Getting boolean variable as a wrong type" @ - "doesn't yield default value"); + "doesn't yield default value."); TEST_ExpectTrue(testJSON.GetNumber("some_boolean", 7) == 7); - Issue("Boolean variable isn't being properly removed"); + Issue("Boolean variable isn't being properly removed."); testJSON.RemoveValue("some_boolean"); TEST_ExpectTrue(testJSON.GetTypeOf("some_boolean") == JSON_Undefined); Issue( "Getters don't return default value for missing key that" @ - "previously stored boolean value, that got removed"); + "previously stored boolean value, that got removed."); TEST_ExpectTrue(testJSON.GetBoolean("some_boolean", true) == true); } @@ -127,32 +127,32 @@ protected static function SubTest_StringGetSetRemove() testJSON.SetString("some_string", "first string"); Context("Testing `JObject`'s get/set/remove functions for" @ - "string variables"); - Issue("String type isn't properly set by `SetString`"); + "string variables."); + Issue("String type isn't properly set by `SetString()`."); TEST_ExpectTrue(testJSON.GetTypeOf("some_string") == JSON_String); - Issue("Value is incorrectly assigned by `SetString`"); + Issue("Value is incorrectly assigned by `SetString()`."); TEST_ExpectTrue(testJSON.GetString("some_string") == "first string"); Issue( "Providing default variable value makes 'GetString'" @ - "return wrong value"); + "return wrong value."); TEST_ExpectTrue( testJSON.GetString("some_string", "alternative") == "first string"); - Issue("Variable value isn't correctly reassigned by `SetString`"); + Issue("Variable value isn't correctly reassigned by `SetString()`."); testJSON.SetString("some_string", "new string!~"); TEST_ExpectTrue(testJSON.GetString("some_string") == "new string!~"); Issue( "Getting string variable as a wrong type" @ - "doesn't yield default value"); + "doesn't yield default value."); TEST_ExpectTrue(testJSON.GetBoolean("some_string", true) == true); - Issue("String variable isn't being properly removed"); + Issue("String variable isn't being properly removed."); testJSON.RemoveValue("some_string"); TEST_ExpectTrue(testJSON.GetTypeOf("some_string") == JSON_Undefined); Issue( "Getters don't return default value for missing key that" @ - "previously stored string value, but got removed"); + "previously stored string value, but got removed."); TEST_ExpectTrue(testJSON.GetString("some_string", "other") == "other"); } @@ -163,32 +163,32 @@ protected static function SubTest_ClassGetSetRemove() testJSON.SetClass("info_class", class'Info'); Context("Testing `JObject`'s get/set/remove functions for" @ - "class variables"); - Issue("String type isn't properly set by `SetClass`"); + "class variables."); + Issue("String type isn't properly set by `SetClass()`."); TEST_ExpectTrue(testJSON.GetTypeOf("info_class") == JSON_String); - Issue("Value is incorrectly assigned by `SetClass`"); + Issue("Value is incorrectly assigned by `SetClass()`."); TEST_ExpectTrue(testJSON.GetClass("info_class") == class'Info'); - Issue( "Providing default variable value makes 'GetClass'" @ - "return wrong value"); + Issue( "Providing default variable value makes 'GetClass()'" @ + "return wrong value."); TEST_ExpectTrue( testJSON.GetClass("info_class", class'Actor') == class'Info'); - Issue("Variable value isn't correctly reassigned by `SetClass`"); + Issue("Variable value isn't correctly reassigned by `SetClass`."); testJSON.SetClass("info_class", class'ReplicationInfo'); TEST_ExpectTrue(testJSON.GetClass("info_class") == class'ReplicationInfo'); Issue( "Getting class variable as a wrong type" @ - "doesn't yield default value"); + "doesn't yield default value."); TEST_ExpectTrue(testJSON.GetBoolean("info_class", true) == true); - Issue("Class variable isn't being properly removed"); + Issue("Class variable isn't being properly removed."); testJSON.RemoveValue("info_class"); TEST_ExpectTrue(testJSON.GetTypeOf("info_class") == JSON_Undefined); Issue( "Getters don't return default value for missing key that" @ - "previously stored class value, but got removed"); + "previously stored class value, but got removed."); TEST_ExpectTrue( testJSON.GetClass("info_class", class'Actor') == class'Actor'); } @@ -229,18 +229,18 @@ protected static function SubTest_NumberGetSetRemove() testJSON.SetNumber("some_number", 3.5); Context("Testing `JObject`'s get/set/remove functions for" @ - "number variables as floats"); - Issue("Number type isn't properly set by `SetNumber`"); + "number variables as floats."); + Issue("Number type isn't properly set by `SetNumber()`."); TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Number); - Issue("Value is incorrectly assigned by `SetNumber`"); + Issue("Value is incorrectly assigned by `SetNumber()`."); TEST_ExpectTrue(testJSON.GetNumber("some_number") == 3.5); - Issue( "Providing default variable value makes 'GetNumber'" @ - "return wrong value"); + Issue( "Providing default variable value makes 'GetNumber()'" @ + "return wrong value."); TEST_ExpectTrue(testJSON.GetNumber("some_number", 5) == 3.5); - Issue("Variable value isn't correctly reassigned by `SetNumber`"); + Issue("Variable value isn't correctly reassigned by `SetNumber()`."); testJSON.SetNumber("some_number", 7); TEST_ExpectTrue(testJSON.GetNumber("some_number") == 7); @@ -248,12 +248,12 @@ protected static function SubTest_NumberGetSetRemove() "doesn't yield default value."); TEST_ExpectTrue(testJSON.GetString("some_number", "default") == "default"); - Issue("Number type isn't being properly removed"); + Issue("Number type isn't being properly removed."); testJSON.RemoveValue("some_number"); TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Undefined); Issue( "Getters don't return default value for missing key that" @ - "previously stored number value, that got removed"); + "previously stored number value, that got removed."); TEST_ExpectTrue(testJSON.GetNumber("some_number", 13) == 13); } @@ -264,18 +264,18 @@ protected static function SubTest_IntegerGetSetRemove() testJSON.SetInteger("some_number", 33653); Context("Testing `JObject`'s get/set/remove functions for" @ - "number variables as integers"); - Issue("Number type isn't properly set by `SetInteger`"); + "number variables as integers."); + Issue("Number type isn't properly set by `SetInteger`."); TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Number); - Issue("Value is incorrectly assigned by `SetInteger`"); + Issue("Value is incorrectly assigned by `SetInteger()`."); TEST_ExpectTrue(testJSON.GetInteger("some_number") == 33653); - Issue( "Providing default variable value makes 'GetInteger'" @ - "return wrong value"); + Issue( "Providing default variable value makes 'GetInteger()'" @ + "return wrong value."); TEST_ExpectTrue(testJSON.GetInteger("some_number", 5) == 33653); - Issue("Variable value isn't correctly reassigned by `SetInteger`"); + Issue("Variable value isn't correctly reassigned by `SetInteger()`."); testJSON.SetInteger("some_number", MaxInt); TEST_ExpectTrue(testJSON.GetInteger("some_number") == MaxInt); @@ -283,12 +283,12 @@ protected static function SubTest_IntegerGetSetRemove() "doesn't yield default value."); TEST_ExpectTrue(testJSON.GetString("some_number", "default") == "default"); - Issue("Number type isn't being properly removed"); + Issue("Number type isn't being properly removed."); testJSON.RemoveValue("some_number"); TEST_ExpectTrue(testJSON.GetTypeOf("some_number") == JSON_Undefined); Issue( "Getters don't return default value for missing key that" @ - "previously stored number value, that got removed"); + "previously stored number value, that got removed."); TEST_ExpectTrue(testJSON.GetInteger("some_number", -235) == -235); } @@ -320,27 +320,27 @@ protected static function SubTest_NullGetSetRemove() testJSON = _().json.newObject(); Context("Testing `JObject`'s get/set/remove functions for" @ - "null values"); - Issue("Undefined variable is incorrectly considered `null`"); + "null values."); + Issue("Undefined variable is incorrectly considered null."); TEST_ExpectFalse(testJSON.IsNull("some_var")); - Issue("Number variable is incorrectly considered `null`"); + Issue("Number variable is incorrectly considered null."); testJSON.SetNumber("some_var", 4); TEST_ExpectFalse(testJSON.IsNull("some_var")); - Issue("Boolean variable is incorrectly considered `null`"); + Issue("Boolean variable is incorrectly considered null."); testJSON.SetBoolean("some_var", true); TEST_ExpectFalse(testJSON.IsNull("some_var")); - Issue("String variable is incorrectly considered `null`"); + Issue("String variable is incorrectly considered null."); testJSON.SetString("some_var", "string"); TEST_ExpectFalse(testJSON.IsNull("some_var")); - Issue("Null value is incorrectly assigned"); + Issue("Null value is incorrectly assigned."); testJSON.SetNull("some_var"); TEST_ExpectTrue(testJSON.IsNull("some_var")); - Issue("Null type isn't properly set by `SetNumber`"); + Issue("Null type isn't properly set by `SetNumber`."); TEST_ExpectTrue(testJSON.GetTypeOf("some_var") == JSON_Null); Issue("Null value isn't being properly removed."); @@ -355,17 +355,14 @@ protected static function SubTest_MultipleVariablesGetSet() local JObject testJSON; testJSON = _().json.newObject(); Context("Testing how `JObject` handles addition, change and removal" @ - "of relatively large (hundreds) number of variables"); - for (i = 0; i < 2000; i += 1) - { + "of relatively large (hundreds) number of variables."); + for (i = 0; i < 2000; i += 1) { testJSON.SetNumber("num" $ string(i), 4 * i*i - 2.6 * i + 0.75); } - for (i = 0; i < 500; i += 1) - { + for (i = 0; i < 500; i += 1) { testJSON.SetString("num" $ string(i), "str" $ string(Sin(i))); } - for (i = 1500; i < 2000; i += 1) - { + for (i = 1500; i < 2000; i += 1) { testJSON.RemoveValue("num" $ string(i)); } allValuesCorrect = true; @@ -375,19 +372,19 @@ protected static function SubTest_MultipleVariablesGetSet() { correctValue = ( testJSON.GetString("num" $ string(i)) == ("str" $ string(Sin(i))) ); - Issue("Variables are incorrectly overwritten"); + Issue("Variables are incorrectly overwritten."); } else if(i < 1500) { correctValue = ( testJSON.GetNumber("num" $ string(i)) == 4 * i*i - 2.6 * i + 0.75); - Issue("Variables are lost"); + Issue("Variables are lost."); } else { correctValue = ( testJSON.GetTypeOf("num" $ string(i)) == JSON_Undefined); - Issue("Variables aren't removed"); + Issue("Variables aren't removed."); } if (!correctValue) { @@ -401,7 +398,7 @@ protected static function SubTest_MultipleVariablesGetSet() protected static function SubTest_Object() { local JObject testObject; - Context("Testing setters and getters for folded objects"); + Context("Testing setters and getters for folded objects."); testObject = _().json.newObject(); testObject.CreateObject("folded"); testObject.GetObject("folded").CreateObject("folded"); @@ -411,72 +408,73 @@ protected static function SubTest_Object() .GetObject("folded") .SetString("in", "string inside"); - Issue("Addressing variables in root object doesn't work"); + Issue("Addressing variables in root object doesn't work."); TEST_ExpectTrue(testObject.GetString("out", "default") == "string outside"); - Issue("Addressing variables in folded object doesn't work"); + Issue("Addressing variables in folded object doesn't work."); TEST_ExpectTrue(testObject.GetObject("folded").GetNumber("mid", 1) == 8); - Issue("Addressing plain variables in folded (twice) object doesn't work"); + Issue("Addressing plain variables in folded (twice) object doesn't work."); TEST_ExpectTrue(testObject.GetObject("folded").GetObject("folded") .GetString("in", "default") == "string inside"); } -protected static function Test_ObjectKeys() +protected static function Test_ObjectPropertynames() { local int i; local bool varFound, clsFound, objFound; local JObject testObject; - local array keys; + local array names; testObject = _().json.newObject(); - Context("Testing getting list of keys from the `JObject`."); - Issue("Just created `JObject` returns non-empty key list."); - TEST_ExpectTrue(testObject.GetKeys().length == 0); - - Issue("`JObject` returns incorrect key list."); - keys = testObject.SetInteger("var", 7).SetClass("cls", class'Actor') - .CreateObject("obj").GetKeys(); - TEST_ExpectTrue(keys.length == 3); - for (i = 0; i < keys.length; i += 1) + Context("Testing getting list of property names from the `JObject`."); + Issue("Just created `JObject` returns non-empty names list."); + TEST_ExpectTrue(testObject.GetPropertyNames().length == 0); + + Issue("`JObject` returns incorrect names list."); + names = testObject.SetInteger("var", 7).SetClass("cls", class'Actor') + .CreateObject("obj").GetPropertyNames(); + TEST_ExpectTrue(names.length == 3); + for (i = 0; i < names.length; i += 1) { - if (keys[i] == "var") { varFound = true; } - if (keys[i] == "cls") { clsFound = true; } - if (keys[i] == "obj") { objFound = true; } + if (names[i] == "var") { varFound = true; } + if (names[i] == "cls") { clsFound = true; } + if (names[i] == "obj") { objFound = true; } } TEST_ExpectTrue(varFound && clsFound && objFound); - Issue("`JObject` returns incorrect key list after removing an element."); - keys = testObject.RemoveValue("cls").GetKeys(); - TEST_ExpectTrue(keys.length == 2); + Issue("`JObject` returns incorrect names list after removing an element."); + names = testObject.RemoveValue("cls").GetPropertyNames(); + TEST_ExpectTrue(names.length == 2); varFound = false; objFound = false; - for (i = 0; i < keys.length; i += 1) + for (i = 0; i < names.length; i += 1) { - if (keys[i] == "var") { varFound = true; } - if (keys[i] == "obj") { objFound = true; } + if (names[i] == "var") { varFound = true; } + if (names[i] == "obj") { objFound = true; } } TEST_ExpectTrue(varFound && objFound); - Issue("`JObject` returns incorrect key list after removing all elements."); - keys = testObject.RemoveValue("var").RemoveValue("obj").GetKeys(); - TEST_ExpectTrue(keys.length == 0); + Issue("`JObject` returns incorrect names list after removing" + @ "all elements."); + names = testObject.RemoveValue("var").RemoveValue("obj").GetPropertyNames(); + TEST_ExpectTrue(names.length == 0); } protected static function SubTest_ArrayUndefined() { local JArray testJSON; testJSON = _().json.newArray(); - Context("Testing how `JArray` handles undefined values"); - Issue("Undefined variable doesn't have `JSON_Undefined` type"); + Context("Testing how `JArray` handles undefined values."); + Issue("Undefined variable doesn't have `JSON_Undefined` type."); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); - Issue("There is a variable in an empty object after `GetTypeOf` call"); + Issue("There is a variable in an empty object after `GetTypeOf` call."); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); - Issue("Negative index refers to a defined value"); + Issue("Negative index refers to a defined value."); TEST_ExpectTrue(testJSON.GetTypeOf(-1) == JSON_Undefined); - Issue("Getters don't return default values for undefined variables"); + Issue("Getters don't return default values for undefined variables."); TEST_ExpectTrue(testJSON.GetNumber(0, 0) == 0); TEST_ExpectTrue(testJSON.GetString(0, "") == ""); TEST_ExpectTrue(testJSON.GetBoolean(0, false) == false); @@ -484,7 +482,7 @@ protected static function SubTest_ArrayUndefined() TEST_ExpectNone(testJSON.GetArray(0)); Issue( "Getters don't return user-defined default values for" @ - "undefined variables"); + "undefined variables."); TEST_ExpectTrue(testJSON.GetNumber(0, 10) == 10); TEST_ExpectTrue(testJSON.GetString(0, "test") == "test"); TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true); @@ -497,27 +495,27 @@ protected static function SubTest_ArrayBooleanGetSetRemove() testJSON.SetBoolean(0, true); Context("Testing `JArray`'s get/set/remove functions for" @ - "boolean variables"); - Issue("Boolean type isn't properly set by `SetBoolean`"); + "boolean variables."); + Issue("Boolean type isn't properly set by `SetBoolean()`."); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Boolean); - Issue("Value is incorrectly assigned by `SetBoolean`"); + Issue("Value is incorrectly assigned by `SetBoolean()`."); TEST_ExpectTrue(testJSON.GetBoolean(0) == true); testJSON.SetBoolean(0, false); - Issue("Variable value isn't correctly reassigned by `SetBoolean`"); + Issue("Variable value isn't correctly reassigned by `SetBoolean()`."); TEST_ExpectTrue(testJSON.GetBoolean(0) == false); Issue( "Getting boolean variable as a wrong type" @ - "doesn't yield default value"); + "doesn't yield default value."); TEST_ExpectTrue(testJSON.GetNumber(0, 7) == 7); - Issue("Boolean variable isn't being properly removed"); + Issue("Boolean variable isn't being properly removed."); testJSON.RemoveValue(0); TEST_ExpectTrue( testJSON.GetTypeOf(0) == JSON_Undefined); Issue( "Getters don't return default value for missing key that" @ - "previously stored boolean value, but got removed"); + "previously stored boolean value, but got removed."); TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true); } @@ -528,31 +526,31 @@ protected static function SubTest_ArrayStringGetSetRemove() testJSON.SetString(0, "first string"); Context("Testing `JArray`'s get/set/remove functions for" @ - "string variables"); - Issue("String type isn't properly set by `SetString`"); + "string variables."); + Issue("String type isn't properly set by `SetString()`."); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_String); - Issue("Value is incorrectly assigned by `SetString`"); + Issue("Value is incorrectly assigned by `SetString()`."); TEST_ExpectTrue(testJSON.GetString(0) == "first string"); - Issue( "Providing default variable value makes 'GetString'" @ - "return incorrect value"); + Issue( "Providing default variable value makes 'GetString()'" @ + "return incorrect value."); TEST_ExpectTrue(testJSON.GetString(0, "alternative") == "first string"); - Issue("Variable value isn't correctly reassigned by `SetString`"); + Issue("Variable value isn't correctly reassigned by `SetString()`."); testJSON.SetString(0, "new string!~"); TEST_ExpectTrue(testJSON.GetString(0) == "new string!~"); Issue( "Getting string variable as a wrong type" @ - "doesn't yield default value"); + "doesn't yield default value."); TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true); - Issue("Boolean variable isn't being properly removed"); + Issue("Boolean variable isn't being properly removed."); testJSON.RemoveValue(0); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); Issue( "Getters don't return default value for missing key that" @ - "previously stored string value, but got removed"); + "previously stored string value, but got removed."); TEST_ExpectTrue(testJSON.GetString(0, "other") == "other"); } @@ -563,31 +561,31 @@ protected static function SubTest_ArrayClassGetSetRemove() testJSON.SetClass(0, class'Actor'); Context("Testing `JArray`'s get/set/remove functions for" @ - "class variables"); - Issue("Class type isn't properly set by `SetClass`"); + "class variables."); + Issue("Class type isn't properly set by `SetClass()`."); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_String); - Issue("Value is incorrectly assigned by `SetClass`"); + Issue("Value is incorrectly assigned by `SetClass()`."); TEST_ExpectTrue(testJSON.GetClass(0) == class'Actor'); - Issue( "Providing default variable value makes `GetClass`" @ - "return incorrect value"); + Issue( "Providing default variable value makes `GetClass()`" @ + "return incorrect value."); TEST_ExpectTrue(testJSON.GetClass(0, class'GameInfo') == class'Actor'); - Issue("Variable value isn't correctly reassigned by `SetClass`"); + Issue("Variable value isn't correctly reassigned by `SetClass()`."); testJSON.SetClass(0, class'Info'); TEST_ExpectTrue(testJSON.GetClass(0) == class'Info'); Issue( "Getting class variable as a wrong type" @ - "doesn't yield default value"); + "doesn't yield default value."); TEST_ExpectTrue(testJSON.GetBoolean(0, true) == true); - Issue("Boolean variable isn't being properly removed"); + Issue("Boolean variable isn't being properly removed."); testJSON.RemoveValue(0); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); Issue( "Getters don't return default value for missing key that" @ - "previously stored string value, but got removed"); + "previously stored string value, but got removed."); TEST_ExpectTrue(testJSON.GetClass(0, class'Mutator') == class'Mutator'); } @@ -625,31 +623,31 @@ protected static function SubTest_ArrayNumberGetSetRemove() testJSON.SetNumber(0, 3.5); Context("Testing `JArray`'s get/set/remove functions for" @ - "number variables"); - Issue("Number type isn't properly set by `SetNumber`"); + "number variables."); + Issue("Number type isn't properly set by `SetNumber()`."); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Number); - Issue("Value is incorrectly assigned by `SetNumber`"); + Issue("Value is incorrectly assigned by `SetNumber()`."); TEST_ExpectTrue(testJSON.GetNumber(0) == 3.5); - Issue( "Providing default variable value makes 'GetNumber'" @ - "return incorrect value"); + Issue( "Providing default variable value makes 'GetNumber()'" @ + "return incorrect value."); TEST_ExpectTrue(testJSON.GetNumber(0, 5) == 3.5); - Issue("Variable value isn't correctly reassigned by `SetNumber`"); + Issue("Variable value isn't correctly reassigned by `SetNumber()`."); testJSON.SetNumber(0, 7); TEST_ExpectTrue(testJSON.GetNumber(0) == 7); Issue( "Getting number variable as a wrong type" @ - "doesn't yield default value"); + "doesn't yield default value."); TEST_ExpectTrue(testJSON.GetString(0, "default") == "default"); - Issue("Number type isn't being properly removed"); + Issue("Number type isn't being properly removed."); testJSON.RemoveValue(0); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); Issue( "Getters don't return default value for missing key that" @ - "previously stored number value, but got removed"); + "previously stored number value, but got removed."); TEST_ExpectTrue(testJSON.GetNumber(0, 13) == 13); } @@ -660,31 +658,31 @@ protected static function SubTest_ArrayIntegerGetSetRemove() testJSON.SetInteger(0, 19); Context("Testing `JArray`'s get/set/remove functions for" @ - "integer variables"); - Issue("Integer type isn't properly set by `SetInteger`"); + "integer variables."); + Issue("Integer type isn't properly set by `SetInteger()`."); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Number); - Issue("Value is incorrectly assigned by `SetInteger`"); + Issue("Value is incorrectly assigned by `SetInteger()`."); TEST_ExpectTrue(testJSON.GetInteger(0) == 19); - Issue( "Providing default variable value makes `GetInteger`" @ - "return incorrect value"); + Issue( "Providing default variable value makes `GetInteger()`" @ + "return incorrect value."); TEST_ExpectTrue(testJSON.GetInteger(0, 5) == 19); - Issue("Variable value isn't correctly reassigned by `SetInteger`"); + Issue("Variable value isn't correctly reassigned by `SetInteger()`."); testJSON.SetInteger(0, MaxInt); TEST_ExpectTrue(testJSON.GetInteger(0) == MaxInt); Issue( "Getting integer variable as a wrong type" @ - "doesn't yield default value"); + "doesn't yield default value."); TEST_ExpectTrue(testJSON.GetString(0, "default") == "default"); - Issue("Integer type isn't being properly removed"); + Issue("Integer type isn't being properly removed."); testJSON.RemoveValue(0); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); Issue( "Getters don't return default value for missing key that" @ - "previously stored integer value, but got removed"); + "previously stored integer value, but got removed."); TEST_ExpectTrue(testJSON.GetInteger(0, 13) == 13); } @@ -716,22 +714,22 @@ protected static function SubTest_ArrayNullGetSetRemove() testJSON = _().json.newArray(); Context("Testing `JArray`'s get/set/remove functions for" @ - "null values"); + "null values."); - Issue("Undefined variable is incorrectly considered `null`"); + Issue("Undefined variable is incorrectly considered null."); TEST_ExpectFalse(testJSON.IsNull(0)); TEST_ExpectFalse(testJSON.IsNull(2)); TEST_ExpectFalse(testJSON.IsNull(-1)); - Issue("Number variable is incorrectly considered `null`"); + Issue("Number variable is incorrectly considered null."); testJSON.SetNumber(0, 4); TEST_ExpectFalse(testJSON.IsNull(0)); - Issue("Boolean variable is incorrectly considered `null`"); + Issue("Boolean variable is incorrectly considered null."); testJSON.SetBoolean(0, true); TEST_ExpectFalse(testJSON.IsNull(0)); - Issue("String variable is incorrectly considered `null`"); + Issue("String variable is incorrectly considered null."); testJSON.SetString(0, "string"); TEST_ExpectFalse(testJSON.IsNull(0)); @@ -739,10 +737,10 @@ protected static function SubTest_ArrayNullGetSetRemove() testJSON.SetNull(0); TEST_ExpectTrue(testJSON.IsNull(0)); - Issue("Null type isn't properly set by `SetNumber`"); + Issue("Null type isn't properly set by `SetNumber`."); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Null); - Issue("Null value isn't being properly removed"); + Issue("Null value isn't being properly removed."); testJSON.RemoveValue(0); TEST_ExpectTrue(testJSON.GetTypeOf(0) == JSON_Undefined); } @@ -769,7 +767,7 @@ protected static function SubTest_ArrayMultipleVariablesStorage() testArray = Prepare_Array(); Context("Testing how `JArray` handles adding and" @ - "changing several variables"); + "changing several variables."); Issue("Stored values are compromised."); TEST_ExpectTrue(testArray.GetNumber(0) == 10.0f); TEST_ExpectTrue(testArray.GetString(1) == "test string"); @@ -784,7 +782,7 @@ protected static function SubTest_ArrayMultipleVariablesStorage() Issue( "After overwriting boolean value with a different type," @ "attempting go get it as a boolean gives old value," @ - "instead of default"); + "instead of default."); TEST_ExpectTrue(testArray.GetBoolean(3, false) == false); Issue("Type of the variable is incorrectly changed."); @@ -800,18 +798,18 @@ protected static function SubTest_ArrayMultipleVariablesRemoval() // [10.0, "test string", "another string", true, 0.0, {"var": 7.0}] Context("Testing how `JArray` handles adding and" @ - "removing several variables"); - Issue("Values are incorrectly removed"); + "removing several variables."); + Issue("Values are incorrectly removed."); testArray.RemoveValue(2); // [10.0, "test string", true, 0.0, {"var": 7.0}] - Issue("Values are incorrectly removed"); + Issue("Values are incorrectly removed."); TEST_ExpectTrue(testArray.GetNumber(0) == 10.0); TEST_ExpectTrue(testArray.GetString(1) == "test string"); TEST_ExpectTrue(testArray.GetBoolean(2) == true); TEST_ExpectTrue(testArray.GetNumber(3) == 0.0f); TEST_ExpectTrue(testArray.GetTypeOf(4) == JSON_Object); - Issue("First element incorrectly removed"); + Issue("First element incorrectly removed."); testArray.RemoveValue(0); // ["test string", true, 0.0, {"var": 7.0}] TEST_ExpectTrue(testArray.GetString(0) == "test string"); @@ -820,7 +818,7 @@ protected static function SubTest_ArrayMultipleVariablesRemoval() TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Object); TEST_ExpectTrue(testArray.GetObject(3).GetNumber("var") == 7.0); - Issue("Last element incorrectly removed"); + Issue("Last element incorrectly removed."); testArray.RemoveValue(3); // ["test string", true, 0.0] TEST_ExpectTrue(testArray.GetLength() == 3); @@ -828,7 +826,7 @@ protected static function SubTest_ArrayMultipleVariablesRemoval() TEST_ExpectTrue(testArray.GetBoolean(1) == true); TEST_ExpectTrue(testArray.GetNumber(2) == 0.0f); - Issue("Removing all elements is handled incorrectly"); + Issue("Removing all elements is handled incorrectly."); testArray.RemoveValue(0); testArray.RemoveValue(0); testArray.RemoveValue(0); @@ -846,8 +844,8 @@ protected static function SubTest_ArrayRemovingMultipleVariablesAtOnce() .AddNumber(7.0); Context("Testing how `JArray`' handles removing" @ - "multiple elements at once"); - Issue("Multiple values are incorrectly removed"); + "multiple elements at once."); + Issue("Multiple values are incorrectly removed."); testArray.RemoveValue(1, 2); TEST_ExpectTrue(testArray.GetLength() == 2); TEST_ExpectTrue(testArray.GetNumber(1) == 7.0); @@ -859,20 +857,20 @@ protected static function SubTest_ArrayRemovingMultipleVariablesAtOnce() // Current array: // [10.0, 7.0, 4.0, "test string", "another string", 8.0] - Issue("Last value is incorrectly removed"); + Issue("Last value is incorrectly removed."); testArray.RemoveValue(5, 1); TEST_ExpectTrue(testArray.GetLength() == 5); TEST_ExpectTrue(testArray.GetString(4) == "another string"); // Current array: // [10.0, 7.0, 4.0, "test string", "another string"] - Issue("Tail elements are incorrectly removed"); + Issue("Tail elements are incorrectly removed."); testArray.RemoveValue(3, 4); TEST_ExpectTrue(testArray.GetLength() == 3); TEST_ExpectTrue(testArray.GetNumber(0) == 10.0); TEST_ExpectTrue(testArray.GetNumber(2) == 4.0); - Issue("Array empties incorrectly"); + Issue("Array empties incorrectly."); testArray.RemoveValue(0, testArray.GetLength()); TEST_ExpectTrue(testArray.GetLength() == 0); TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Undefined); @@ -884,15 +882,14 @@ protected static function SubTest_ArrayExpansions() local JArray testArray; testArray = _().json.newArray(); - Context("Testing how `JArray`' handles expansions/shrinking " @ - "via `SetLength()`"); - Issue("`SetLength()` doesn't properly expand empty array"); + Context("Testing how `JArray`' handles expansions/shrinking."); + Issue("`SetLength()` doesn't properly expand empty array."); testArray.SetLength(2); TEST_ExpectTrue(testArray.GetLength() == 2); TEST_ExpectTrue(testArray.GetTypeOf(0) == JSON_Null); TEST_ExpectTrue(testArray.GetTypeOf(1) == JSON_Null); - Issue("`SetLength()` doesn't properly expand non-empty array"); + Issue("`SetLength()` doesn't properly expand non-empty array."); testArray.AddNumber(1); testArray.SetLength(4); TEST_ExpectTrue(testArray.GetLength() == 4); @@ -912,14 +909,14 @@ protected static function SubSubTest_ArraySetNumberExpansions() testArray = _().json.newArray(); Context("Testing how `JArray`' handles expansions via" @ - "`SetNumber()` function"); - Issue("Setters don't create correct first element"); + "`SetNumber()` method."); + Issue("Setters don't create correct first element."); testArray.SetNumber(0, 1); TEST_ExpectTrue(testArray.GetLength() == 1); TEST_ExpectTrue(testArray.GetNumber(0) == 1); Issue( "`SetNumber()` doesn't properly define array when setting" @ - "value out-of-bounds"); + "value out-of-bounds."); testArray = _().json.newArray(); testArray.AddNumber(1); testArray.SetNumber(4, 2); @@ -929,13 +926,6 @@ protected static function SubSubTest_ArraySetNumberExpansions() TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null); TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null); TEST_ExpectTrue(testArray.GetNumber(4) == 2); - - Issue("`SetNumber()` expands array even when it told not to"); - testArray.SetNumber(6, 7, true); - TEST_ExpectTrue(testArray.GetLength() == 5); - TEST_ExpectTrue(testArray.GetNumber(6) == 0); - TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined); - TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined); } protected static function SubSubTest_ArraySetStringExpansions() @@ -944,14 +934,14 @@ protected static function SubSubTest_ArraySetStringExpansions() testArray = _().json.newArray(); Context("Testing how `JArray`' handles expansions via" @ - "`SetString()` function"); - Issue("Setters don't create correct first element"); + "`SetString()` method."); + Issue("Setters don't create correct first element."); testArray.SetString(0, "str"); TEST_ExpectTrue(testArray.GetLength() == 1); TEST_ExpectTrue(testArray.GetString(0) == "str"); Issue( "`SetString()` doesn't properly define array when setting" @ - "value out-of-bounds"); + "value out-of-bounds."); testArray = _().json.newArray(); testArray.AddString("str"); testArray.SetString(4, "str2"); @@ -961,13 +951,6 @@ protected static function SubSubTest_ArraySetStringExpansions() TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null); TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null); TEST_ExpectTrue(testArray.GetString(4) == "str2"); - - Issue("`SetString()` expands array even when it told not to"); - testArray.SetString(6, "new string", true); - TEST_ExpectTrue(testArray.GetLength() == 5); - TEST_ExpectTrue(testArray.GetString(6) == ""); - TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined); - TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined); } protected static function SubSubTest_ArraySetBooleanExpansions() @@ -976,14 +959,14 @@ protected static function SubSubTest_ArraySetBooleanExpansions() testArray = _().json.newArray(); Context("Testing how `JArray`' handles expansions via" @ - "`SetBoolean()` function"); - Issue("Setters don't create correct first element"); + "`SetBoolean()` method."); + Issue("Setters don't create correct first element."); testArray.SetBoolean(0, false); TEST_ExpectTrue(testArray.GetLength() == 1); TEST_ExpectTrue(testArray.GetBoolean(0) == false); Issue( "`SetBoolean()` doesn't properly define array when setting" @ - "value out-of-bounds"); + "value out-of-bounds."); testArray = _().json.newArray(); testArray.AddBoolean(true); testArray.SetBoolean(4, true); @@ -993,13 +976,6 @@ protected static function SubSubTest_ArraySetBooleanExpansions() TEST_ExpectTrue(testArray.GetTypeOf(2) == JSON_Null); TEST_ExpectTrue(testArray.GetTypeOf(3) == JSON_Null); TEST_ExpectTrue(testArray.GetBoolean(4) == true); - - Issue("`SetBoolean()` expands array even when it told not to"); - testArray.SetBoolean(6, true, true); - TEST_ExpectTrue(testArray.GetLength() == 5); - TEST_ExpectTrue(testArray.GetBoolean(6) == false); - TEST_ExpectTrue(testArray.GetTypeOf(5) == JSON_Undefined); - TEST_ExpectTrue(testArray.GetTypeOf(6) == JSON_Undefined); } protected static function JObject Prepare_FoldedObject() @@ -1024,7 +1000,7 @@ protected static function JObject Prepare_FoldedObject() protected static function Test_JSONComparison() { - Context("Testing comparison of JSON objects"); + Context("Testing comparison of JSON objects."); SubTest_JSONIsEqual(); SubTest_JSONIsSubsetOf(); SubTest_JSONCompare(); @@ -1083,7 +1059,8 @@ protected static function SubTest_JSONIsSubsetOf() TEST_ExpectTrue(empty.IsSubsetOf(test1)); TEST_ExpectFalse(test1.IsSubsetOf(empty)); - Issue("`IsSubsetOf()` incorrectly handles objects that cannot be compared."); + Issue("`IsSubsetOf()` incorrectly handles objects that cannot be" + @ "compared."); test2.GetObject("innerObject").GetArray("array").SetNull(1); TEST_ExpectFalse(test1.IsSubsetOf(test2)); TEST_ExpectFalse(test2.IsSubsetOf(test1)); @@ -1193,7 +1170,7 @@ protected static function SubTest_JSONObjectParsingWithParser() Issue("`ParseObjectWith()` cannot parse empty JSON object."); parsedObject = _().json.ParseObjectWith(_().text.ParseString("{}")); TEST_ExpectNotNone(parsedObject); - TEST_ExpectTrue(parsedObject.GetKeys().length == 0); + TEST_ExpectTrue(parsedObject.GetPropertyNames().length == 0); Issue("`ParseObjectWith()` doesn't report error when parsing an incorrect" @ "object."); @@ -1243,7 +1220,7 @@ protected static function SubTest_JSONArrayParsingWithParser() TEST_ExpectTrue(parsedArray.IsNull(0)); TEST_ExpectTrue(parsedArray.GetNumber(1) == 6734.9); TEST_ExpectTrue(parsedArray.GetString(2) == "what"); - TEST_ExpectTrue(parsedArray.GetObject(3).GetKeys().length == 0); + TEST_ExpectTrue(parsedArray.GetObject(3).GetPropertyNames().length == 0); Issue("`JArray.ParseIntoSelfWith()` cannot add new elements."); TEST_ExpectTrue(parsedArray.ParseIntoSelfWith( @@ -1258,7 +1235,7 @@ protected static function SubTest_JSONObjectParsingText() Issue("`ParseObject()` cannot parse empty JSON object."); parsedObject = _().json.ParseObject(_().text.FromString("{}")); TEST_ExpectNotNone(parsedObject); - TEST_ExpectTrue(parsedObject.GetKeys().length == 0); + TEST_ExpectTrue(parsedObject.GetPropertyNames().length == 0); Issue("`ParseObject()` doesn't report error when parsing an incorrect" @ "object."); @@ -1304,7 +1281,7 @@ protected static function SubTest_JSONArrayParsingText() TEST_ExpectTrue(parsedArray.IsNull(0)); TEST_ExpectTrue(parsedArray.GetNumber(1) == 6734.9); TEST_ExpectTrue(parsedArray.GetString(2) == "what"); - TEST_ExpectTrue(parsedArray.GetObject(3).GetKeys().length == 0); + TEST_ExpectTrue(parsedArray.GetObject(3).GetPropertyNames().length == 0); Issue("`JArray.ParseIntoSelf()` cannot add new elements."); TEST_ExpectTrue(parsedArray.ParseIntoSelf( @@ -1319,7 +1296,7 @@ protected static function SubTest_JSONObjectParsingRaw() Issue("`ParseObjectRaw()` cannot parse empty JSON object."); parsedObject = _().json.ParseObjectRaw(_().text.StringToRaw("{}")); TEST_ExpectNotNone(parsedObject); - TEST_ExpectTrue(parsedObject.GetKeys().length == 0); + TEST_ExpectTrue(parsedObject.GetPropertyNames().length == 0); Issue("`ParseObjectRaw()` doesn't report error when parsing an incorrect" @ "object."); @@ -1369,7 +1346,7 @@ protected static function SubTest_JSONArrayParsingRaw() TEST_ExpectTrue(parsedArray.IsNull(0)); TEST_ExpectTrue(parsedArray.GetNumber(1) == 6734.9); TEST_ExpectTrue(parsedArray.GetString(2) == "what"); - TEST_ExpectTrue(parsedArray.GetObject(3).GetKeys().length == 0); + TEST_ExpectTrue(parsedArray.GetObject(3).GetPropertyNames().length == 0); Issue("`JArray.ParseIntoSelfRaw()` cannot add new elements."); TEST_ExpectTrue(parsedArray.ParseIntoSelfRaw( @@ -1384,7 +1361,7 @@ protected static function SubTest_JSONObjectParsingString() Issue("`ParseObjectString()` cannot parse empty JSON object."); parsedObject = _().json.ParseObjectString("{}"); TEST_ExpectNotNone(parsedObject); - TEST_ExpectTrue(parsedObject.GetKeys().length == 0); + TEST_ExpectTrue(parsedObject.GetPropertyNames().length == 0); Issue("`ParseObjectString()` doesn't report error when parsing an incorrect" @ "object."); @@ -1427,7 +1404,7 @@ protected static function SubTest_JSONArrayParsingString() TEST_ExpectTrue(parsedArray.IsNull(0)); TEST_ExpectTrue(parsedArray.GetNumber(1) == 6734.9); TEST_ExpectTrue(parsedArray.GetString(2) == "what"); - TEST_ExpectTrue(parsedArray.GetObject(3).GetKeys().length == 0); + TEST_ExpectTrue(parsedArray.GetObject(3).GetPropertyNames().length == 0); Issue("`JArray.ParseIntoSelfString()` cannot add new elements."); TEST_ExpectTrue(parsedArray.ParseIntoSelfString("[\"huh\", Null]"));