diff options
Diffstat (limited to 'sourcemod/scripting/include/json.inc')
| -rw-r--r-- | sourcemod/scripting/include/json.inc | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/sourcemod/scripting/include/json.inc b/sourcemod/scripting/include/json.inc new file mode 100644 index 0000000..ebc46e4 --- /dev/null +++ b/sourcemod/scripting/include/json.inc @@ -0,0 +1,473 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * Provides a pure SourcePawn implementation of JSON encoding and decoding. + * https://github.com/clugg/sm-json + * + * sm-json (C)2019 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + */ + +#if defined _json_included + #endinput +#endif +#define _json_included + +#include <string> +#include <json/definitions> +#include <json/helpers/decode> +#include <json/helpers/encode> +#include <json/helpers/string> +#include <json/object> + +/** + * Encodes a JSON instance into its string representation. + * + * @param obj Object to encode. + * @param output String buffer to store output. + * @param max_size Maximum size of string buffer. + * @param pretty_print Should the output be pretty printed (newlines and spaces)? [default: false] + * @param depth The current depth of the encoder. [default: 0] + */ +stock void json_encode( + JSON_Object obj, + char[] output, + int max_size, + bool pretty_print = false, + int depth = 0 +) +{ + bool is_array = obj.IsArray; + bool is_empty = true; + int builder_size; + + // used in key iterator + int str_length = 1; + int int_value; + int cell_length = 0; + + StringMapSnapshot snap = null; + int json_size = 0; + if (is_array) { + json_size = obj.CurrentIndex; + + strcopy(output, max_size, "["); + } else { + snap = obj.Snapshot(); + json_size = snap.Length; + + strcopy(output, max_size, "{"); + } + + int key_length = 0; + for (int i = 0; i < json_size; ++i) { + key_length = (is_array) ? JSON_INDEX_BUFFER_SIZE : snap.KeyBufferSize(i); + char[] key = new char[key_length]; + + if (is_array) { + obj.GetIndexString(key, key_length, i); + } else { + snap.GetKey(i, key, key_length); + } + + // skip meta-keys + if (json_is_meta_key(key)) { + continue; + } + + // skip keys that are marked as hidden + if (obj.GetKeyHidden(key)) { + continue; + } + + JSON_CELL_TYPE type = obj.GetKeyType(key); + // skip keys of unknown type + if (type == Type_Invalid) { + continue; + } + + // if we are dealing with a string, prepare the str_value variable for fetching + if (type == Type_String) { + str_length = obj.GetKeyLength(key); + } + char[] str_value = new char[str_length + 1]; + + // determine the length of the char[] needed to represent our cell data + cell_length = 0; + switch (type) { + case Type_String: { + // get the string value early, as its cell_length is determined by its contents + obj.GetString(key, str_value, str_length + 1); + cell_length = json_cell_string_size(str_length); + } + case Type_Int: { + // get the int value early, as its cell_length is determined by its contents + int_value = obj.GetInt(key); + cell_length = json_cell_int_size(int_value); + } + case Type_Float: { + cell_length = json_cell_float_size(); + } + case Type_Bool: { + cell_length = json_cell_bool_size(); + } + case Type_Null: { + cell_length = json_cell_null_size(); + } + case Type_Object: { + cell_length = max_size; + } + } + + // fit the contents into the cell + char[] cell = new char[cell_length]; + switch (type) { + case Type_String: { + json_cell_string(str_value, cell, cell_length); + } + case Type_Int: { + json_cell_int(int_value, cell, cell_length); + } + case Type_Float: { + float value = obj.GetFloat(key); + json_cell_float(value, cell, cell_length); + } + case Type_Bool: { + bool value = obj.GetBool(key); + json_cell_bool(value, cell, cell_length); + } + case Type_Null: { + json_cell_null(cell, cell_length); + } + case Type_Object: { + JSON_Object child = obj.GetObject(key); + json_encode(child, cell, cell_length, pretty_print, depth + 1); + } + } + + // make the builder fit our key:value + // use previously determined cell length and + 1 for , + builder_size = cell_length + 1; + if (! is_array) { + // get the length of the key and + 1 for : + builder_size += json_cell_string_size(strlen(key)) + 1; + + if (pretty_print) { + builder_size += strlen(JSON_PP_AFTER_COLON); + } + } + + char[] builder = new char[builder_size]; + strcopy(builder, builder_size, ""); + + // add the key if we're working with an object + if (! is_array) { + json_cell_string(key, builder, builder_size); + StrCat(builder, builder_size, ":"); + + if (pretty_print) { + StrCat(builder, builder_size, JSON_PP_AFTER_COLON); + } + } + + // add the value and a trailing comma + StrCat(builder, builder_size, cell); + StrCat(builder, builder_size, ","); + + // prepare pretty printing then send builder to output afterwards + if (pretty_print) { + StrCat(output, max_size, JSON_PP_NEWLINE); + + for (int j = 0; j < depth + 1; ++j) { + StrCat(output, max_size, JSON_PP_INDENT); + } + } + + StrCat(output, max_size, builder); + + is_empty = false; + } + + if (snap != null) { + delete snap; + } + + if (! is_empty) { + // remove the final comma + output[strlen(output) - 1] = '\0'; + + if (pretty_print) { + StrCat(output, max_size, JSON_PP_NEWLINE); + + for (int j = 0; j < depth; ++j) { + StrCat(output, max_size, JSON_PP_INDENT); + } + } + } + + // append closing bracket + StrCat(output, max_size, (is_array) ? "]" : "}"); +} + +/** + * Decodes a JSON string into a JSON instance. + * + * @param buffer Buffer to decode. + * @param result Object to store output in. Setting this allows loading over + * an existing JSON instance, 'refreshing' it as opposed to + * creating a new one. [default: null] + * @param pos Current position of the decoder as a bytes offset into the buffer. + * @param depth Current depth of the decoder as child elements in the object. + * @returns JSON instance or null if decoding failed (buffer didn't contain valid JSON). + */ +stock JSON_Object json_decode( + const char[] buffer, + JSON_Object result = null, + int &pos = 0, + int depth = 0 +) +{ + int length = strlen(buffer); + bool is_array = false; + + // skip preceding whitespace + if (! json_skip_whitespace(buffer, length, pos)) { + LogError("json_decode: buffer ended early at position %d", pos); + + return null; + } + + if (json_is_object(buffer[pos])) { + is_array = false; + } else if (json_is_array(buffer[pos])) { + is_array = true; + } else { + LogError("json_decode: character not identified as object or array at position %d", pos); + + return null; + } + + if (result == null) { + result = new JSON_Object(is_array); + } + + bool empty_checked = false; + char[] key = new char[length]; + char[] cell = new char[length]; + + // while we haven't reached the end of our structure + while (! is_array && ! json_is_object_end(buffer[pos]) + || is_array && ! json_is_array_end(buffer[pos])) { + // pos is either an opening structure or comma, so increment past it + ++pos; + + // skip any whitespace preceding the element + if (! json_skip_whitespace(buffer, length, pos)) { + LogError("json_decode: buffer ended early at position %d", pos); + + return null; + } + + // if we are at the end of an object or array + // and haven't checked for empty yet, we can stop here (empty structure) + if ((! is_array && json_is_object_end(buffer[pos]) + || is_array && json_is_array_end(buffer[pos])) + && ! empty_checked) { + break; + } + + empty_checked = true; + + // if dealing with an object, look for the key + if (! is_array) { + if (! json_is_string(buffer[pos])) { + LogError("json_decode: expected key string at position %d", pos); + + return null; + } + + // extract the key from the buffer + json_extract_string(buffer, length, pos, key, length, is_array); + + // skip any whitespace following the key + if (! json_skip_whitespace(buffer, length, pos)) { + LogError("json_decode: buffer ended early at position %d", pos); + + return null; + } + + // ensure that we find a colon + if (buffer[pos++] != ':') { + LogError("json_decode: expected colon after key at position %d", pos); + + return null; + } + + // skip any whitespace following the colon + if (! json_skip_whitespace(buffer, length, pos)) { + LogError("json_decode: buffer ended early at position %d", pos); + + return null; + } + } + + if (json_is_object(buffer[pos]) || json_is_array(buffer[pos])) { + // if we are dealing with an object or array + // fetch the existing object if one exists at the key + JSON_Object current = (! is_array) ? result.GetObject(key) : null; + + // decode recursively + JSON_Object value = json_decode(buffer, current, pos, depth + 1); + + // decoding failed, error will be logged in json_decode + if (value == null) { + return null; + } + + if (is_array) { + result.PushObject(value); + } else { + result.SetObject(key, value); + } + } else if (json_is_string(buffer[pos])) { + // if we are dealing with a string, attempt to extract it + if (! json_extract_string(buffer, length, pos, cell, length, is_array)) { + LogError("json_decode: couldn't extract string at position %d", pos); + + return null; + } + + if (is_array) { + result.PushString(cell); + } else { + result.SetString(key, cell); + } + } else { + if (! json_extract_until_end(buffer, length, pos, cell, length, is_array)) { + LogError("json_decode: couldn't extract until end at position %d", pos); + + return null; + } + + if (strlen(cell) == 0) { + LogError("json_decode: empty cell encountered at position %d", pos); + + return null; + } + + if (json_is_int(cell)) { + int value = json_extract_int(cell); + if (is_array) { + result.PushInt(value); + } else { + result.SetInt(key, value); + } + } else if (json_is_float(cell)) { + float value = json_extract_float(cell); + if (is_array) { + result.PushFloat(value); + } else { + result.SetFloat(key, value); + } + } else if (json_is_bool(cell)) { + bool value = json_extract_bool(cell); + if (is_array) { + result.PushBool(value); + } else { + result.SetBool(key, value); + } + } else if (json_is_null(cell)) { + if (is_array) { + result.PushHandle(null); + } else { + result.SetHandle(key, null); + } + } else { + LogError("json_decode: unknown type encountered at position %d: %s", pos, cell); + + return null; + } + } + + if (! json_skip_whitespace(buffer, length, pos)) { + LogError("json_decode: buffer ended early at position %d", pos); + + return null; + } + } + + // skip remaining whitespace and ensure we're at the end of the buffer + ++pos; + if (json_skip_whitespace(buffer, length, pos) && depth == 0) { + LogError("json_decode: unexpected data after end of structure at position %d", pos); + + return null; + } + + return result; +} + +/** + * Recursively cleans up a JSON instance and any JSON instances stored within. + * + * @param obj JSON instance to clean up. + */ +stock void json_cleanup(JSON_Object obj) +{ + bool is_array = obj.IsArray; + + int key_length = 0; + StringMapSnapshot snap = obj.Snapshot(); + for (int i = 0; i < snap.Length; ++i) { + key_length = snap.KeyBufferSize(i); + char[] key = new char[key_length]; + + // ignore meta keys + snap.GetKey(i, key, key_length); + if (json_is_meta_key(key)) { + continue; + } + + // only clean up objects + JSON_CELL_TYPE type = obj.GetKeyType(key); + if (type != Type_Object) { + continue; + } + + JSON_Object nested_obj = obj.GetObject(key); + if (nested_obj != null) { + nested_obj.Cleanup(); + delete nested_obj; + } + } + + obj.Clear(); + delete snap; + + if (is_array) { + obj.SetValue(JSON_ARRAY_INDEX_KEY, 0); + } +} |
