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.
788 lines
28 KiB
788 lines
28 KiB
/** |
|
* 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 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 |
|
* 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 <https://www.gnu.org/licenses/>. |
|
*/ |
|
class JSON extends AcediaActor |
|
abstract |
|
config(AcediaSystem); |
|
|
|
/** |
|
* Enumeration for possible types of JSON values. |
|
*/ |
|
enum JType |
|
{ |
|
// 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 |
|
}; |
|
|
|
/** |
|
* Represents a single JSON value. |
|
*/ |
|
struct JStorageAtom |
|
{ |
|
// What type is stored exactly? |
|
// Depending on that, uses one of the other fields as a storage. |
|
var JType type; |
|
var float numberValue; |
|
var string stringValue; |
|
var bool booleanValue; |
|
// Used for storing both JSON objects and arrays. |
|
var JSON complexValue; |
|
// Numeric value might not fit into a `float` very well, so we will store |
|
// them as both `float` and `integer` and allow user to request any version |
|
// of them |
|
var int numberValueAsInt; |
|
var bool preferIntegerValue; |
|
// Some `string` values might be actually used to represent classes, |
|
// so we will give users an ability to request `string` value as a class. |
|
var class<Object> stringValueAsClass; |
|
// To avoid several unsuccessful attempts to load `class` object from |
|
// a `string`, we will record whether we've already tied that. |
|
var bool classLoadingWasAttempted; |
|
}; |
|
|
|
/** |
|
* Enumeration of possible result of comparing two JSON containers |
|
* (objects or arrays). |
|
* Containers are compared as sets of stored variables. |
|
*/ |
|
enum JComparisonResult |
|
{ |
|
// Containers contain different sets of values and |
|
// neither can be considered a subset of another. |
|
JCR_Incomparable, |
|
// "Left" container is a subset of the "right" one. |
|
JCR_SubSet, |
|
// "Right" container is a subset of the "left" one. |
|
JCR_Overset, |
|
// Both objects are identical. |
|
JCR_Equal |
|
}; |
|
|
|
/** |
|
* Describes how JSON containers are supposed to be displayed. |
|
*/ |
|
struct JSONDisplaySettings |
|
{ |
|
// Should it be displayed as a formatted string, with added color tags? |
|
var bool colored; |
|
// Should we "stack" indentation of folded objects? |
|
var bool stackIndentation; |
|
// Indentation for elements in object/array |
|
var string subObjectIndentation, subArrayIndentation; |
|
// Strings to put immediately before and after object opening: '{' |
|
var string beforeObjectOpening, afterObjectOpening; |
|
// Strings to put immediately before and after object closing: '}' |
|
var string beforeObjectEnding, afterObjectEnding; |
|
// {<beforePropertyName>"name"<afterPropertyName>:value} |
|
var string beforePropertyName, afterPropertyName; |
|
// {"name":<beforePropertyValue>value<afterPropertyValue>} |
|
var string beforePropertyValue, afterPropertyValue; |
|
// String to put immediately after comma inside object, |
|
// can be used to break line after each property record |
|
var string afterObjectComma; |
|
// Strings to put immediately before and after array opening: '[' |
|
var string beforeArrayOpening, afterArrayOpening; |
|
// Strings to put immediately before and after array closing: ']' |
|
var string beforeArrayEnding, afterArrayEnding; |
|
// [<beforeElement>element1<afterElement>,<afterArrayComma>...] |
|
var string beforeElement, afterElement; |
|
// Can be used to break line after each property record |
|
var string afterArrayComma; |
|
}; |
|
|
|
// 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; |
|
if (rightJSON == none) return JCR_Incomparable; |
|
firstIsSubset = IsSubsetOf(rightJSON); |
|
secondIsSubset = rightJSON.IsSubsetOf(self); |
|
if (firstIsSubset) |
|
{ |
|
if (secondIsSubset) { |
|
return JCR_Equal; |
|
} |
|
else { |
|
return JCR_SubSet; |
|
} |
|
} |
|
else { |
|
if (secondIsSubset) { |
|
return JCR_Overset; |
|
} |
|
else { |
|
return JCR_Incomparable; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Checks if two objects are equal. |
|
* |
|
* A shortcut for `Compare(rightJSON) == JCR_Equal`. |
|
* |
|
* @param rightJSON Value to compare caller object to. |
|
* @return `true` if caller and `rightJSON` store exactly same set of values |
|
* (under the same names for `JObject`) and `false` otherwise. |
|
*/ |
|
public final function bool IsEqual(JSON rightJSON) |
|
{ |
|
return (Compare(rightJSON) == JCR_Equal); |
|
} |
|
|
|
/** |
|
* Displays caller JSON container with one of the presets. |
|
* |
|
* Default compact preset displays JSON in as little characters as possible, |
|
* fancy preset tries to make it human-readable with appropriate spacing and |
|
* indentation for sub objects. |
|
* |
|
* See `DisplayWith()` for a more tweakable method. |
|
* |
|
* @param fancyPrinting Leave empty of `false` for a compact display and |
|
* `true` to display it with a fancy preset. |
|
* @param colorSettings Display JSON container as a formatted string, |
|
* adding color tags to JSON syntax. |
|
* @return String representation of caller JSON container, |
|
* in plain format if `colorSettings == false` and |
|
* as a formatted string if `colorSettings == true`. |
|
*/ |
|
public final function string Display( |
|
optional bool fancyPrinting, |
|
optional bool colorSettings) |
|
{ |
|
local JSONDisplaySettings settingsToUse; |
|
// Settings are minimal by default |
|
if (fancyPrinting) { |
|
settingsToUse = GetFancySettings(); |
|
} |
|
if (colorSettings) { |
|
settingsToUse.colored = true; |
|
} |
|
return DisplayWith(settingsToUse); |
|
} |
|
|
|
/** |
|
* Displays caller JSON container with a provided preset. |
|
* |
|
* See `Display()` for a simpler to use method. |
|
* |
|
* @param displaySettings Struct that describes precisely how to display |
|
* caller JSON container. Can be used to emulate `Display()` call. |
|
* @return String representation of caller JSON container in format defined by |
|
* `displaySettings`. |
|
*/ |
|
public function string DisplayWith(JSONDisplaySettings displaySettings) |
|
{ |
|
return ""; |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a new set of properties inside |
|
* the caller JSON container. |
|
* |
|
* Only adds new properties if parsing the whole object was successful, |
|
* otherwise even successfully parsed properties will be discarded. |
|
* |
|
* `parser` must point at the text describing a JSON object or an array |
|
* (depending on whether a caller object is `JObject` or `JArray`) in |
|
* a valid notation. Then it parses that container inside memory, but |
|
* instead of creating it as a separate entity, adds it's values to |
|
* the caller container. |
|
* Everything that comes after parsed JSON container is discarded. |
|
* |
|
* This method does not try to validate passed JSON and can accept invalid |
|
* JSON by making some assumptions, but it is an undefined behavior and |
|
* one should not expect it. |
|
* Method is only guaranteed to work on valid JSON. |
|
* |
|
* @param parser Parser that method would use to parse JSON container from |
|
* wherever it left. It's confirmed will not be changed, but if parsing |
|
* was successful, - it will point at the next available character. |
|
* Do not treat `parser` being in a non-failed state as a confirmation of |
|
* successful parsing: JSON parsing might fail regardless. |
|
* Check return value for that. |
|
* @return `true` if parsing was successful and `false` otherwise. |
|
*/ |
|
public function bool ParseIntoSelfWith(Parser parser) |
|
{ |
|
return false; |
|
} |
|
|
|
/** |
|
* Parse a new set of properties inside the caller JSON container from |
|
* a given `Text`. |
|
* |
|
* Only adds new properties if parsing the whole object was successful, |
|
* otherwise even successfully parsed properties will be discarded. |
|
* |
|
* JSON container is parsed from a given `Text`, but instead of creating |
|
* new object as a separate entity, method adds it's values to |
|
* the caller container. |
|
* Everything that comes after parsed JSON container is discarded. |
|
* |
|
* This method does not try to validate passed JSON and can accept invalid |
|
* JSON by making some assumptions, but it is an undefined behavior and |
|
* one should not expect it. |
|
* Method is only guaranteed to work on valid JSON. |
|
* |
|
* @param source `Text` to get JSON container definition from. |
|
* @return `true` if parsing was successful and `false` otherwise. |
|
*/ |
|
public final function bool ParseIntoSelf(Text source) |
|
{ |
|
local bool successfullyParsed; |
|
local Parser jsonParser; |
|
jsonParser = _.text.Parse(source); |
|
successfullyParsed = ParseIntoSelfWith(jsonParser); |
|
_.memory.Free(jsonParser); |
|
return successfullyParsed; |
|
} |
|
|
|
/** |
|
* Parse a new set of properties inside the caller JSON container from |
|
* a given `string`. |
|
* |
|
* Only adds new properties if parsing the whole object was successful, |
|
* otherwise even successfully parsed properties will be discarded. |
|
* |
|
* JSON container is parsed from a given `string`, but instead of creating |
|
* new object as a separate entity, method adds it's values to |
|
* the caller container. |
|
* Everything that comes after parsed JSON container is discarded. |
|
* |
|
* This method does not try to validate passed JSON and can accept invalid |
|
* JSON by making some assumptions, but it is an undefined behavior and |
|
* one should not expect it. |
|
* Method is only guaranteed to work on valid JSON. |
|
* |
|
* @param source `string` to get JSON container definition from. |
|
* @return `true` if parsing was successful and `false` otherwise. |
|
*/ |
|
public final function bool ParseIntoSelfString( |
|
string source, |
|
optional Text.StringType stringType) |
|
{ |
|
local bool successfullyParsed; |
|
local Parser jsonParser; |
|
jsonParser = _.text.ParseString(source, stringType); |
|
successfullyParsed = ParseIntoSelfWith(jsonParser); |
|
_.memory.Free(jsonParser); |
|
return successfullyParsed; |
|
} |
|
|
|
/** |
|
* Parse a new set of properties inside the caller JSON container from |
|
* a given raw data. |
|
* |
|
* Only adds new properties if parsing the whole object was successful, |
|
* otherwise even successfully parsed properties will be discarded. |
|
* |
|
* JSON container is parsed from a given raw data, but instead of creating |
|
* new object as a separate entity, method adds it's values to |
|
* the caller container. |
|
* Everything that comes after parsed JSON container is discarded. |
|
* |
|
* This method does not try to validate passed JSON and can accept invalid |
|
* JSON by making some assumptions, but it is an undefined behavior and |
|
* one should not expect it. |
|
* Method is only guaranteed to work on valid JSON. |
|
* |
|
* @param source Raw data *array of `Text.Character`) to get JSON container |
|
* definition from. |
|
* @return `true` if parsing was successful and `false` otherwise. |
|
*/ |
|
public final function bool ParseIntoSelfRaw(array<Text.Character> rawSource) |
|
{ |
|
local bool successfullyParsed; |
|
local Parser jsonParser; |
|
jsonParser = _.text.ParseRaw(rawSource); |
|
successfullyParsed = ParseIntoSelfWith(jsonParser); |
|
_.memory.Free(jsonParser); |
|
return successfullyParsed; |
|
} |
|
|
|
/** |
|
* Checks if two `JStorageAtom` values represent the same values. |
|
* |
|
* Atoms storing the same value does not necessarily mean that they are equal |
|
* as structs because they contain several different container members and |
|
* unused ones can differ. |
|
* |
|
* @return `true` if atoms stores the same value, `false` otherwise. |
|
*/ |
|
protected final function bool AreAtomsEqual( |
|
JStorageAtom atom1, |
|
JStorageAtom atom2) |
|
{ |
|
if (atom1.type != atom2.type) return false; |
|
if (atom1.type == JSON_Undefined) return true; |
|
if (atom1.type == JSON_Null) return true; |
|
if (atom1.type == JSON_Number) { |
|
return ( atom1.numberValue == atom2.numberValue |
|
&& atom1.numberValueAsInt == atom2.numberValueAsInt); |
|
} |
|
if (atom1.type == JSON_Boolean) { |
|
return (atom1.booleanValue == atom2.booleanValue); |
|
} |
|
if (atom1.type == JSON_String) { |
|
return (atom1.stringValue == atom2.stringValue); |
|
} |
|
if (atom1.complexValue == none && atom2.complexValue == none) { |
|
return true; |
|
} |
|
if (atom1.complexValue == none || atom2.complexValue == none) { |
|
return false; |
|
} |
|
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; |
|
atom.classLoadingWasAttempted = true; |
|
atom.stringValueAsClass = |
|
class<Object>(DynamicLoadObject(atom.stringValue, class'Class', true)); |
|
} |
|
|
|
/** |
|
* 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) |
|
{ |
|
local string colorTag; |
|
local string result; |
|
if ( atom.complexValue != none |
|
&& (atom.type == JSON_Object || atom.type == JSON_Array) ) { |
|
return atom.complexValue.DisplayWith(displaySettings); |
|
} |
|
if (atom.type == JSON_Null) { |
|
result = "null"; |
|
colorTag = "$json_null"; |
|
} |
|
else if (atom.type == JSON_Number) { |
|
if (atom.preferIntegerValue) { |
|
result = string(atom.numberValueAsInt); |
|
} |
|
else { |
|
result = DisplayFloat(atom.numberValue); |
|
} |
|
colorTag = "$json_number"; |
|
} |
|
else if (atom.type == JSON_String) { |
|
result = DisplayJSONString(atom.stringValue); |
|
colorTag = "$json_string"; |
|
} |
|
else if (atom.type == JSON_Boolean) { |
|
if (atom.booleanValue) { |
|
result = "true"; |
|
} |
|
else { |
|
result = "false"; |
|
} |
|
colorTag = "$json_boolean"; |
|
} |
|
if (displaySettings.colored) { |
|
return "{" $ colorTag @ result $ "}"; |
|
} |
|
return result; |
|
} |
|
|
|
// 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; |
|
local int howManyZeroes; |
|
local string zeroes; |
|
local string result; |
|
precision = Clamp(MAX_FLOAT_PRECISION, 0, 10); |
|
if (number < 0) { |
|
number *= -1; |
|
result = "-"; |
|
} |
|
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 (howManyZeroes > 0) { |
|
zeroes $= "0"; |
|
howManyZeroes -= 1; |
|
} |
|
// Cut off trailing zeroes and |
|
while (fractionalPart > 0 && fractionalPart % 10 == 0) { |
|
fractionalPart /= 10; |
|
} |
|
return result $ zeroes $ string(fractionalPart); |
|
} |
|
|
|
// 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) |
|
{ |
|
number -= (number % 10); |
|
number /= 10; |
|
digitCounter += 1; |
|
} |
|
return digitCounter; |
|
} |
|
|
|
/** |
|
* 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) |
|
// into escaped sequences |
|
ReplaceText(input, "\"", "\\\""); |
|
ReplaceText(input, "/", "\\/"); |
|
ReplaceText(input, "\\", "\\\\"); |
|
ReplaceText(input, Chr(0x08), "\\b"); |
|
ReplaceText(input, Chr(0x0c), "\\f"); |
|
ReplaceText(input, Chr(0x0a), "\\n"); |
|
ReplaceText(input, Chr(0x0d), "\\r"); |
|
ReplaceText(input, Chr(0x09), "\\t"); |
|
// TODO: test if there are control characters and render them as "\u...." |
|
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) |
|
{ |
|
local string lineFeed; |
|
local string lineFeedIndent; |
|
local JSONDisplaySettings indentedSettings; |
|
indentedSettings = inputSettings; |
|
lineFeed = Chr(0x0a); |
|
if (indentingArray) { |
|
lineFeedIndent = lineFeed $ inputSettings.subArrayIndentation; |
|
} |
|
else { |
|
lineFeedIndent = lineFeed $ inputSettings.subObjectIndentation; |
|
} |
|
if (lineFeedIndent == lineFeed) { |
|
return indentedSettings; |
|
} |
|
ReplaceText(indentedSettings.afterObjectEnding, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.afterPropertyName, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.afterObjectComma, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.afterArrayOpening, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.beforeArrayEnding, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.afterArrayEnding, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.beforeElement, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.afterElement, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.afterArrayComma, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.beforeObjectOpening, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.beforePropertyValue, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.afterObjectOpening, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.beforeObjectEnding, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.beforeArrayOpening, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.afterPropertyValue, lineFeed, lineFeedIndent); |
|
ReplaceText(indentedSettings.beforePropertyName, lineFeed, lineFeedIndent); |
|
return indentedSettings; |
|
} |
|
|
|
/** |
|
* 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; |
|
local JStorageAtom newAtom; |
|
if (parser == none) return newAtom; |
|
if (!parser.Ok()) return newAtom; |
|
initState = parser.GetCurrentState(); |
|
if (parser.MStringLiteral(newAtom.stringValue).Ok()) |
|
{ |
|
newAtom.type = JSON_String; |
|
return newAtom; |
|
} |
|
newAtom = ParseLiteral(parser.RestoreState(initState)); |
|
if (newAtom.type != JSON_Undefined) { |
|
return newAtom; |
|
} |
|
newAtom = ParseComplex(parser.RestoreState(initState)); |
|
if (newAtom.type != JSON_Undefined) { |
|
return newAtom; |
|
} |
|
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 Parser.ParserState initState; |
|
initState = parser.GetCurrentState(); |
|
if (parser.Match("null", true).Ok()) |
|
{ |
|
newAtom.type = JSON_Null; |
|
return newAtom; |
|
} |
|
if (parser.RestoreState(initState).Match("false", true).Ok()) |
|
{ |
|
newAtom.type = JSON_Boolean; |
|
return newAtom; |
|
} |
|
if (parser.RestoreState(initState).Match("true", true).Ok()) |
|
{ |
|
newAtom.type = JSON_Boolean; |
|
newAtom.booleanValue = true; |
|
return newAtom; |
|
} |
|
} |
|
|
|
/** |
|
* 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 Parser.ParserState initState; |
|
initState = parser.GetCurrentState(); |
|
if (parser.Match("{").Ok()) |
|
{ |
|
newAtom.complexValue = _.json.NewObject(); |
|
newAtom.type = JSON_Object; |
|
} |
|
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)) { |
|
return newAtom; |
|
} |
|
newAtom.type = JSON_Undefined; |
|
newAtom.complexValue = none; |
|
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 initState, integerParsedState; |
|
initState = parser.GetCurrentState(); |
|
if (!parser.MInteger(newAtom.numberValueAsInt).Ok()) { |
|
return newAtom; |
|
} |
|
newAtom.type = JSON_Number; |
|
integerParsedState = parser.GetCurrentState(); |
|
// For a number check if it is recorded as a float specifically. |
|
// If not - prefer integer for storage. |
|
if ( parser.Match(".").Ok() |
|
|| parser.RestoreState(integerParsedState).Match("e", true).Ok()) |
|
{ |
|
parser.RestoreState(initState).MNumber(newAtom.numberValue); |
|
return newAtom; |
|
} |
|
parser.RestoreState(integerParsedState); |
|
newAtom.numberValue = newAtom.numberValueAsInt; |
|
newAtom.preferIntegerValue = true; |
|
return newAtom; |
|
} |
|
|
|
event Destroyed() |
|
{ |
|
super.Destroyed(); |
|
Clear(); |
|
} |
|
|
|
defaultproperties |
|
{ |
|
MAX_FLOAT_PRECISION = 4 |
|
} |