From 427d6fea58b152be69ae939ffbf1d655741819e3 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Tue, 31 Mar 2020 13:23:55 +0700 Subject: [PATCH] Add basic JSON data support Add classes to store data that can be transferred in a text JSON format. --- sources/Data/JSONArray.uc | 351 +++++++++++++++++++++++++++++++++++++ sources/Data/JSONBase.uc | 70 ++++++++ sources/Data/JSONObject.uc | 272 ++++++++++++++++++++++++++++ 3 files changed, 693 insertions(+) create mode 100644 sources/Data/JSONArray.uc create mode 100644 sources/Data/JSONBase.uc create mode 100644 sources/Data/JSONObject.uc diff --git a/sources/Data/JSONArray.uc b/sources/Data/JSONArray.uc new file mode 100644 index 0000000..d0e9e71 --- /dev/null +++ b/sources/Data/JSONArray.uc @@ -0,0 +1,351 @@ +/** + * 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. + * 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 . + */ +class JSONArray extends JSONBase; + +// Data will simply 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. +public final function JSONType GetType(int index) +{ + if (index < 0) return JSON_Undefined; + if (index >= data.length) return JSON_Undefined; + + return data[index].type; +} + +// Returns current length of this array. +public final function int GetLength() +{ + return data.length; +} + +// Changes length of this array. +// In case of the increase - fills new indices with `null` values. +public final function SetLength(int newLength) +{ + local int i; + local int oldLength; + oldLength = data.length; + data.length = newLength; + if (oldLength >= newLength) + { + return; + } + i = oldLength; + while (i < newLength) + { + SetNull(i); + i += 1; + } +} + +// 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`. +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; +} + +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; +} + +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; +} + +public final function bool IsNull(int index) +{ + if (index < 0) return false; + if (index >= data.length) return false; + + return (data[index].type == JSON_Null); +} + +public final function JSONArray GetArray(int index) +{ + if (index < 0) return none; + if (index >= data.length) return none; + if (data[index].type != JSON_Array) return none; + + return JSONArray(data[index].complexValue); +} + +public final function JSONObject GetObject(int index) +{ + if (index < 0) return none; + if (index >= data.length) return none; + if (data[index].type != JSON_Object) return none; + + return JSONObject(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 JSONArray SetNumber +( + int index, + float value, + optional bool preventExpansion +) +{ + local JSONStorageValue newStorageValue; + if (index < 0) return self; + + if (index >= data.length) + { + if (preventExpansion) + { + return self; + } + else + { + SetLength(index + 1); + } + } + newStorageValue.type = JSON_Number; + newStorageValue.numberValue = value; + data[index] = newStorageValue; + return self; +} + +public final function JSONArray SetString +( + int index, + string value, + optional bool preventExpansion +) +{ + local JSONStorageValue newStorageValue; + if (index < 0) return self; + + if (index >= data.length) + { + if (preventExpansion) + { + return self; + } + else + { + SetLength(index + 1); + } + } + newStorageValue.type = JSON_String; + newStorageValue.stringValue = value; + data[index] = newStorageValue; + return self; +} + +public final function JSONArray SetBoolean +( + int index, + bool value, + optional bool preventExpansion +) +{ + local JSONStorageValue newStorageValue; + if (index < 0) return self; + + if (index >= data.length) + { + if (preventExpansion) + { + return self; + } + else + { + SetLength(index + 1); + } + } + newStorageValue.type = JSON_Boolean; + newStorageValue.booleanValue = value; + data[index] = newStorageValue; + return self; +} + +public final function JSONArray SetNull +( + int index, + optional bool preventExpansion +) +{ + local JSONStorageValue newStorageValue; + if (index < 0) return self; + + if (index >= data.length) + { + if (preventExpansion) + { + return self; + } + else + { + SetLength(index + 1); + } + } + newStorageValue.type = JSON_Null; + data[index] = newStorageValue; + 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 JSONArray CreateArray +( + int index, + optional bool preventExpansion +) +{ + local JSONStorageValue newStorageValue; + if (index < 0) return self; + + if (index >= data.length) + { + if (preventExpansion) + { + return self; + } + else + { + SetLength(index + 1); + } + } + newStorageValue.type = JSON_Array; + newStorageValue.complexValue = new class'JSONArray'; + data[index] = newStorageValue; + return self; +} + +public final function JSONArray CreateObject +( + int index, + optional bool preventExpansion +) +{ + local JSONStorageValue newStorageValue; + if (index < 0) return self; + + if (index >= data.length) + { + if (preventExpansion) + { + return self; + } + else + { + SetLength(index + 1); + } + } + newStorageValue.type = JSON_Object; + newStorageValue.complexValue = new class'JSONObject'; + data[index] = newStorageValue; + return self; +} + +// Wrappers for setter functions that don't take index or +// `preventExpansion` parameters and add/create value at the end of the array. +public final function JSONArray AddNumber(float value) +{ + return SetNumber(data.length, value); +} + +public final function JSONArray AddString(string value) +{ + return SetString(data.length, value); +} + +public final function JSONArray AddBoolean(bool value) +{ + return SetBoolean(data.length, value); +} + +public final function JSONArray AddNull() +{ + return SetNull(data.length); +} + +public final function JSONArray AddArray() +{ + return CreateArray(data.length); +} + +public final function JSONArray AddObject() +{ + return CreateObject(data.length); +} + +// Removes up to `amount` 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. +public final function bool RemoveValue(int index, optional int amount) +{ + if (index < 0) return false; + if (index >= data.length) return false; + if (amount < 1) return false; + + amount = Max(amount, 1); + amount = Min(amount, data.length - index); + data.Remove(index, amount); + return true; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Data/JSONBase.uc b/sources/Data/JSONBase.uc new file mode 100644 index 0000000..60e6196 --- /dev/null +++ b/sources/Data/JSONBase.uc @@ -0,0 +1,70 @@ +/** + * JSON is an open standard file format, and data interchange format, + * 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. + * + * JSON data is stored as an object (represented via `JSONObject`) that + * contains a set of name-value pairs, where value can be + * a number, string, boolean value, another object or + * an array (represented by `JSONArray`). + * 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 . + */ +class JSONBase extends Object; + +// Enumeration for possible types of JSON values. +enum JSONType +{ + // Technical type, used to indicate that requested value is missing. + // Undefined values are not part of JSON format. + JSON_Undefined, + // An empty value, in teste representation defined by a single word "null". + JSON_Null, + // A number, recorded as a float. + // JSON itself doesn't specify whether number is an integer or float. + JSON_Number, + // A string. + JSON_String, + // A bool value. + JSON_Boolean, + // Array of other JSON values, stored without names; + // Single array can contain any mix of value types. + JSON_Array, + // Another JSON object, i.e. associative array of name-value pairs + JSON_Object +}; + +// Stores a single JSON value +struct JSONStorageValue +{ + // What type is stored exactly? + // Depending on that, uses one of the other fields as a storage. + var public JSONType type; + var protected float numberValue; + var protected string stringValue; + var protected bool booleanValue; + // Used for storing both JSON objects and arrays. + var protected JSONBase complexValue; +}; + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Data/JSONObject.uc b/sources/Data/JSONObject.uc new file mode 100644 index 0000000..6e1667e --- /dev/null +++ b/sources/Data/JSONObject.uc @@ -0,0 +1,272 @@ +/** + * 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 `JSONArray` 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. + * 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. + * 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 . + */ +class JSONObject extends JSONBase; + +// We will store all our data as a simple array of key(name)-value pairs. +struct JSONKeyValuePair +{ + var string key; + var JSONStorageValue value; +}; +var private array data; + +// Returns index of key-value pair in `data` with a given key. +// Returns `-1` if such a pair does not exist in `data`. +private final function int GetIndex(string key) +{ + local int i; + for (i = 0; i < data.length; i += 1) + { + if (key == data[i].key) + { + return i; + } + } + return -1; +} + +// Returns `JSONType` of a variable with a given key in our data. +// 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`. +public final function JSONType GetType(string key) +{ + local int index; + index = GetIndex(key); + if (index < 0) return JSON_Undefined; + + return data[index].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`. +public final function float GetNumber(string key, optional float defaultValue) +{ + local int index; + index = GetIndex(key); + if (index < 0) return defaultValue; + if (data[index].value.type != JSON_Number) return defaultValue; + + return data[index].value.numberValue; +} + +public final function string GetString(string key, optional string defaultValue) +{ + local int index; + index = GetIndex(key); + if (index < 0) return defaultValue; + if (data[index].value.type != JSON_String) return defaultValue; + + return data[index].value.stringValue; +} + +public final function bool GetBoolean(string key, optional bool defaultValue) +{ + local int index; + index = GetIndex(key); + if (index < 0) return defaultValue; + if (data[index].value.type != JSON_Boolean) return defaultValue; + + return data[index].value.booleanValue; +} + +public final function bool IsNull(string key) +{ + local int index; + index = GetIndex(key); + if (index < 0) return false; + if (data[index].value.type != JSON_Null) return false; + + return (data[index].value.type == JSON_Null); +} + +public final function JSONArray GetArray(string key) +{ + local int index; + index = GetIndex(key); + if (index < 0) return none; + if (data[index].value.type != JSON_Array) return none; + + return JSONArray(data[index].value.complexValue); +} + +public final function JSONObject GetObject(string key) +{ + local int index; + index = GetIndex(key); + if (index < 0) return none; + if (data[index].value.type != JSON_Object) return none; + + return JSONObject(data[index].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);`. +public final function JSONObject SetNumber(string key, float value) +{ + local int index; + local JSONKeyValuePair newKeyValuePair; + local JSONStorageValue newStorageValue; + index = GetIndex(key); + if (index < 0) + { + index = data.length; + } + newStorageValue.type = JSON_Number; + newStorageValue.numberValue = value; + newKeyValuePair.key = key; + newKeyValuePair.value = newStorageValue; + data[index] = newKeyValuePair; + return self; +} + +public final function JSONObject SetString(string key, string value) +{ + local int index; + local JSONKeyValuePair newKeyValuePair; + local JSONStorageValue newStorageValue; + index = GetIndex(key); + if (index < 0) + { + index = data.length; + } + newStorageValue.type = JSON_String; + newStorageValue.stringValue = value; + newKeyValuePair.key = key; + newKeyValuePair.value = newStorageValue; + data[index] = newKeyValuePair; + return self; +} + +public final function JSONObject SetBoolean(string key, bool value) +{ + local int index; + local JSONKeyValuePair newKeyValuePair; + local JSONStorageValue newStorageValue; + index = GetIndex(key); + if (index < 0) + { + index = data.length; + } + newStorageValue.type = JSON_Boolean; + newStorageValue.booleanValue = value; + newKeyValuePair.key = key; + newKeyValuePair.value = newStorageValue; + data[index] = newKeyValuePair; + return self; +} + +public final function JSONObject SetNull(string key) +{ + local int index; + local JSONKeyValuePair newKeyValuePair; + local JSONStorageValue newStorageValue; + index = GetIndex(key); + if (index < 0) + { + index = data.length; + } + newStorageValue.type = JSON_Null; + newKeyValuePair.key = key; + newKeyValuePair.value = newStorageValue; + data[index] = newKeyValuePair; + 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");`. +public final function JSONObject CreateArray(string key) +{ + local int index; + local JSONKeyValuePair newKeyValuePair; + local JSONStorageValue newStorageValue; + index = GetIndex(key); + if (index < 0) + { + index = data.length; + } + newStorageValue.type = JSON_Array; + newStorageValue.complexValue = new class'JSONArray'; + newKeyValuePair.key = key; + newKeyValuePair.value = newStorageValue; + data[index] = newKeyValuePair; + return self; +} + +public final function JSONObject CreateObject(string key) +{ + local int index; + local JSONKeyValuePair newKeyValuePair; + local JSONStorageValue newStorageValue; + index = GetIndex(key); + if (index < 0) + { + index = data.length; + } + newStorageValue.type = JSON_Object; + newStorageValue.complexValue = new class'JSONObject'; + newKeyValuePair.key = key; + newKeyValuePair.value = newStorageValue; + data[index] = newKeyValuePair; + return self; +} + +// Removes values with a given name. +// Returns `true` if value was actually removed and `false` if it didn't exist. +public final function bool RemoveValue(string key) +{ + local int index; + index = GetIndex(key); + if (index < 0) return false; + + data.Remove(index, 1); + return true; +} + +defaultproperties +{ +} \ No newline at end of file