summaryrefslogtreecommitdiff
path: root/sourcemod/scripting/include/json.inc
diff options
context:
space:
mode:
authornavewindre <nw@moneybot.cc>2023-12-04 18:06:10 +0100
committernavewindre <nw@moneybot.cc>2023-12-04 18:06:10 +0100
commitaef0d1c1268ab7d4bc18996c9c6b4da16a40aadc (patch)
tree43e766b51704f4ab8b383583bdc1871eeeb9c698 /sourcemod/scripting/include/json.inc
parent38f1140c11724da05a23a10385061200b907cf6e (diff)
bbbbbbbbwaaaaaaaaaaa
Diffstat (limited to 'sourcemod/scripting/include/json.inc')
-rw-r--r--sourcemod/scripting/include/json.inc473
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);
+ }
+}