You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
948 lines
32 KiB
948 lines
32 KiB
/** |
|
* This class implements JSON array storage capabilities. |
|
* 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. 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. |
|
* |
|
* Acedia is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation, version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* Acedia is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
|
*/ |
|
class JArray extends JSON; |
|
|
|
// Data will be stored as an array of JSON values |
|
var private array<JStorageAtom> data; |
|
|
|
/** |
|
* 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; |
|
if (index >= data.length) return JSON_Undefined; |
|
|
|
return data[index].type; |
|
} |
|
|
|
/** |
|
* 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 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) { |
|
return; |
|
} |
|
i = oldLength; |
|
while (i < newLength) |
|
{ |
|
SetNull(i); |
|
i += 1; |
|
} |
|
} |
|
|
|
/** |
|
* 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; |
|
if (index >= data.length) return defaultValue; |
|
if (data[index].type != JSON_Number) return 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; |
|
if (index >= data.length) return defaultValue; |
|
if (data[index].type != JSON_Number) return 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; |
|
if (index >= data.length) return defaultValue; |
|
if (data[index].type != JSON_String) return defaultValue; |
|
|
|
return data[index].stringValue; |
|
} |
|
|
|
/** |
|
* Gets the value at the index `index` as a `class`, assuming it has |
|
* `JSON_String` type. |
|
* |
|
* JSON does not support to store class data type, but we can use string type |
|
* for that. This method attempts to load a class object from it's full name, |
|
* (like `Engine.Actor`) recorded inside an appropriate string value. |
|
* |
|
* @param index Index of the value to get; |
|
* must be between 0 and `GetLength() - 1` inclusively. |
|
* @param defaultValue Value to return if element does not exist, |
|
* has a different type (can be checked by `GetTypeOf()`) or not |
|
* a valid class name. |
|
* @return Class value of the element at the index `index`, |
|
* if it exists, has `JSON_String` type and it represents |
|
* a full name of some class. |
|
* Otherwise returns passed `defaultValue`. |
|
*/ |
|
public final function class<Object> GetClass( |
|
int index, |
|
optional class<Object> defaultValue) |
|
{ |
|
if (index < 0) return defaultValue; |
|
if (index >= data.length) return defaultValue; |
|
if (data[index].type != JSON_String) return defaultValue; |
|
|
|
TryLoadingStringAsClass(data[index]); |
|
if (data[index].stringValueAsClass != none) { |
|
return data[index].stringValueAsClass; |
|
} |
|
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; |
|
if (index >= data.length) return defaultValue; |
|
if (data[index].type != JSON_Boolean) return 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; |
|
if (index >= data.length) return false; |
|
|
|
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; |
|
if (index >= data.length) return none; |
|
if (data[index].type != JSON_Array) return none; |
|
|
|
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; |
|
if (index >= data.length) return none; |
|
if (data[index].type != JSON_Object) return none; |
|
|
|
return JObject(data[index].complexValue); |
|
} |
|
|
|
/** |
|
* 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) { |
|
SetLength(index + 1); |
|
} |
|
newStorageValue.type = JSON_Number; |
|
newStorageValue.numberValue = value; |
|
newStorageValue.numberValueAsInt = int(value); |
|
newStorageValue.preferIntegerValue = false; |
|
data[index] = newStorageValue; |
|
return self; |
|
} |
|
|
|
/** |
|
* 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) { |
|
SetLength(index + 1); |
|
} |
|
newStorageValue.type = JSON_Number; |
|
newStorageValue.numberValue = float(value); |
|
newStorageValue.numberValueAsInt = value; |
|
newStorageValue.preferIntegerValue = true; |
|
data[index] = newStorageValue; |
|
return self; |
|
} |
|
|
|
/** |
|
* 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) { |
|
SetLength(index + 1); |
|
} |
|
newStorageValue.type = JSON_String; |
|
newStorageValue.stringValue = value; |
|
data[index] = newStorageValue; |
|
return self; |
|
} |
|
|
|
/** |
|
* Sets the string value, corresponding to a given class `value`, |
|
* at the index `index`, erasing previous value (if it was recorded). |
|
* |
|
* Value in question will have `JSON_String` type. |
|
* |
|
* If negative index is given - does nothing. |
|
* If given index is too large (`>= GetLength()`) then array will be |
|
* extended, setting values at new indices (except specified `index`) |
|
* to "null" value (`JSON_Null`). |
|
* |
|
* We want to allow storing `class` data in our JSON containers, but JSON |
|
* standard does not support such a type, so we have to use string type |
|
* to store `class`' name instead. |
|
* Also see `GetClass()` method`. |
|
* |
|
* @param index Index at which to set given value. |
|
* @param value Value to set at given index. |
|
* @return Reference to the caller object, to allow for function chaining. |
|
*/ |
|
public final function JArray SetClass(int index, class<Object> value) |
|
{ |
|
local JStorageAtom newStorageValue; |
|
if (index < 0) return self; |
|
|
|
if (index >= data.length) { |
|
SetLength(index + 1); |
|
} |
|
newStorageValue.type = JSON_String; |
|
newStorageValue.stringValue = string(value); |
|
newStorageValue.stringValueAsClass = value; |
|
data[index] = newStorageValue; |
|
return self; |
|
} |
|
|
|
/** |
|
* 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) { |
|
SetLength(index + 1); |
|
} |
|
newStorageValue.type = JSON_Boolean; |
|
newStorageValue.booleanValue = value; |
|
data[index] = newStorageValue; |
|
return self; |
|
} |
|
|
|
/** |
|
* 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) { |
|
SetLength(index + 1); |
|
} |
|
newStorageValue.type = JSON_Null; |
|
data[index] = newStorageValue; |
|
return self; |
|
} |
|
|
|
/** |
|
* 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 >= data.length) { |
|
SetLength(index + 1); |
|
} |
|
newStorageValue.type = JSON_Array; |
|
newStorageValue.complexValue = template.Clone(); |
|
data[index] = newStorageValue; |
|
return self; |
|
} |
|
|
|
/** |
|
* 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 >= data.length) { |
|
SetLength(index + 1); |
|
} |
|
newStorageValue.type = JSON_Object; |
|
newStorageValue.complexValue = template.Clone(); |
|
data[index] = newStorageValue; |
|
return self; |
|
} |
|
|
|
/** |
|
* 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) { |
|
SetLength(index + 1); |
|
} |
|
newStorageValue.type = JSON_Array; |
|
newStorageValue.complexValue = _.json.newArray(); |
|
data[index] = newStorageValue; |
|
return self; |
|
} |
|
|
|
/** |
|
* 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) { |
|
SetLength(index + 1); |
|
} |
|
newStorageValue.type = JSON_Object; |
|
newStorageValue.complexValue = _.json.newObject(); |
|
data[index] = newStorageValue; |
|
return self; |
|
} |
|
|
|
/** |
|
* Appends numeric value (as `float`) at the end of the caller `JArray`. |
|
* |
|
* Forms a pair with `AddInteger()` method. |
|
* While JSON standard allows to store numbers with arbitrary precision, |
|
* UnrealScript's types have a limited range. |
|
* To alleviate this problem we store numbers in both `float`- and |
|
* `int`-type variables to extended supported range of values. |
|
* So if you need to store a number with fractional part, you should |
|
* prefer `AddNumber()` and for integer values `AddInteger()` is preferable. |
|
* Both will record a value of type `JSON_Number`. |
|
* |
|
* @param value Numeric value to append. |
|
* @return Reference to the caller object, to allow for function chaining. |
|
*/ |
|
public final function JArray AddNumber(float value) |
|
{ |
|
return SetNumber(data.length, value); |
|
} |
|
|
|
/** |
|
* Appends numeric value (as `int`) at the end of the caller `JArray`. |
|
* |
|
* Forms a pair with `AddNumber()` method. |
|
* While JSON standard allows to store numbers with arbitrary precision, |
|
* UnrealScript's types have a limited range. |
|
* To alleviate this problem we store numbers in both `float`- and |
|
* `int`-type variables to extended supported range of values. |
|
* So if you need to store a number with fractional part, you should |
|
* prefer `AddNumber()` and for integer values `AddInteger()` is preferable. |
|
* Both will record a value of type `JSON_Number`. |
|
* |
|
* @param value Numeric value to append. |
|
* @return Reference to the caller object, to allow for function chaining. |
|
*/ |
|
public final function JArray AddInteger(int value) |
|
{ |
|
return SetInteger(data.length, value); |
|
} |
|
|
|
/** |
|
* Appends string value at the end of the caller `JArray`. |
|
* |
|
* Also see `AddClass()` method. |
|
* |
|
* @param value String value to append. |
|
* @return Reference to the caller object, to allow for function chaining. |
|
*/ |
|
public final function JArray AddString(string value) |
|
{ |
|
return SetString(data.length, value); |
|
} |
|
|
|
/** |
|
* Appends string value, corresponding to a given class `value`, at the end of |
|
* the caller `JArray`. |
|
* |
|
* Value in question will have `JSON_String` type. |
|
* |
|
* We want to allow storing `class` data in our JSON containers, but JSON |
|
* standard does not support such a type, so we have to use string type |
|
* to store `class`' name instead. |
|
* Also see `GetClass()` method`. |
|
* |
|
* @param value Class value to append. |
|
* @return Reference to the caller object, to allow for function chaining. |
|
*/ |
|
public final function JArray AddClass(class<Object> value) |
|
{ |
|
return SetClass(data.length, value); |
|
} |
|
|
|
/** |
|
* Appends boolean value at the end of the caller `JArray`. |
|
* |
|
* @param value Boolean value to append. |
|
* @return Reference to the caller object, to allow for function chaining. |
|
*/ |
|
public final function JArray AddBoolean(bool value) |
|
{ |
|
return SetBoolean(data.length, value); |
|
} |
|
|
|
/** |
|
* Appends "null" value at the end of the caller `JArray`. |
|
* |
|
* @return Reference to the caller object, to allow for function chaining. |
|
*/ |
|
public final function JArray AddNull() |
|
{ |
|
return SetNull(data.length); |
|
} |
|
|
|
/** |
|
* Appends new empty `JArray` (JSON array type) at the end of |
|
* the caller `JArray`. |
|
* |
|
* @return Reference to the caller object, to allow for function chaining. |
|
*/ |
|
public final function JArray AddArray() |
|
{ |
|
return CreateArray(data.length); |
|
} |
|
|
|
/** |
|
* Appends new empty `JObject` (JSON object type) at the end of |
|
* the caller `JArray`. |
|
* |
|
* @return Reference to the caller object, to allow for function chaining. |
|
*/ |
|
public final function JArray AddObject() |
|
{ |
|
return CreateObject(data.length); |
|
} |
|
|
|
// Removes up to `amount` (minimum of `1`) of values, starting from |
|
// 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; |
|
if (index < 0) return false; |
|
if (index >= data.length) return false; |
|
|
|
amount = Max(amount, 1); |
|
amount = Min(amount, data.length - index); |
|
for (i = index; i < index + amount; i += 1) |
|
{ |
|
if (data[index].complexValue != none) { |
|
data[index].complexValue.Destroy(); |
|
} |
|
} |
|
data.Remove(index, 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; |
|
local JArray rightArray; |
|
local array<JStorageAtom> rightAtomArray; |
|
rightArray = JArray(rightValue); |
|
if (rightArray == none) return false; |
|
if (data.length > rightArray.data.length) return false; |
|
rightAtomArray = rightArray.data; |
|
for (i = 0; i < data.length; i += 1) |
|
{ |
|
if (!AreAtomsEqual(data[i], rightAtomArray[i])) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
/** |
|
* 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; |
|
local JArray clonedArray; |
|
local array<JStorageAtom> clonedData; |
|
clonedArray = _.json.NewArray(); |
|
if (clonedArray == none) |
|
{ |
|
_.logger.Failure("Cannot clone `JArray`: cannot spawn a new instance."); |
|
return none; |
|
} |
|
clonedData = data; |
|
for (i = 0; i < clonedData.length; i += 1) |
|
{ |
|
if (clonedData[i].complexValue == none) continue; |
|
if ( clonedData[i].type != JSON_Array |
|
&& clonedData[i].type != JSON_Object) { |
|
continue; |
|
} |
|
clonedData[i].complexValue = clonedData[i].complexValue.Clone(); |
|
} |
|
clonedArray.data = clonedData; |
|
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; |
|
local Parser.ParserState initState, confirmedState; |
|
local JStorageAtom nextAtom; |
|
local array<JStorageAtom> parsedAtoms; |
|
if (parser == none) return false; |
|
initState = parser.GetCurrentState(); |
|
confirmedState = parser.Skip().Match("[").GetCurrentState(); |
|
if (!parser.Ok()) |
|
{ |
|
parser.RestoreState(initState); |
|
return false; |
|
} |
|
while (parser.Ok() && !parser.HasFinished()) |
|
{ |
|
confirmedState = parser.Skip().GetCurrentState(); |
|
if (parser.Match("]").Ok()) { |
|
parsingSucceeded = true; |
|
break; |
|
} |
|
if ( parsedAtoms.length > 0 |
|
&& !parser.RestoreState(confirmedState).Match(",").Skip().Ok()) { |
|
break; |
|
} |
|
else if (parser.Ok()) { |
|
confirmedState = parser.GetCurrentState(); |
|
} |
|
nextAtom = ParseAtom(parser.RestoreState(confirmedState)); |
|
if (nextAtom.type == JSON_Undefined) { |
|
break; |
|
} |
|
parsedAtoms[parsedAtoms.length] = nextAtom; |
|
} |
|
HandleParsedAtoms(parsedAtoms, parsingSucceeded); |
|
if (!parsingSucceeded) { |
|
parser.RestoreState(initState); |
|
} |
|
return parsingSucceeded; |
|
} |
|
|
|
// Either cleans up or adds a list of parsed values, |
|
// depending on whether parsing was successful or not. |
|
private function HandleParsedAtoms( |
|
array<JStorageAtom> parsedAtoms, |
|
bool parsingSucceeded) |
|
{ |
|
local int i; |
|
if (parsingSucceeded) |
|
{ |
|
for (i = 0; i < parsedAtoms.length; i += 1) { |
|
data[data.length] = parsedAtoms[i]; |
|
} |
|
return; |
|
} |
|
for (i = 0; i < parsedAtoms.length; i += 1) |
|
{ |
|
if (parsedAtoms[i].complexValue != none) { |
|
parsedAtoms[i].complexValue.Destroy(); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* 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; |
|
local bool isntFirstElement; |
|
local string contents; |
|
local string openingBraces, closingBraces; |
|
local string elementsSeparator; |
|
local JSONDisplaySettings innerSettings; |
|
if (displaySettings.stackIndentation) { |
|
innerSettings = IndentSettings(displaySettings, true); |
|
} |
|
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 = displaySettings.beforeArrayOpening |
|
$ "[" $ innerSettings.afterArrayOpening; |
|
closingBraces = displaySettings.beforeArrayEnding |
|
$ "]" $ displaySettings.afterArrayEnding; |
|
elementsSeparator = "," $ innerSettings.afterArrayComma; |
|
if (innerSettings.colored) { |
|
elementsSeparator = "{$json_comma" $ elementsSeparator $ "}"; |
|
openingBraces = "{$json_arrayBraces" $ openingBraces $ "}"; |
|
closingBraces = "{$json_arrayBraces" $ closingBraces $ "}"; |
|
} |
|
// Display inner properties |
|
for (i = 0; i < data.length; i += 1) |
|
{ |
|
if (isntFirstElement) { |
|
contents $= elementsSeparator; |
|
} |
|
contents $= DisplayAtom(data[i], innerSettings); |
|
isntFirstElement = true; |
|
} |
|
return openingBraces $ contents $ closingBraces; |
|
} |
|
|
|
defaultproperties |
|
{ |
|
} |