19 changed files with 6546 additions and 28 deletions
@ -1,3 +1,4 @@ |
|||
/.idea/ |
|||
/target/ |
|||
/ext/plugins/ |
|||
*.smx |
|||
|
|||
@ -0,0 +1 @@ |
|||
ripext from https://github.com/ErikMinekus/sm-ripext |
|||
File diff suppressed because it is too large
@ -0,0 +1,803 @@ |
|||
/** |
|||
* vim: set ts=4 : |
|||
* ============================================================================= |
|||
* sm-json |
|||
* A pure SourcePawn JSON encoder/decoder. |
|||
* https://github.com/clugg/sm-json |
|||
* |
|||
* sm-json (C)2022 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/errors> |
|||
#include <json/helpers/string> |
|||
#include <json/object> |
|||
#include <json/array> |
|||
|
|||
/** |
|||
* Calculates the buffer size required to store an encoded JSON instance. |
|||
* |
|||
* @param obj Object to encode. |
|||
* @param options Bitwise combination of `JSON_ENCODE_*` options. |
|||
* @param depth The current depth of the encoder. |
|||
* @return The required buffer size. |
|||
*/ |
|||
stock int json_encode_size(JSON_Object obj, int options = JSON_NONE, int depth = 0) |
|||
{ |
|||
bool pretty_print = (options & JSON_ENCODE_PRETTY) != 0; |
|||
|
|||
bool is_array = obj.IsArray; |
|||
|
|||
int size = 1; // for opening bracket |
|||
|
|||
// used in key iterator |
|||
int json_size = obj.Length; |
|||
JSON_Object child = null; |
|||
bool is_empty = true; |
|||
int str_length = 0; |
|||
|
|||
int key_length = 0; |
|||
for (int i = 0; i < json_size; i += 1) { |
|||
key_length = is_array ? JSON_INT_BUFFER_SIZE : obj.GetKeySize(i); |
|||
char[] key = new char[key_length]; |
|||
|
|||
if (is_array) { |
|||
IntToString(i, key, key_length); |
|||
} else { |
|||
obj.GetKey(i, key, key_length); |
|||
} |
|||
|
|||
// skip keys that are marked as hidden |
|||
if (obj.GetHidden(key)) { |
|||
continue; |
|||
} |
|||
|
|||
JSONCellType type = obj.GetType(key); |
|||
// skip keys of unknown type |
|||
if (type == JSON_Type_Invalid) { |
|||
continue; |
|||
} |
|||
|
|||
if (pretty_print) { |
|||
size += strlen(JSON_PP_NEWLINE); |
|||
size += (depth + 1) * strlen(JSON_PP_INDENT); |
|||
} |
|||
|
|||
if (! is_array) { |
|||
// add the size of the key and + 1 for : |
|||
size += json_cell_string_size(key) + 1; |
|||
|
|||
if (pretty_print) { |
|||
size += strlen(JSON_PP_AFTER_COLON); |
|||
} |
|||
} |
|||
|
|||
switch (type) { |
|||
case JSON_Type_String: { |
|||
str_length = obj.GetSize(key); |
|||
char[] value = new char[str_length]; |
|||
obj.GetString(key, value, str_length); |
|||
|
|||
size += json_cell_string_size(value); |
|||
} |
|||
case JSON_Type_Int: { |
|||
size += JSON_INT_BUFFER_SIZE; |
|||
} |
|||
#if SM_INT64_SUPPORTED |
|||
case JSON_Type_Int64: { |
|||
size += JSON_INT64_BUFFER_SIZE; |
|||
} |
|||
#endif |
|||
case JSON_Type_Float: { |
|||
size += JSON_FLOAT_BUFFER_SIZE; |
|||
} |
|||
case JSON_Type_Bool: { |
|||
size += JSON_BOOL_BUFFER_SIZE; |
|||
} |
|||
case JSON_Type_Object: { |
|||
child = obj.GetObject(key); |
|||
size += child != null ? json_encode_size(child, options, depth + 1) : JSON_NULL_BUFFER_SIZE; |
|||
} |
|||
} |
|||
|
|||
// increment for comma |
|||
size += 1; |
|||
|
|||
is_empty = false; |
|||
} |
|||
|
|||
if (! is_empty) { |
|||
// remove the final comma |
|||
size -= 1; |
|||
|
|||
if (pretty_print) { |
|||
size += strlen(JSON_PP_NEWLINE); |
|||
size += depth * strlen(JSON_PP_INDENT); |
|||
} |
|||
} |
|||
|
|||
size += 2; // closing bracket + NULL |
|||
|
|||
return size; |
|||
} |
|||
|
|||
/** |
|||
* 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 options Bitwise combination of `JSON_ENCODE_*` options. |
|||
* @param depth The current depth of the encoder. |
|||
*/ |
|||
stock void json_encode( |
|||
JSON_Object obj, |
|||
char[] output, |
|||
int max_size, |
|||
int options = JSON_NONE, |
|||
int depth = 0 |
|||
) |
|||
{ |
|||
bool pretty_print = (options & JSON_ENCODE_PRETTY) != 0; |
|||
|
|||
bool is_array = obj.IsArray; |
|||
strcopy(output, max_size, is_array ? "[" : "{"); |
|||
|
|||
// used in key iterator |
|||
int json_size = obj.Length; |
|||
int builder_size = 0; |
|||
int str_length = 1; |
|||
JSON_Object child = null; |
|||
int cell_length = 0; |
|||
bool is_empty = true; |
|||
|
|||
int key_length = 0; |
|||
for (int i = 0; i < json_size; i += 1) { |
|||
key_length = is_array ? JSON_INT_BUFFER_SIZE : obj.GetKeySize(i); |
|||
char[] key = new char[key_length]; |
|||
|
|||
if (is_array) { |
|||
IntToString(i, key, key_length); |
|||
} else { |
|||
obj.GetKey(i, key, key_length); |
|||
} |
|||
|
|||
// skip keys that are marked as hidden |
|||
if (obj.GetHidden(key)) { |
|||
continue; |
|||
} |
|||
|
|||
JSONCellType type = obj.GetType(key); |
|||
// skip keys of unknown type |
|||
if (type == JSON_Type_Invalid) { |
|||
continue; |
|||
} |
|||
|
|||
// determine the length of the char[] needed to represent our cell data |
|||
cell_length = 0; |
|||
switch (type) { |
|||
case JSON_Type_String: { |
|||
str_length = obj.GetSize(key); |
|||
char[] value = new char[str_length]; |
|||
obj.GetString(key, value, str_length); |
|||
|
|||
cell_length = json_cell_string_size(value); |
|||
} |
|||
case JSON_Type_Int: { |
|||
cell_length = JSON_INT_BUFFER_SIZE; |
|||
} |
|||
#if SM_INT64_SUPPORTED |
|||
case JSON_Type_Int64: { |
|||
cell_length = JSON_INT64_BUFFER_SIZE; |
|||
} |
|||
#endif |
|||
case JSON_Type_Float: { |
|||
cell_length = JSON_FLOAT_BUFFER_SIZE; |
|||
} |
|||
case JSON_Type_Bool: { |
|||
cell_length = JSON_BOOL_BUFFER_SIZE; |
|||
} |
|||
case JSON_Type_Object: { |
|||
child = obj.GetObject(key); |
|||
cell_length = child != null ? max_size : JSON_NULL_BUFFER_SIZE; |
|||
} |
|||
} |
|||
|
|||
// fit the contents into the cell |
|||
char[] cell = new char[cell_length]; |
|||
switch (type) { |
|||
case JSON_Type_String: { |
|||
char[] value = new char[str_length]; |
|||
obj.GetString(key, value, str_length); |
|||
json_cell_string(value, cell, cell_length); |
|||
} |
|||
case JSON_Type_Int: { |
|||
int value = obj.GetInt(key); |
|||
IntToString(value, cell, cell_length); |
|||
} |
|||
#if SM_INT64_SUPPORTED |
|||
case JSON_Type_Int64: { |
|||
int value[2]; |
|||
obj.GetInt64(key, value); |
|||
Int64ToString(value, cell, cell_length); |
|||
} |
|||
#endif |
|||
case JSON_Type_Float: { |
|||
float value = obj.GetFloat(key); |
|||
FloatToString(value, cell, cell_length); |
|||
|
|||
// trim trailing 0s from float output up until decimal point |
|||
int last_char = strlen(cell) - 1; |
|||
while (cell[last_char] == '0' && cell[last_char - 1] != '.') { |
|||
cell[last_char--] = '\0'; |
|||
} |
|||
} |
|||
case JSON_Type_Bool: { |
|||
bool value = obj.GetBool(key); |
|||
strcopy(cell, cell_length, value ? "true" : "false"); |
|||
} |
|||
case JSON_Type_Object: { |
|||
if (child != null) { |
|||
json_encode(child, cell, cell_length, options, depth + 1); |
|||
} else { |
|||
strcopy(cell, cell_length, "null"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 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(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 += 1) { |
|||
StrCat(output, max_size, JSON_PP_INDENT); |
|||
} |
|||
} |
|||
|
|||
StrCat(output, max_size, builder); |
|||
|
|||
is_empty = false; |
|||
} |
|||
|
|||
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 += 1) { |
|||
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 options Bitwise combination of `JSON_DECODE_*` options. |
|||
* @param pos Current position of the decoder as bytes |
|||
* offset into the buffer. |
|||
* @param depth Current nested depth of the decoder. |
|||
* @return JSON instance or null if decoding failed becase |
|||
* the buffer didn't contain valid JSON. |
|||
* @error If the buffer does not contain valid JSON, |
|||
* an error will be thrown. |
|||
*/ |
|||
stock JSON_Object json_decode( |
|||
const char[] buffer, |
|||
int options = JSON_NONE, |
|||
int &pos = 0, |
|||
int depth = 0 |
|||
) |
|||
{ |
|||
int length = strlen(buffer); |
|||
// skip preceding whitespace |
|||
if (! json_skip_whitespace(buffer, length, pos)) { |
|||
json_set_last_error("buffer ended early at position %d", pos); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
bool is_array = false; |
|||
JSON_Array arr = null; |
|||
JSON_Object obj = null; |
|||
if (buffer[pos] == '{') { |
|||
is_array = false; |
|||
obj = new JSON_Object(); |
|||
} else if (buffer[pos] == '[') { |
|||
is_array = true; |
|||
arr = new JSON_Array(); |
|||
} else { |
|||
json_set_last_error("no object or array found at position %d", pos); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
bool allow_single_quotes = (options & JSON_DECODE_SINGLE_QUOTES) > 0; |
|||
|
|||
bool empty_checked = false; |
|||
|
|||
// while we haven't reached the end of our structure |
|||
while ( |
|||
(! is_array && buffer[pos] != '}') |
|||
|| (is_array && buffer[pos] != ']') |
|||
) { |
|||
// pos is either an opening structure or comma, so increment past it |
|||
pos += 1; |
|||
|
|||
// skip any whitespace preceding the element |
|||
if (! json_skip_whitespace(buffer, length, pos)) { |
|||
json_set_last_error("buffer ended early at position %d", pos); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
// if we haven't checked for empty yet and we are at the end |
|||
// of an object or array, we can stop here (empty structure) |
|||
if (! empty_checked) { |
|||
if ( |
|||
(! is_array && buffer[pos] == '}') |
|||
|| (is_array && buffer[pos] == ']') |
|||
) { |
|||
break; |
|||
} |
|||
|
|||
empty_checked = true; |
|||
} |
|||
|
|||
int key_length = 1; |
|||
if (! is_array) { |
|||
// if dealing with an object, look for the key and determine length |
|||
if (! json_is_string(buffer[pos], allow_single_quotes)) { |
|||
json_set_last_error("expected key string at position %d", pos); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
key_length = json_extract_string_size(buffer, length, pos); |
|||
} |
|||
|
|||
char[] key = new char[key_length]; |
|||
|
|||
if (! is_array) { |
|||
// extract the key from the buffer |
|||
json_extract_string(buffer, length, pos, key, key_length, is_array); |
|||
|
|||
// skip any whitespace following the key |
|||
if (! json_skip_whitespace(buffer, length, pos)) { |
|||
json_set_last_error("buffer ended early at position %d", pos); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
// ensure that we find a colon |
|||
if (buffer[pos++] != ':') { |
|||
json_set_last_error( |
|||
"expected colon after key at position %d", |
|||
pos |
|||
); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
// skip any whitespace following the colon |
|||
if (! json_skip_whitespace(buffer, length, pos)) { |
|||
json_set_last_error("buffer ended early at position %d", pos); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
int cell_length = 1; |
|||
JSONCellType cell_type = JSON_Type_Invalid; |
|||
if (buffer[pos] == '{' || buffer[pos] == '[') { |
|||
cell_type = JSON_Type_Object; |
|||
} else if (json_is_string(buffer[pos], allow_single_quotes)) { |
|||
cell_type = JSON_Type_String; |
|||
cell_length = json_extract_string_size(buffer, length, pos); |
|||
} else { |
|||
// in this particular instance, we use JSON_Type_Invalid to |
|||
// represent any type that isn't an object or string |
|||
cell_length = json_extract_until_end_size( |
|||
buffer, |
|||
length, |
|||
pos, |
|||
is_array |
|||
); |
|||
} |
|||
|
|||
if (! is_array && obj.HasKey(key)) { |
|||
obj.Remove(key); |
|||
} |
|||
|
|||
char[] cell = new char[cell_length]; |
|||
switch (cell_type) { |
|||
case JSON_Type_Object: { |
|||
// if we are dealing with an object or array, decode recursively |
|||
JSON_Object value = json_decode( |
|||
buffer, |
|||
options, |
|||
pos, |
|||
depth + 1 |
|||
); |
|||
|
|||
// decoding failed, error will be logged in json_decode |
|||
if (value == null) { |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
if (is_array) { |
|||
arr.PushObject(value); |
|||
} else { |
|||
obj.SetObject(key, value); |
|||
} |
|||
} |
|||
case JSON_Type_String: { |
|||
// if we are dealing with a string, attempt to extract it |
|||
if (! json_extract_string( |
|||
buffer, |
|||
length, |
|||
pos, |
|||
cell, |
|||
cell_length, |
|||
is_array |
|||
)) { |
|||
json_set_last_error( |
|||
"couldn't extract string at position %d", |
|||
pos |
|||
); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
if (is_array) { |
|||
arr.PushString(cell); |
|||
} else { |
|||
obj.SetString(key, cell); |
|||
} |
|||
} |
|||
case JSON_Type_Invalid: { |
|||
if (! json_extract_until_end( |
|||
buffer, |
|||
length, |
|||
pos, |
|||
cell, |
|||
cell_length, |
|||
is_array |
|||
)) { |
|||
json_set_last_error( |
|||
"couldn't extract until end at position %d", |
|||
pos |
|||
); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
if (strlen(cell) == 0) { |
|||
json_set_last_error( |
|||
"empty cell encountered at position %d", |
|||
pos |
|||
); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
if (json_is_int(cell)) { |
|||
int value = StringToInt(cell); |
|||
#if SM_INT64_SUPPORTED |
|||
if (json_is_int64(cell, value)) { |
|||
int values[2]; |
|||
StringToInt64(cell, values); |
|||
|
|||
if (is_array) { |
|||
arr.PushInt64(values); |
|||
} else { |
|||
obj.SetInt64(key, values); |
|||
} |
|||
} else { |
|||
if (is_array) { |
|||
arr.PushInt(value); |
|||
} else { |
|||
obj.SetInt(key, value); |
|||
} |
|||
} |
|||
#else |
|||
if (is_array) { |
|||
arr.PushInt(value); |
|||
} else { |
|||
obj.SetInt(key, value); |
|||
} |
|||
#endif |
|||
} else if (json_is_float(cell)) { |
|||
float value = StringToFloat(cell); |
|||
if (is_array) { |
|||
arr.PushFloat(value); |
|||
} else { |
|||
obj.SetFloat(key, value); |
|||
} |
|||
} else if (StrEqual(cell, "true") || StrEqual(cell, "false")) { |
|||
bool value = StrEqual(cell, "true"); |
|||
if (is_array) { |
|||
arr.PushBool(value); |
|||
} else { |
|||
obj.SetBool(key, value); |
|||
} |
|||
} else if (StrEqual(cell, "null")) { |
|||
if (is_array) { |
|||
arr.PushObject(null); |
|||
} else { |
|||
obj.SetObject(key, null); |
|||
} |
|||
} else { |
|||
json_set_last_error( |
|||
"unknown type encountered at position %d: %s", |
|||
pos, |
|||
cell |
|||
); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (! json_skip_whitespace(buffer, length, pos)) { |
|||
json_set_last_error("buffer ended early at position %d", pos); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
// skip remaining whitespace and ensure we're at the end of the buffer |
|||
pos += 1; |
|||
if (json_skip_whitespace(buffer, length, pos) && depth == 0) { |
|||
json_set_last_error( |
|||
"unexpected data after structure end at position %d", |
|||
pos |
|||
); |
|||
json_cleanup_and_delete(obj); |
|||
json_cleanup_and_delete(arr); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
return is_array ? view_as<JSON_Object>(arr) : obj; |
|||
} |
|||
|
|||
/** |
|||
* Encodes the object with the options provided and writes |
|||
* the output to the file at the path specified. |
|||
* |
|||
* @param obj Object to encode/write to file. |
|||
* @param path Path of file to write to. |
|||
* @param options Options to pass to `json_encode`. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
stock bool json_write_to_file( |
|||
JSON_Object obj, |
|||
const char[] path, |
|||
int options = JSON_NONE |
|||
) |
|||
{ |
|||
File f = OpenFile(path, "wb"); |
|||
if (f == null) { |
|||
return false; |
|||
} |
|||
|
|||
int size = json_encode_size(obj, options); |
|||
char[] buffer = new char[size]; |
|||
json_encode(obj, buffer, size, options); |
|||
|
|||
bool success = f.WriteString(buffer, false); |
|||
delete f; |
|||
|
|||
return success; |
|||
} |
|||
|
|||
/** |
|||
* Reads and decodes the contents of a JSON file. |
|||
* |
|||
* @param path Path of file to read from. |
|||
* @param options Options to pass to `json_decode`. |
|||
* @return The decoded object on success, null otherwise. |
|||
*/ |
|||
stock JSON_Object json_read_from_file(const char[] path, int options = JSON_NONE) |
|||
{ |
|||
File f = OpenFile(path, "rb"); |
|||
if (f == null) { |
|||
return null; |
|||
} |
|||
|
|||
f.Seek(0, SEEK_END); |
|||
int size = f.Position + 1; |
|||
char[] buffer = new char[size]; |
|||
|
|||
f.Seek(0, SEEK_SET); |
|||
f.ReadString(buffer, size); |
|||
delete f; |
|||
|
|||
return json_decode(buffer, options); |
|||
} |
|||
|
|||
/** |
|||
* Creates a shallow copy of the specified object. |
|||
* |
|||
* @param obj Object to copy. |
|||
* @return A shallow copy of the specified object. |
|||
*/ |
|||
stock JSON_Object json_copy_shallow(JSON_Object obj) |
|||
{ |
|||
bool isArray = obj.IsArray; |
|||
JSON_Object result = isArray |
|||
? view_as<JSON_Object>(new JSON_Array()) |
|||
: new JSON_Object(); |
|||
|
|||
if (isArray) { |
|||
view_as<JSON_Array>(result).Concat(view_as<JSON_Array>(obj)); |
|||
} else { |
|||
result.Merge(obj); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* Creates a deep copy of the specified object. |
|||
* |
|||
* @param obj Object to copy. |
|||
* @return A deep copy of the specified object. |
|||
*/ |
|||
stock JSON_Object json_copy_deep(JSON_Object obj) |
|||
{ |
|||
JSON_Object result = json_copy_shallow(obj); |
|||
|
|||
int length = obj.Length; |
|||
int key_length = 0; |
|||
for (int i = 0; i < length; i += 1) { |
|||
key_length = obj.GetKeySize(i); |
|||
char[] key = new char[key_length]; |
|||
obj.GetKey(i, key, key_length); |
|||
|
|||
// only deep copy objects |
|||
JSONCellType type = obj.GetType(key); |
|||
if (type != JSON_Type_Object) { |
|||
continue; |
|||
} |
|||
|
|||
JSON_Object value = obj.GetObject(key); |
|||
result.SetObject(key, value != null ? json_copy_deep(value) : null); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* Recursively cleans up the instance and any instances stored within. |
|||
* |
|||
* @param obj Object to clean up. |
|||
*/ |
|||
stock void json_cleanup(JSON_Object obj) |
|||
{ |
|||
if (obj == null) { |
|||
return; |
|||
} |
|||
|
|||
int length = obj.Length; |
|||
int key_length = 0; |
|||
for (int i = 0; i < length; i += 1) { |
|||
key_length = obj.GetKeySize(i); |
|||
char[] key = new char[key_length]; |
|||
obj.GetKey(i, key, key_length); |
|||
|
|||
// only clean up objects |
|||
JSONCellType type = obj.GetType(key); |
|||
if (type != JSON_Type_Object) { |
|||
continue; |
|||
} |
|||
|
|||
JSON_Object value = obj.GetObject(key); |
|||
if (value != null) { |
|||
json_cleanup(value); |
|||
} |
|||
} |
|||
|
|||
obj.Super.Cleanup(); |
|||
} |
|||
|
|||
/** |
|||
* Cleans up an object and sets the passed variable to null. |
|||
* |
|||
* @param obj Object to clean up. |
|||
*/ |
|||
stock void json_cleanup_and_delete(JSON_Object &obj) |
|||
{ |
|||
json_cleanup(obj); |
|||
obj = null; |
|||
} |
|||
@ -0,0 +1,955 @@ |
|||
/** |
|||
* vim: set ts=4 : |
|||
* ============================================================================= |
|||
* sm-json |
|||
* A pure SourcePawn JSON encoder/decoder. |
|||
* https://github.com/clugg/sm-json |
|||
* |
|||
* sm-json (C)2022 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_array_included |
|||
#endinput |
|||
#endif |
|||
#define _json_array_included |
|||
|
|||
#include <string> |
|||
#include <json/definitions> |
|||
#include <json/helpers/errors> |
|||
#include <json/object> |
|||
|
|||
methodmap JSON_Array < JSON_Object |
|||
{ |
|||
/** |
|||
* @section Helpers |
|||
*/ |
|||
|
|||
/** |
|||
* Views the instance as its superclass to access overridden methods. |
|||
*/ |
|||
property JSON_Object Super |
|||
{ |
|||
public get() |
|||
{ |
|||
return view_as<JSON_Object>(this); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* The enforced type of the array. |
|||
*/ |
|||
property JSONCellType Type |
|||
{ |
|||
public get() |
|||
{ |
|||
return view_as<JSONCellType>(this.Meta.GetOptionalValue( |
|||
JSON_ENFORCE_TYPE_KEY, |
|||
JSON_Type_Invalid |
|||
)); |
|||
} |
|||
|
|||
public set(JSONCellType value) |
|||
{ |
|||
if (value == JSON_Type_Invalid) { |
|||
this.Meta.Remove(JSON_ENFORCE_TYPE_KEY); |
|||
} else { |
|||
this.Meta.SetValue(JSON_ENFORCE_TYPE_KEY, value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Checks whether the array accepts the type provided. |
|||
* |
|||
* @param type Type to check for enforcement. |
|||
* @return True if the type can be used, false otherwise. |
|||
*/ |
|||
public bool CanUseType(JSONCellType type) |
|||
{ |
|||
return this.Type == JSON_Type_Invalid || this.Type == type; |
|||
} |
|||
|
|||
/** |
|||
* Checks whether the object has an index. |
|||
* |
|||
* @param index Index to check existence of. |
|||
* @return True if the index exists, false otherwise. |
|||
*/ |
|||
public bool HasKey(int index) |
|||
{ |
|||
return index >= 0 && index < this.Length; |
|||
} |
|||
|
|||
/** |
|||
* @section Metadata Getters |
|||
*/ |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* @internal |
|||
* |
|||
* @see MetaStringMap.GetMeta |
|||
*/ |
|||
public any GetMeta(int index, JSONMetaInfo meta, any default_value) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return default_value; |
|||
} |
|||
|
|||
return this.Super.GetMeta(key, meta, default_value); |
|||
} |
|||
|
|||
/** |
|||
* Gets the cell type stored at an index. |
|||
* |
|||
* @param index Index to get value type for. |
|||
* @return Value type for index provided, |
|||
* or JSON_Type_Invalid if it does not exist. |
|||
*/ |
|||
public JSONCellType GetType(int index) |
|||
{ |
|||
return view_as<JSONCellType>( |
|||
this.GetMeta(index, JSON_Meta_Type, JSON_Type_Invalid) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Gets the length of the string stored at an index. |
|||
* |
|||
* @param index Index to get string length for. |
|||
* @return Length of string at index provided, |
|||
* or -1 if it is not a string/does not exist. |
|||
*/ |
|||
public int GetSize(int index) |
|||
{ |
|||
return view_as<int>(this.GetMeta(index, JSON_Meta_Size, -1)); |
|||
} |
|||
|
|||
/** |
|||
* Gets whether the index should be hidden from encoding. |
|||
* |
|||
* @param index Index to get hidden state for. |
|||
* @return Whether or not the index should be hidden. |
|||
*/ |
|||
public bool GetHidden(int index) |
|||
{ |
|||
return view_as<bool>(this.GetMeta(index, JSON_Meta_Hidden, false)); |
|||
} |
|||
|
|||
/** |
|||
* @section Metadata Setters |
|||
*/ |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* @internal |
|||
* |
|||
* @see JSON_Object.SetMeta |
|||
*/ |
|||
public bool SetMeta(int index, JSONMetaInfo meta, any value) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.SetMeta(key, meta, value); |
|||
} |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* @internal |
|||
* |
|||
* @see JSON_Object.RemoveMeta |
|||
*/ |
|||
public bool RemoveMeta(int index, JSONMetaInfo meta) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.RemoveMeta(key, meta); |
|||
} |
|||
|
|||
/** |
|||
* Sets whether the index should be hidden from encoding. |
|||
* |
|||
* @param index Index to set hidden state for. |
|||
* @param hidden Whether or not the index should be hidden. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetHidden(int index, bool hidden) |
|||
{ |
|||
return this.SetMeta(index, JSON_Meta_Hidden, hidden); |
|||
} |
|||
|
|||
/** |
|||
* @section Getters |
|||
*/ |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see MetaStringMap.GetValue |
|||
*/ |
|||
public bool GetValue(int index, any &value) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.GetValue(key, value); |
|||
} |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* @internal |
|||
* |
|||
* @see MetaStringMap.GetOptionalValue |
|||
*/ |
|||
public any GetOptionalValue(int index, any default_value = -1) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.GetOptionalValue(key, default_value); |
|||
} |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see MetaStringMap.GetString |
|||
*/ |
|||
public bool GetString(int index, char[] value, int max_size, int &size = 0) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.GetString(key, value, max_size, size); |
|||
} |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see MetaStringMap.GetInt |
|||
*/ |
|||
public int GetInt(int index, int default_value = -1) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return default_value; |
|||
} |
|||
|
|||
return this.Super.GetInt(key, default_value); |
|||
} |
|||
|
|||
#if SM_INT64_SUPPORTED |
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see JSON_Object.GetInt64 |
|||
*/ |
|||
public bool GetInt64(int index, int value[2]) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.GetInt64(key, value); |
|||
} |
|||
#endif |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see MetaStringMap.GetFloat |
|||
*/ |
|||
public float GetFloat(int index, float default_value = -1.0) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return default_value; |
|||
} |
|||
|
|||
return this.Super.GetFloat(key, default_value); |
|||
} |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see MetaStringMap.GetBool |
|||
*/ |
|||
public bool GetBool(int index, bool default_value = false) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return default_value; |
|||
} |
|||
|
|||
return this.Super.GetBool(key, default_value); |
|||
} |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see MetaStringMap.GetObject |
|||
*/ |
|||
public JSON_Object GetObject(int index, JSON_Object default_value = null) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return default_value; |
|||
} |
|||
|
|||
return this.Super.GetObject(key, default_value); |
|||
} |
|||
|
|||
/** |
|||
* @section Setters |
|||
*/ |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see JSON_Object.SetString |
|||
*/ |
|||
public bool SetString(int index, const char[] value) |
|||
{ |
|||
if (! this.CanUseType(JSON_Type_String)) { |
|||
return false; |
|||
} |
|||
|
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.SetString(key, value); |
|||
} |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see JSON_Object.SetInt |
|||
*/ |
|||
public bool SetInt(int index, int value) |
|||
{ |
|||
if (! this.CanUseType(JSON_Type_Int)) { |
|||
return false; |
|||
} |
|||
|
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.SetInt(key, value); |
|||
} |
|||
|
|||
#if SM_INT64_SUPPORTED |
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see JSON_Object.SetInt64 |
|||
*/ |
|||
public bool SetInt64(int index, int value[2]) |
|||
{ |
|||
if (! this.CanUseType(JSON_Type_Int64)) { |
|||
return false; |
|||
} |
|||
|
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.SetInt64(key, value); |
|||
} |
|||
#endif |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see JSON_Object.SetFloat |
|||
*/ |
|||
public bool SetFloat(int index, float value) |
|||
{ |
|||
if (! this.CanUseType(JSON_Type_Float)) { |
|||
return false; |
|||
} |
|||
|
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.SetFloat(key, value); |
|||
} |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see JSON_Object.SetBool |
|||
*/ |
|||
public bool SetBool(int index, bool value) |
|||
{ |
|||
if (! this.CanUseType(JSON_Type_Bool)) { |
|||
return false; |
|||
} |
|||
|
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.SetBool(key, value); |
|||
} |
|||
|
|||
/** |
|||
* Converts index to a string ('key') and calls the relevant Super method. |
|||
* |
|||
* @see JSON_Object.SetObject |
|||
*/ |
|||
public bool SetObject(int index, JSON_Object value) |
|||
{ |
|||
if (! this.CanUseType(JSON_Type_Object)) { |
|||
return false; |
|||
} |
|||
|
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
return this.Super.SetObject(key, value); |
|||
} |
|||
|
|||
/** |
|||
* @section Pushers |
|||
*/ |
|||
|
|||
/** |
|||
* Pushes a string to the end of the array. |
|||
* |
|||
* @param value Value to push. |
|||
* @return The element's index on success, -1 otherwise. |
|||
*/ |
|||
public int PushString(const char[] value) |
|||
{ |
|||
int index = this.Length; |
|||
if (! this.SetString(index, value)) { |
|||
return -1; |
|||
} |
|||
|
|||
return index; |
|||
} |
|||
|
|||
/** |
|||
* Pushes an int to the end of the array. |
|||
* |
|||
* @param value Value to push. |
|||
* @return The element's index on success, -1 otherwise. |
|||
*/ |
|||
public int PushInt(int value) |
|||
{ |
|||
int index = this.Length; |
|||
if (! this.SetInt(index, value)) { |
|||
return -1; |
|||
} |
|||
|
|||
return index; |
|||
} |
|||
|
|||
#if SM_INT64_SUPPORTED |
|||
/** |
|||
* Pushes an int64 to the end of the array. |
|||
* |
|||
* @param value Value to push. |
|||
* @return The element's index on success, -1 otherwise. |
|||
*/ |
|||
public int PushInt64(int value[2]) |
|||
{ |
|||
int index = this.Length; |
|||
if (! this.SetInt64(index, value)) { |
|||
return -1; |
|||
} |
|||
|
|||
return index; |
|||
} |
|||
#endif |
|||
|
|||
/** |
|||
* Pushes a float to the end of the array. |
|||
* |
|||
* @param value Value to push. |
|||
* @return The element's index on success, -1 otherwise. |
|||
*/ |
|||
public int PushFloat(float value) |
|||
{ |
|||
int index = this.Length; |
|||
if (! this.SetFloat(index, value)) { |
|||
return -1; |
|||
} |
|||
|
|||
return index; |
|||
} |
|||
|
|||
/** |
|||
* Pushes a bool to the end of the array. |
|||
* |
|||
* @param value Value to push. |
|||
* @return The element's index on success, -1 otherwise. |
|||
*/ |
|||
public int PushBool(bool value) |
|||
{ |
|||
int index = this.Length; |
|||
if (! this.SetBool(index, value)) { |
|||
return -1; |
|||
} |
|||
|
|||
return index; |
|||
} |
|||
|
|||
/** |
|||
* Pushes a JSON object to the end of the array. |
|||
* |
|||
* @param value Value to push. |
|||
* @return The element's index on success, -1 otherwise. |
|||
*/ |
|||
public int PushObject(JSON_Object value) |
|||
{ |
|||
int index = this.Length; |
|||
if (! this.SetObject(index, value)) { |
|||
return -1; |
|||
} |
|||
|
|||
return index; |
|||
} |
|||
|
|||
/** |
|||
* @section Search Helpers |
|||
*/ |
|||
|
|||
/** |
|||
* Finds the index of a value in the array. |
|||
* |
|||
* @param value Value to search for. |
|||
* @return The index of the value if it is found, -1 otherwise. |
|||
*/ |
|||
public int IndexOf(any value) |
|||
{ |
|||
any current; |
|||
int length = this.Length; |
|||
for (int i = 0; i < length; i += 1) { |
|||
if (this.GetValue(i, current) && value == current) { |
|||
return i; |
|||
} |
|||
} |
|||
|
|||
return -1; |
|||
} |
|||
|
|||
/** |
|||
* Finds the index of a string in the array. |
|||
* |
|||
* @param value String to search for. |
|||
* @return The index of the string if it is found, -1 otherwise. |
|||
*/ |
|||
public int IndexOfString(const char[] value) |
|||
{ |
|||
int length = this.Length; |
|||
for (int i = 0; i < length; i += 1) { |
|||
if (this.GetType(i) != JSON_Type_String) { |
|||
continue; |
|||
} |
|||
|
|||
int current_size = this.GetSize(i); |
|||
char[] current = new char[current_size]; |
|||
this.GetString(i, current, current_size); |
|||
if (StrEqual(value, current)) { |
|||
return i; |
|||
} |
|||
} |
|||
|
|||
return -1; |
|||
} |
|||
|
|||
/** |
|||
* Determines whether the array contains a value. |
|||
* |
|||
* @param value Value to search for. |
|||
* @return True if the value is found, false otherwise. |
|||
*/ |
|||
public bool Contains(any value) |
|||
{ |
|||
return this.IndexOf(value) != -1; |
|||
} |
|||
|
|||
/** |
|||
* Determines whether the array contains a string. |
|||
* |
|||
* @param value String to search for. |
|||
* @return True if the string is found, false otherwise. |
|||
*/ |
|||
public bool ContainsString(const char[] value) |
|||
{ |
|||
return this.IndexOfString(value) != -1; |
|||
} |
|||
|
|||
/** |
|||
* @section Misc |
|||
*/ |
|||
|
|||
/** |
|||
* Removes an index and its related meta-keys from the array, |
|||
* and shifts down all following element indices. |
|||
* |
|||
* @param key Key to remove. |
|||
* @return True on success, false if the value was never set. |
|||
*/ |
|||
public bool Remove(int index) |
|||
{ |
|||
char key[JSON_INT_BUFFER_SIZE]; |
|||
if (! this.GetKey(index, key, sizeof(key))) { |
|||
return false; |
|||
} |
|||
|
|||
int length = this.Length; |
|||
|
|||
// remove existing value at index |
|||
if (! this.Super.Remove(key)) { |
|||
return false; |
|||
} |
|||
|
|||
// shift all following elements down |
|||
char current_key[JSON_INT_BUFFER_SIZE]; |
|||
for (int oldIndex = index + 1; oldIndex < length; oldIndex += 1) { |
|||
int newIndex = oldIndex - 1; |
|||
JSONCellType type = this.GetType(oldIndex); |
|||
|
|||
switch (type) { |
|||
case JSON_Type_String: { |
|||
int str_length = this.GetSize(oldIndex); |
|||
char[] str_value = new char[str_length]; |
|||
|
|||
this.GetString(oldIndex, str_value, str_length); |
|||
this.SetString(newIndex, str_value); |
|||
} |
|||
case JSON_Type_Int: { |
|||
this.SetInt(newIndex, this.GetInt(oldIndex)); |
|||
} |
|||
#if SM_INT64_SUPPORTED |
|||
case JSON_Type_Int64: { |
|||
int value[2]; |
|||
this.GetInt64(oldIndex, value); |
|||
this.SetInt64(newIndex, value); |
|||
} |
|||
#endif |
|||
case JSON_Type_Float: { |
|||
this.SetFloat(newIndex, this.GetFloat(oldIndex)); |
|||
} |
|||
case JSON_Type_Bool: { |
|||
this.SetBool(newIndex, this.GetBool(oldIndex)); |
|||
} |
|||
case JSON_Type_Object: { |
|||
this.SetObject(newIndex, this.GetObject(oldIndex)); |
|||
} |
|||
} |
|||
|
|||
this.SetHidden(newIndex, this.GetHidden(oldIndex)); |
|||
|
|||
if (this.GetKey( |
|||
oldIndex, |
|||
current_key, |
|||
sizeof(current_key) |
|||
)) { |
|||
this.Super.Remove(current_key); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Concatenates the entries from the specified array |
|||
* on to the end of this array. |
|||
* |
|||
* @param from Array to concat entries from. |
|||
* @return True on success, false otherwise. |
|||
* @error If the object being merged is an object or the |
|||
* arrays being merged don't have the same strict |
|||
* type set, an error will be thrown. |
|||
*/ |
|||
public bool Concat(JSON_Array from) |
|||
{ |
|||
if (! this.IsArray || ! from.IsArray) { |
|||
json_set_last_error("attempted to concat using object(s)"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
if (this.Type != from.Type) { |
|||
json_set_last_error( |
|||
"attempted to concat arrays with mismatched strict types" |
|||
); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
int current_length = this.Length; |
|||
int json_size = from.Length; |
|||
for (int i = 0; i < json_size; i += 1) { |
|||
JSONCellType type = from.GetType(i); |
|||
// skip keys of unknown type |
|||
if (type == JSON_Type_Invalid) { |
|||
continue; |
|||
} |
|||
|
|||
// push value onto array |
|||
switch (type) { |
|||
case JSON_Type_String: { |
|||
int length = from.GetSize(i); |
|||
char[] value = new char[length]; |
|||
from.GetString(i, value, length); |
|||
|
|||
this.PushString(value); |
|||
} |
|||
case JSON_Type_Int: { |
|||
this.PushInt(from.GetInt(i)); |
|||
} |
|||
#if SM_INT64_SUPPORTED |
|||
case JSON_Type_Int64: { |
|||
int value[2]; |
|||
from.GetInt64(i, value); |
|||
this.PushInt64(value); |
|||
} |
|||
#endif |
|||
case JSON_Type_Float: { |
|||
this.PushFloat(from.GetFloat(i)); |
|||
} |
|||
case JSON_Type_Bool: { |
|||
this.PushBool(from.GetBool(i)); |
|||
} |
|||
case JSON_Type_Object: { |
|||
this.PushObject(from.GetObject(i)); |
|||
} |
|||
} |
|||
|
|||
this.SetHidden(current_length + i, from.GetHidden(i)); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Typed Helpers |
|||
*/ |
|||
|
|||
/** |
|||
* The length of the longest string in the array. |
|||
*/ |
|||
property int MaxStringLength |
|||
{ |
|||
public get() |
|||
{ |
|||
int max = -1; |
|||
int current = -1; |
|||
int length = this.Length; |
|||
for (int i = 0; i < length; i += 1) { |
|||
if (this.GetType(i) != JSON_Type_String) { |
|||
continue; |
|||
} |
|||
|
|||
current = this.GetSize(i); |
|||
if (current > max) { |
|||
max = current; |
|||
} |
|||
} |
|||
|
|||
return max; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Sets the array to enforce a specific type. |
|||
* This will fail if there are any existing elements |
|||
* in the array which are not of the same type. |
|||
* |
|||
* @param type Type to enforce. |
|||
* @return True if the type was enforced successfully, false otherwise. |
|||
*/ |
|||
public bool EnforceType(JSONCellType type) |
|||
{ |
|||
if (type == JSON_Type_Invalid) { |
|||
this.Type = type; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
int length = this.Length; |
|||
for (int i = 0; i < length; i += 1) { |
|||
if (this.GetType(i) != type) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
this.Type = type; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Imports a native array's values into the instance. |
|||
* |
|||
* @param type Type of native values. |
|||
* @param values Array of values. |
|||
* @param size Size of array. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool ImportValues(JSONCellType type, any[] values, int size) |
|||
{ |
|||
bool success = true; |
|||
for (int i = 0; i < size; i += 1) { |
|||
switch (type) { |
|||
case JSON_Type_Int: { |
|||
success = success && this.PushInt(values[i]) > -1; |
|||
} |
|||
case JSON_Type_Float: { |
|||
success = success && this.PushFloat(values[i]) > -1; |
|||
} |
|||
case JSON_Type_Bool: { |
|||
success = success && this.PushBool(values[i]) > -1; |
|||
} |
|||
case JSON_Type_Object: { |
|||
success = success && this.PushObject(values[i]) > -1; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return success; |
|||
} |
|||
|
|||
/** |
|||
* Imports a native array's strings into the instance. |
|||
* |
|||
* @param strings Array of strings. |
|||
* @param size Size of array. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool ImportStrings(const char[][] strings, int size) |
|||
{ |
|||
bool success = true; |
|||
for (int i = 0; i < size; i += 1) { |
|||
success = success && this.PushString(strings[i]) > -1; |
|||
} |
|||
|
|||
return success; |
|||
} |
|||
|
|||
/** |
|||
* Exports the instance's values into a native array. |
|||
* |
|||
* @param values Array to export to. |
|||
* @param max_size Maximum size of array. |
|||
*/ |
|||
public void ExportValues(any[] values, int max_size) |
|||
{ |
|||
int length = this.Length; |
|||
if (length < max_size) { |
|||
max_size = length; |
|||
} |
|||
|
|||
for (int i = 0; i < max_size; i += 1) { |
|||
this.GetValue(i, values[i]); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Exports the instance's strings into a native array. |
|||
* |
|||
* @param values Array to export to. |
|||
* @param max_size Maximum size of array. |
|||
* @param max_string_size Maximum size of array elements. |
|||
*/ |
|||
public void ExportStrings( |
|||
char[][] values, |
|||
int max_size, |
|||
int max_string_size |
|||
) { |
|||
int length = this.Length; |
|||
if (length < max_size) { |
|||
max_size = length; |
|||
} |
|||
|
|||
for (int i = 0; i < max_size; i += 1) { |
|||
this.GetString(i, values[i], max_string_size); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* json.inc Aliases |
|||
*/ |
|||
|
|||
/** @see JSON_Object.ShallowCopy */ |
|||
public JSON_Array ShallowCopy() |
|||
{ |
|||
return view_as<JSON_Array>(this.Super.ShallowCopy()); |
|||
} |
|||
|
|||
/** @see JSON_Object.DeepCopy */ |
|||
public JSON_Array DeepCopy() |
|||
{ |
|||
return view_as<JSON_Array>(this.Super.DeepCopy()); |
|||
} |
|||
|
|||
/** |
|||
* @section Constructor |
|||
*/ |
|||
|
|||
/** |
|||
* Creates a new JSON_Array. |
|||
* |
|||
* @param type The type to enforce for this array, or |
|||
* JSON_Type_Invalid for no enforced type. |
|||
* @return A new JSON_Array. |
|||
*/ |
|||
public JSON_Array(JSONCellType type = JSON_Type_Invalid) |
|||
{ |
|||
JSON_Array self = view_as<JSON_Array>(new JSON_Object()); |
|||
self.Meta.SetBool(JSON_ARRAY_KEY, true); |
|||
self.EnforceType(type); |
|||
|
|||
return self; |
|||
} |
|||
}; |
|||
@ -0,0 +1,180 @@ |
|||
/** |
|||
* vim: set ts=4 : |
|||
* ============================================================================= |
|||
* sm-json |
|||
* A pure SourcePawn JSON encoder/decoder. |
|||
* https://github.com/clugg/sm-json |
|||
* |
|||
* sm-json (C)2022 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_definitions_included |
|||
#endinput |
|||
#endif |
|||
#define _json_definitions_included |
|||
|
|||
#include <string> |
|||
#include <json/helpers/string> |
|||
|
|||
#define SM_INT64_SUPPORTED SOURCEMOD_V_MAJOR >= 1 \ |
|||
&& SOURCEMOD_V_MINOR >= 11 \ |
|||
&& SOURCEMOD_V_REV >= 6861 |
|||
|
|||
/** |
|||
* @section Settings |
|||
*/ |
|||
|
|||
/** Used when no options are desired. */ |
|||
#define JSON_NONE 0 |
|||
|
|||
/** |
|||
* @section json_encode settings |
|||
*/ |
|||
|
|||
/** Should encoded output be pretty printed? */ |
|||
#define JSON_ENCODE_PRETTY 1 << 0 |
|||
|
|||
/** |
|||
* @section json_decode settings |
|||
*/ |
|||
|
|||
/** Should single quote wrapped strings be accepted during decoding? */ |
|||
#define JSON_DECODE_SINGLE_QUOTES 1 << 0 |
|||
|
|||
/** |
|||
* @section json_merge settings |
|||
*/ |
|||
|
|||
/** During merge, should existing keys be replaced if they exist in both objects? */ |
|||
#define JSON_MERGE_REPLACE 1 << 0 |
|||
|
|||
/** During merge, should existing objects be cleaned up if they exist in |
|||
* both objects? (only applies when JSON_MERGE_REPLACE is also set) */ |
|||
#define JSON_MERGE_CLEANUP 1 << 1 |
|||
|
|||
/** |
|||
* @section Pretty Print Constants |
|||
* |
|||
* Used to determine how pretty printed JSON should be formatted when encoded. |
|||
* You can modify these if you prefer your JSON formatted differently. |
|||
*/ |
|||
|
|||
char JSON_PP_AFTER_COLON[32] = " "; |
|||
char JSON_PP_INDENT[32] = " "; |
|||
char JSON_PP_NEWLINE[32] = "\n"; |
|||
|
|||
/** |
|||
* @section Buffer Size Constants |
|||
*/ |
|||
|
|||
/** The longest representable integer ("-2147483648") + NULL terminator */ |
|||
#define JSON_INT_BUFFER_SIZE 12 |
|||
|
|||
#if SM_INT64_SUPPORTED |
|||
/** The longest representable int64 ("-9223372036854775808") + NULL terminator */ |
|||
#define JSON_INT64_BUFFER_SIZE 21 |
|||
#endif |
|||
|
|||
/** You may need to change this if you are working with large floats. */ |
|||
#define JSON_FLOAT_BUFFER_SIZE 32 |
|||
|
|||
/** "true"|"false" + NULL terminator */ |
|||
#define JSON_BOOL_BUFFER_SIZE 6 |
|||
|
|||
/** "null" + NULL terminator */ |
|||
#define JSON_NULL_BUFFER_SIZE 5 |
|||
|
|||
/** |
|||
* @section Array/Object Constants |
|||
*/ |
|||
|
|||
#define JSON_ARRAY_KEY "is_array" |
|||
#define JSON_ENFORCE_TYPE_KEY "enforce_type" |
|||
|
|||
/** |
|||
* Types of cells within a JSON object |
|||
*/ |
|||
enum JSONCellType { |
|||
JSON_Type_Invalid = -1, |
|||
JSON_Type_String = 0, |
|||
JSON_Type_Int, |
|||
#if SM_INT64_SUPPORTED |
|||
JSON_Type_Int64, |
|||
#endif |
|||
JSON_Type_Float, |
|||
JSON_Type_Bool, |
|||
JSON_Type_Object |
|||
}; |
|||
|
|||
/** |
|||
* Types of metadata a JSON element can have |
|||
*/ |
|||
enum JSONMetaInfo { |
|||
JSON_Meta_Type = 0, |
|||
JSON_Meta_Size, |
|||
JSON_Meta_Hidden, |
|||
JSON_Meta_Index |
|||
} |
|||
|
|||
/** |
|||
* An array of all possible meta info values. |
|||
*/ |
|||
JSONMetaInfo JSON_ALL_METADATA[4] = { |
|||
JSON_Meta_Type, JSON_Meta_Size, JSON_Meta_Hidden, JSON_Meta_Index |
|||
}; |
|||
|
|||
/** |
|||
* Calculates the length required to store a meta key |
|||
* for a specified key/metainfo combination. |
|||
* @internal |
|||
* |
|||
* @param key |
|||
* @return The length required to store the meta key. |
|||
*/ |
|||
stock int json_meta_key_length(const char[] key) |
|||
{ |
|||
// %s:%d |
|||
return strlen(key) + 1 + JSON_INT_BUFFER_SIZE; |
|||
} |
|||
|
|||
/** |
|||
* Formats the key/metainfo combination into a buffer. |
|||
* @internal |
|||
* |
|||
* @param output String buffer to store output. |
|||
* @param max_size Maximum size of string buffer. |
|||
* @param key Key to generate metakey for. |
|||
* @param meta Meta information to generate metakey for. |
|||
*/ |
|||
stock void json_format_meta_key( |
|||
char[] output, |
|||
int max_size, |
|||
const char[] key, |
|||
JSONMetaInfo meta |
|||
) |
|||
{ |
|||
FormatEx(output, max_size, "k|%s:%d", key, view_as<int>(meta)); |
|||
} |
|||
@ -0,0 +1,567 @@ |
|||
/** |
|||
* vim: set ts=4 : |
|||
* ============================================================================= |
|||
* sm-json |
|||
* A pure SourcePawn JSON encoder/decoder. |
|||
* https://github.com/clugg/sm-json |
|||
* |
|||
* sm-json (C)2022 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_helpers_decode_included |
|||
#endinput |
|||
#endif |
|||
#define _json_helpers_decode_included |
|||
|
|||
#include <string> |
|||
#include <json/helpers/string> |
|||
|
|||
/** |
|||
* @section Determine Buffer Contents |
|||
*/ |
|||
|
|||
/** |
|||
* Checks whether the character at the beginning of the buffer is whitespace. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @return True if the first character in the buffer |
|||
* is whitespace, false otherwise. |
|||
*/ |
|||
stock bool json_is_whitespace(const char[] buffer) |
|||
{ |
|||
return buffer[0] == ' ' |
|||
|| buffer[0] == '\t' |
|||
|| buffer[0] == '\r' |
|||
|| buffer[0] == '\n'; |
|||
} |
|||
|
|||
/** |
|||
* Checks whether the character at the beginning |
|||
* of the buffer is the start of a string. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @param allow_single_quotes Should strings using single quotes be accepted? |
|||
* @return True if the first character in the buffer |
|||
* is the start of a string, false otherwise. |
|||
*/ |
|||
stock bool json_is_string(const char[] buffer, bool allow_single_quotes = false) |
|||
{ |
|||
return buffer[0] == '"' || (allow_single_quotes && buffer[0] == '\''); |
|||
} |
|||
|
|||
/** |
|||
* Checks whether the buffer provided contains an int. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @return True if buffer contains an int, false otherwise. |
|||
*/ |
|||
stock bool json_is_int(const char[] buffer) |
|||
{ |
|||
int length = strlen(buffer); |
|||
if (length == 0) { |
|||
return false; |
|||
} |
|||
|
|||
bool starts_with_zero = false; |
|||
bool has_digit_gt_zero = false; |
|||
|
|||
for (int i = 0; i < length; i += 1) { |
|||
// allow minus as first character only |
|||
if (i == 0 && buffer[i] == '-') { |
|||
continue; |
|||
} |
|||
|
|||
if (! IsCharNumeric(buffer[i])) { |
|||
return false; |
|||
} |
|||
|
|||
if (buffer[i] == '0') { |
|||
if (starts_with_zero) { |
|||
// detect repeating leading zeros |
|||
return false; |
|||
} else if (! has_digit_gt_zero) { |
|||
starts_with_zero = true; |
|||
} |
|||
} else if (starts_with_zero) { |
|||
// detect numbers with leading zero |
|||
return false; |
|||
} else { |
|||
has_digit_gt_zero = true; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
#if SM_INT64_SUPPORTED |
|||
/** |
|||
* Checks whether the buffer provided contains an int64, assuming it has |
|||
* already been validated as an int and attempted to convert to an int32. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @param value Converted int value to compare with. |
|||
* @return True if buffer contains an int64, false otherwise. |
|||
*/ |
|||
stock bool json_is_int64(const char[] buffer, int value) |
|||
{ |
|||
if ( |
|||
(value == 0 && ! StrEqual(buffer, "0")) |
|||
|| (value == -1 && ! StrEqual(buffer, "-1")) |
|||
) { |
|||
// failed to produce output of validated int, must be 64bit |
|||
return true; |
|||
} |
|||
|
|||
if (buffer[0] != '-' && value < 0) { |
|||
// 32-bit unsigned positive int which is incorrectly |
|||
// interpreted as a negative signed int by sourcepawn |
|||
return true; |
|||
} |
|||
|
|||
if (buffer[0] == '-' && value > 0) { |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
#endif |
|||
|
|||
/** |
|||
* Checks whether the buffer provided contains a float. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @return True if buffer contains a float, false otherwise. |
|||
*/ |
|||
stock bool json_is_float(const char[] buffer) |
|||
{ |
|||
int length = strlen(buffer); |
|||
if (length == 0) { |
|||
return false; |
|||
} |
|||
|
|||
bool starts_with_zero = false; |
|||
bool has_digit_gt_zero = false; |
|||
bool after_decimal = false; |
|||
bool has_digit_after_decimal = false; |
|||
bool after_exponent = false; |
|||
bool has_digit_after_exponent = false; |
|||
|
|||
for (int i = 0; i < length; i += 1) { |
|||
// allow minus as first character only |
|||
if (i == 0 && buffer[i] == '-') { |
|||
continue; |
|||
} |
|||
|
|||
// if we haven't encountered a decimal or exponent yet |
|||
if (! after_decimal && ! after_exponent) { |
|||
if (buffer[i] == '.') { |
|||
// if we encounter a decimal before any digits |
|||
if (! starts_with_zero && ! has_digit_gt_zero) { |
|||
return false; |
|||
} |
|||
|
|||
after_decimal = true; |
|||
} else if (buffer[i] == 'e' || buffer[i] == 'E') { |
|||
// if we encounter an exponent before any digits |
|||
if (! starts_with_zero && ! has_digit_gt_zero) { |
|||
return false; |
|||
} |
|||
|
|||
after_exponent = true; |
|||
} else if (IsCharNumeric(buffer[i])) { |
|||
if (buffer[i] == '0') { |
|||
if (starts_with_zero) { |
|||
// detect repeating leading zeros |
|||
return false; |
|||
} else if (! has_digit_gt_zero) { |
|||
starts_with_zero = true; |
|||
} |
|||
} else { |
|||
if (starts_with_zero) { |
|||
// detect numbers with leading zero |
|||
return false; |
|||
} |
|||
|
|||
has_digit_gt_zero = true; |
|||
} |
|||
} else { |
|||
return false; |
|||
} |
|||
} else if (after_decimal && ! after_exponent) { |
|||
// after decimal has been encountered, allow any numerics |
|||
if (IsCharNumeric(buffer[i])) { |
|||
has_digit_after_decimal = true; |
|||
} else if (buffer[i] == 'e' || buffer[i] == 'E') { |
|||
if (! has_digit_after_decimal) { |
|||
// detect exponents directly after decimal |
|||
return false; |
|||
} |
|||
|
|||
after_exponent = true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} else if (after_exponent) { |
|||
if ( |
|||
(buffer[i] == '+' || buffer[i] == '-') |
|||
&& (buffer[i - 1] == 'e' || buffer[i - 1] == 'E') |
|||
) { |
|||
// allow + or - directly after exponent |
|||
continue; |
|||
} else if (IsCharNumeric(buffer[i])) { |
|||
has_digit_after_exponent = true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// if we have a decimal, there should be digit(s) after it |
|||
if (after_decimal && ! has_digit_after_decimal) { |
|||
return false; |
|||
} |
|||
|
|||
// if we have an exponent, there should be digit(s) after it |
|||
if (after_exponent && ! has_digit_after_exponent) { |
|||
return false; |
|||
} |
|||
|
|||
// we should have reached an exponent, decimal, or both |
|||
// otherwise this number can be handled by the int parser |
|||
return after_decimal || after_exponent; |
|||
} |
|||
|
|||
/** |
|||
* Checks whether the character at the beginning of the buffer |
|||
* is considered a valid 'end point' for an element, |
|||
* such as a colon (indicating the end of a key), |
|||
* a comma (indicating the end of an element), |
|||
* or the end of an object or array. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @param is_array Whether the decoder is processing an array. |
|||
* @return True if the first character in the buffer |
|||
* is a valid element end point, false otherwise. |
|||
*/ |
|||
stock bool json_is_at_end(const char[] buffer, bool is_array) |
|||
{ |
|||
return buffer[0] == ',' |
|||
|| (! is_array && (buffer[0] == ':' || buffer[0] == '}')) |
|||
|| (is_array && buffer[0] == ']'); |
|||
} |
|||
|
|||
/** |
|||
* @section Extract Contents from Buffer |
|||
*/ |
|||
|
|||
/** |
|||
* Moves the position until it reaches a non-whitespace |
|||
* character or the end of the buffer's maximum size. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @param max_size Maximum size of string buffer. |
|||
* @param pos Position to increment. |
|||
* @return True if pos has not reached the end |
|||
* of the buffer, false otherwise. |
|||
*/ |
|||
stock bool json_skip_whitespace(const char[] buffer, int max_size, int &pos) |
|||
{ |
|||
while (json_is_whitespace(buffer[pos]) && pos < max_size) { |
|||
pos += 1; |
|||
} |
|||
|
|||
return pos < max_size; |
|||
} |
|||
|
|||
/** |
|||
* Calculates the size of the buffer required to store the next |
|||
* JSON cell stored in the provided buffer at the provided position. |
|||
* This function is quite forgiving of malformed input and shouldn't be |
|||
* relied upon as proof that the input is valid. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @param max_size Maximum size of string buffer. |
|||
* @param pos Position to increment. |
|||
* @param is_array Whether the decoder is processing an array. |
|||
* @return The size of the buffer required to store the cell. |
|||
*/ |
|||
stock int json_extract_until_end_size( |
|||
const char[] buffer, |
|||
int max_size, |
|||
int pos, |
|||
bool is_array |
|||
) |
|||
{ |
|||
int length = 1; // for NULL terminator |
|||
|
|||
// while we haven't hit whitespace, an end point or the end of the buffer |
|||
while ( |
|||
! json_is_whitespace(buffer[pos]) |
|||
&& ! json_is_at_end(buffer[pos], is_array) |
|||
&& pos < max_size |
|||
) { |
|||
pos += 1; |
|||
length += 1; |
|||
} |
|||
|
|||
return length; |
|||
} |
|||
|
|||
/** |
|||
* Extracts a JSON cell from the buffer until a valid end point is reached. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @param max_size Maximum size of string buffer. |
|||
* @param pos Position to increment. |
|||
* @param output String buffer to store output. |
|||
* @param output_max_size Maximum size of output string buffer. |
|||
* @param is_array Whether the decoder is processing an array. |
|||
* @return True if pos has not reached the end |
|||
* of the buffer, false otherwise. |
|||
*/ |
|||
stock bool json_extract_until_end( |
|||
const char[] buffer, |
|||
int max_size, |
|||
int &pos, |
|||
char[] output, |
|||
int output_max_size, |
|||
bool is_array |
|||
) { |
|||
strcopy(output, output_max_size, ""); |
|||
|
|||
// set start to position of first character in cell |
|||
int start = pos; |
|||
|
|||
// while we haven't hit whitespace, an end point or the end of the buffer |
|||
while ( |
|||
! json_is_whitespace(buffer[pos]) |
|||
&& ! json_is_at_end(buffer[pos], is_array) |
|||
&& pos < max_size |
|||
) { |
|||
pos += 1; |
|||
} |
|||
|
|||
// set end to the current position |
|||
int end = pos; |
|||
|
|||
// skip any following whitespace |
|||
json_skip_whitespace(buffer, max_size, pos); |
|||
|
|||
// if we aren't at a valid endpoint, extraction has failed |
|||
if (! json_is_at_end(buffer[pos], is_array)) { |
|||
return false; |
|||
} |
|||
|
|||
// copy only from start with length end - start + NULL terminator |
|||
strcopy(output, end - start + 1, buffer[start]); |
|||
|
|||
return pos < max_size; |
|||
} |
|||
|
|||
/** |
|||
* Calculates the size of the buffer required to store the next |
|||
* JSON string stored in the provided buffer at the provided position. |
|||
* This function is quite forgiving of malformed input and shouldn't be |
|||
* relied upon as proof that the input is valid. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @param max_size Maximum size of string buffer. |
|||
* @param pos Position to increment. |
|||
* @return The size of the buffer required to store the string. |
|||
*/ |
|||
stock int json_extract_string_size(const char[] buffer, int max_size, int pos) |
|||
{ |
|||
int length = 1; // for NULL terminator |
|||
|
|||
// store initial quote |
|||
char quote = buffer[pos]; |
|||
|
|||
// increment past opening quote |
|||
pos += 1; |
|||
|
|||
// while we haven't hit the end of the buffer |
|||
int continuous_backslashes = 0; |
|||
while (pos < max_size) { |
|||
if (buffer[pos] == quote) { |
|||
// if we have an even number of preceding backslashes, |
|||
// the quote isn't escaped so this is the end of the string |
|||
if (continuous_backslashes % 2 == 0) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (buffer[pos] == '\\') { |
|||
continuous_backslashes += 1; |
|||
} else { |
|||
continuous_backslashes = 0; |
|||
} |
|||
|
|||
// pass over the character as it is part of the string |
|||
pos += 1; |
|||
length += 1; |
|||
} |
|||
|
|||
return length; |
|||
} |
|||
|
|||
/** |
|||
* Extracts a JSON string from the buffer until a valid end point is reached. |
|||
* |
|||
* @param buffer String buffer of data. |
|||
* @param max_size Maximum size of string buffer. |
|||
* @param pos Position to increment. |
|||
* @param output String buffer to store output. |
|||
* @param output_max_size Maximum size of output string buffer. |
|||
* @param is_array Whether the decoder is processing an array. |
|||
* @return True if pos has not reached the end |
|||
* of the buffer, false otherwise. |
|||
*/ |
|||
stock bool json_extract_string( |
|||
const char[] buffer, |
|||
int max_size, |
|||
int &pos, |
|||
char[] output, |
|||
int output_max_size, |
|||
bool is_array |
|||
) { |
|||
strcopy(output, output_max_size, ""); |
|||
|
|||
// store initial quote |
|||
char quote = buffer[pos]; |
|||
|
|||
// increment past opening quote |
|||
pos += 1; |
|||
|
|||
// set start to position of first character in string |
|||
int start = pos; |
|||
|
|||
// while we haven't hit the end of the buffer |
|||
int continuous_backslashes = 0; |
|||
while (pos < max_size) { |
|||
// check for unescaped control characters |
|||
if ( |
|||
buffer[pos] == '\b' |
|||
|| buffer[pos] == '\f' |
|||
|| buffer[pos] == '\n' |
|||
|| buffer[pos] == '\r' |
|||
|| buffer[pos] == '\t' |
|||
) { |
|||
return false; |
|||
} |
|||
|
|||
if (buffer[pos] == quote) { |
|||
// if we have an even number of preceding backslashes, |
|||
// the quote isn't escaped so this is the end of the string |
|||
if (continuous_backslashes % 2 == 0) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (buffer[pos] == '\\') { |
|||
continuous_backslashes += 1; |
|||
} else { |
|||
if (continuous_backslashes % 2 != 0) { |
|||
if (buffer[pos] == 'u') { |
|||
if (pos + 4 >= max_size) { |
|||
// less than 4 characters left in the buffer |
|||
return false; |
|||
} |
|||
|
|||
// ensure next 4 chars are hex and not a high surrogate |
|||
for (int i = 0; i < 4; i += 1) { |
|||
pos += 1; |
|||
|
|||
if (! json_char_is_hex(buffer[pos])) { |
|||
return false; |
|||
} |
|||
|
|||
if ( |
|||
i == 1 |
|||
&& buffer[pos - 1] == 'D' |
|||
&& buffer[pos] >= '8' |
|||
) { |
|||
// detected a high surrogate value |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
// jump back to the last hex char so it is safe to continue |
|||
pos -= 1; |
|||
} else if ( |
|||
buffer[pos] != '"' |
|||
&& buffer[pos] != '\'' |
|||
&& buffer[pos] != '/' |
|||
&& buffer[pos] != 'b' |
|||
&& buffer[pos] != 'f' |
|||
&& buffer[pos] != 'n' |
|||
&& buffer[pos] != 'r' |
|||
&& buffer[pos] != 't' |
|||
) { |
|||
// illegal escape detected |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
continuous_backslashes = 0; |
|||
} |
|||
|
|||
// pass over the character as it is part of the string |
|||
pos += 1; |
|||
} |
|||
|
|||
// set end to the current position |
|||
int end = pos; |
|||
|
|||
// increment past closing quote |
|||
pos += 1; |
|||
|
|||
// skip trailing whitespace |
|||
if (! json_skip_whitespace(buffer, max_size, pos)) { |
|||
return false; |
|||
} |
|||
|
|||
// if we haven't reached an ending character at the end of the cell, |
|||
// there is likely junk data not encapsulated by a string |
|||
if (! json_is_at_end(buffer[pos], is_array)) { |
|||
return false; |
|||
} |
|||
|
|||
// copy only from start with length end - start + NULL terminator |
|||
int length = end - start + 1; |
|||
strcopy( |
|||
output, |
|||
length > output_max_size ? output_max_size : length, |
|||
buffer[start] |
|||
); |
|||
|
|||
if (quote == '\'') { |
|||
ReplaceString(output, max_size, "\\'", "'"); |
|||
} |
|||
|
|||
json_unescape_string(output, max_size); |
|||
|
|||
return pos < max_size; |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
/** |
|||
* vim: set ts=4 : |
|||
* ============================================================================= |
|||
* sm-json |
|||
* A pure SourcePawn JSON encoder/decoder. |
|||
* https://github.com/clugg/sm-json |
|||
* |
|||
* sm-json (C)2022 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_helpers_errors_included |
|||
#endinput |
|||
#endif |
|||
#define _json_helpers_errors_included |
|||
|
|||
static char g_jsonLastError[1024] = ""; |
|||
|
|||
/** |
|||
* Stores the error provided as the 'last error' for later access. |
|||
* @internal |
|||
* |
|||
* @param error Error to store. |
|||
* @param ... Further arguments to pass to message formatter. |
|||
*/ |
|||
stock void json_set_last_error(const char[] error, any ...) |
|||
{ |
|||
VFormat(g_jsonLastError, sizeof(g_jsonLastError), error, 2); |
|||
} |
|||
|
|||
/** |
|||
* Retrieves the last error encountered and stores it in the buffer provided. |
|||
* |
|||
* @param buffer String buffer. |
|||
* @param max_size Maximum size of string buffer. |
|||
* @return True if the error was copied successfuly, |
|||
* false otherwise. |
|||
*/ |
|||
stock bool json_get_last_error(char[] buffer, int max_size) |
|||
{ |
|||
return strcopy(buffer, max_size, g_jsonLastError) > 0; |
|||
} |
|||
@ -0,0 +1,237 @@ |
|||
/** |
|||
* vim: set ts=4 : |
|||
* ============================================================================= |
|||
* sm-json |
|||
* A pure SourcePawn JSON encoder/decoder. |
|||
* https://github.com/clugg/sm-json |
|||
* |
|||
* sm-json (C)2022 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 _metastringmap_included |
|||
#endinput |
|||
#endif |
|||
#define _metastringmap_included |
|||
|
|||
#include <string> |
|||
#include <json/helpers/typedstringmap> |
|||
|
|||
/** |
|||
* A TypedStringMap which contains a nested `Data` `TypedStringMap` property. |
|||
* Standard methods and properties have been overridden to run against `Data`, |
|||
* but you can access the parent methods/properties using the `Meta` property. |
|||
*/ |
|||
methodmap MetaStringMap < TypedStringMap |
|||
{ |
|||
/** |
|||
* @section Properties |
|||
*/ |
|||
|
|||
/** |
|||
* Views the instance as its superclass to access overridden methods. |
|||
*/ |
|||
property TypedStringMap Meta |
|||
{ |
|||
public get() |
|||
{ |
|||
return view_as<TypedStringMap>(this); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets the nested stringmap where data is stored. |
|||
*/ |
|||
property TypedStringMap Data |
|||
{ |
|||
public get() |
|||
{ |
|||
return view_as<TypedStringMap>(this.Meta.GetHandle("data")); |
|||
} |
|||
|
|||
public set(TypedStringMap value) |
|||
{ |
|||
this.Meta.SetHandle("data", value); |
|||
} |
|||
} |
|||
|
|||
/** @see TypedStringMap.Length */ |
|||
property int Length { |
|||
public get() |
|||
{ |
|||
return this.Data.Length; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @section Getters |
|||
*/ |
|||
|
|||
/** @see StringMap.GetValue */ |
|||
public bool GetValue(const char[] key, any &value) |
|||
{ |
|||
return this.Data.GetValue(key, value); |
|||
} |
|||
|
|||
/** |
|||
* @see TypedStringMap.GetOptionalValue |
|||
* @internal |
|||
*/ |
|||
public any GetOptionalValue(const char[] key, any default_value = -1) |
|||
{ |
|||
return this.Data.GetOptionalValue(key, default_value); |
|||
} |
|||
|
|||
/** @see StringMap.GetString */ |
|||
public bool GetString( |
|||
const char[] key, |
|||
char[] value, |
|||
int max_size, |
|||
int &size = 0 |
|||
) { |
|||
return this.Data.GetString(key, value, max_size, size); |
|||
} |
|||
|
|||
/** @see TypedStringMap.GetInt */ |
|||
public int GetInt(const char[] key, int default_value = -1) |
|||
{ |
|||
return this.Data.GetInt(key, default_value); |
|||
} |
|||
|
|||
/** @see TypedStringMap.GetFloat */ |
|||
public float GetFloat(const char[] key, float default_value = -1.0) |
|||
{ |
|||
return this.Data.GetFloat(key, default_value); |
|||
} |
|||
|
|||
/** @see TypedStringMap.GetBool */ |
|||
public bool GetBool(const char[] key, bool default_value = false) |
|||
{ |
|||
return this.Data.GetBool(key, default_value); |
|||
} |
|||
|
|||
/** @see TypedStringMap.GetHandle */ |
|||
public Handle GetHandle(const char[] key, Handle default_value = null) |
|||
{ |
|||
return this.Data.GetHandle(key, default_value); |
|||
} |
|||
|
|||
/** |
|||
* @section Setters |
|||
*/ |
|||
|
|||
/** @see StringMap.SetValue */ |
|||
public bool SetValue(const char[] key, any value) |
|||
{ |
|||
return this.Data.SetValue(key, value); |
|||
} |
|||
|
|||
/** @see StringMap.SetString */ |
|||
public bool SetString(const char[] key, const char[] value) |
|||
{ |
|||
return this.Data.SetString(key, value); |
|||
} |
|||
|
|||
/** @see TypedStringMap.SetInt */ |
|||
public bool SetInt(const char[] key, int value) |
|||
{ |
|||
return this.Data.SetInt(key, value); |
|||
} |
|||
|
|||
/** @see TypedStringMap.SetFloat */ |
|||
public bool SetFloat(const char[] key, float value) |
|||
{ |
|||
return this.Data.SetFloat(key, value); |
|||
} |
|||
|
|||
/** @see TypedStringMap.SetBool */ |
|||
public bool SetBool(const char[] key, bool value) |
|||
{ |
|||
return this.Data.SetBool(key, value); |
|||
} |
|||
|
|||
/** @see TypedStringMap.SetHandle */ |
|||
public bool SetHandle(const char[] key, Handle value) |
|||
{ |
|||
return this.Data.SetHandle(key, value); |
|||
} |
|||
|
|||
/** @see StringMap.Remove */ |
|||
public bool Remove(const char[] key) |
|||
{ |
|||
return this.Data.Remove(key); |
|||
} |
|||
|
|||
/** |
|||
* @section Misc |
|||
*/ |
|||
|
|||
/** @see TypedStringMap.HasKey */ |
|||
public bool HasKey(const char[] key) |
|||
{ |
|||
return this.Data.HasKey(key); |
|||
} |
|||
|
|||
/** @see StringMap.Clear */ |
|||
public void Clear() |
|||
{ |
|||
TypedStringMap data = this.Data; |
|||
data.Clear(); |
|||
this.Meta.Clear(); |
|||
this.Data = data; |
|||
} |
|||
|
|||
/** |
|||
* Deletes the instance's data StringMap as well as the instance itself. |
|||
*/ |
|||
public void Cleanup() |
|||
{ |
|||
delete this.Data; |
|||
delete this; |
|||
} |
|||
|
|||
/** @see StringMap.Snapshot */ |
|||
public StringMapSnapshot Snapshot() |
|||
{ |
|||
return this.Data.Snapshot(); |
|||
} |
|||
|
|||
/** |
|||
* @section Constructor |
|||
*/ |
|||
|
|||
/** |
|||
* Creates a new MetaStringMap. |
|||
* |
|||
* @return A new MetaStringMap. |
|||
*/ |
|||
public MetaStringMap() |
|||
{ |
|||
MetaStringMap self = view_as<MetaStringMap>(new TypedStringMap()); |
|||
self.Data = new TypedStringMap(); |
|||
|
|||
return self; |
|||
} |
|||
}; |
|||
@ -0,0 +1,247 @@ |
|||
/** |
|||
* vim: set ts=4 : |
|||
* ============================================================================= |
|||
* sm-json |
|||
* A pure SourcePawn JSON encoder/decoder. |
|||
* https://github.com/clugg/sm-json |
|||
* |
|||
* sm-json (C)2022 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_helpers_string_included |
|||
#endinput |
|||
#endif |
|||
#define _json_helpers_string_included |
|||
|
|||
#include <json/helpers/unicode> |
|||
|
|||
/** |
|||
* Mapping characters to their escaped form. |
|||
*/ |
|||
char JSON_STRING_NORMAL[][] = { |
|||
"\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" |
|||
}; |
|||
char JSON_STRING_ESCAPED[][] = { |
|||
"\\\\", "\\\"", "\\/", "\\b", "\\f", "\\n", "\\r", "\\t" |
|||
}; |
|||
|
|||
/** |
|||
* Escapes a string in-place in a buffer. |
|||
* |
|||
* @param buffer String buffer. |
|||
* @param max_size Maximum size of string buffer. |
|||
*/ |
|||
stock void json_escape_string(char[] buffer, int max_size) |
|||
{ |
|||
for (int i = 0; i < sizeof(JSON_STRING_NORMAL); i += 1) { |
|||
ReplaceString( |
|||
buffer, |
|||
max_size, |
|||
JSON_STRING_NORMAL[i], |
|||
JSON_STRING_ESCAPED[i] |
|||
); |
|||
} |
|||
|
|||
int length = strlen(buffer) + 1; |
|||
for (int pos = 0; pos < length && pos < max_size; pos += 1) { |
|||
if (buffer[pos] < 0x80) { |
|||
// skip standard ascii values |
|||
continue; |
|||
} |
|||
|
|||
// consume the ascii bytes of the next utf8 character |
|||
int ascii_size; |
|||
int utf8 = json_ascii_to_utf8(buffer[pos], length - pos, ascii_size); |
|||
if (ascii_size <= 0) { |
|||
continue; |
|||
} |
|||
|
|||
// convert the utf8 value to escaped format |
|||
char escaped[7]; |
|||
FormatEx(escaped, sizeof(escaped), "\\u%04x", utf8); |
|||
|
|||
// duplicate the consumed byte array |
|||
ascii_size += 1; |
|||
char[] ascii = new char[ascii_size]; |
|||
for (int i = 0; i < ascii_size; i += 1) { |
|||
ascii[i] = buffer[pos + i]; |
|||
} |
|||
ascii[ascii_size - 1] = '\0'; |
|||
|
|||
// replace bytes with the escaped value |
|||
int replacements = ReplaceString(buffer, max_size, ascii, escaped); |
|||
|
|||
// calculate new string length based on replacements made |
|||
length -= replacements * ascii_size - 1; |
|||
length += replacements * sizeof(escaped) - 1; |
|||
|
|||
// skip to the last of the bytes we just replaced |
|||
pos += sizeof(escaped) - 2; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Unescapes a string in-place in a buffer. |
|||
* |
|||
* @param buffer String buffer. |
|||
* @param max_size Maximum size of string buffer. |
|||
*/ |
|||
stock void json_unescape_string(char[] buffer, int max_size) |
|||
{ |
|||
int length = strlen(buffer) + 1; |
|||
int continuous_backslashes = 0; |
|||
for (int pos = 0; pos < length && pos < max_size; pos += 1) { |
|||
if (buffer[pos] == '\\') { |
|||
continuous_backslashes += 1; |
|||
} else { |
|||
if (continuous_backslashes % 2 != 0 && buffer[pos] == 'u') { |
|||
// consume the entire escape starting at backslash |
|||
pos -= 1; |
|||
char escaped[7]; |
|||
for (int i = 0; i < 6; i += 1) { |
|||
escaped[i] = buffer[pos + i]; |
|||
} |
|||
escaped[sizeof(escaped) - 1] = '\0'; |
|||
|
|||
// convert the hex to decimal |
|||
int utf8 = StringToInt(escaped[2], 16); |
|||
|
|||
// convert the utf8 to ascii |
|||
int ascii_size = json_utf8_to_ascii_size(utf8) + 1; |
|||
char[] ascii = new char[ascii_size]; |
|||
int written = json_utf8_to_ascii(utf8, ascii, ascii_size); |
|||
|
|||
// replace the escaped value with ascii bytes |
|||
int replacements = ReplaceString( |
|||
buffer, |
|||
max_size, |
|||
escaped, |
|||
ascii, |
|||
false |
|||
); |
|||
|
|||
// calculate new string length based on replacements made |
|||
length -= replacements * sizeof(escaped) - 1; |
|||
length += replacements * written; |
|||
|
|||
// skip to the last of the bytes we just replaced |
|||
pos += written - 1; |
|||
} |
|||
|
|||
continuous_backslashes = 0; |
|||
} |
|||
} |
|||
|
|||
for (int i = 0; i < sizeof(JSON_STRING_NORMAL); i += 1) { |
|||
ReplaceString( |
|||
buffer, |
|||
max_size, |
|||
JSON_STRING_ESCAPED[i], |
|||
JSON_STRING_NORMAL[i] |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Checks whether the provided character is a valid hexadecimal character. |
|||
* |
|||
* @param c Character to check. |
|||
* @return True if c is a hexadecimal character, false otherwise. |
|||
*/ |
|||
stock bool json_char_is_hex(int c) |
|||
{ |
|||
return ( |
|||
(c >= '0' && c <= '9') |
|||
|| (c >= 'a' && c <= 'f') |
|||
|| (c >= 'A' && c <= 'F') |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Calculates the maximum buffer length required to |
|||
* store the JSON cell representation of a string. |
|||
* |
|||
* @param length The length of the string. |
|||
* @return Maximum buffer length. |
|||
*/ |
|||
stock int json_cell_string_size(const char[] input) |
|||
{ |
|||
int size = 3; // for outside quotes + NULL terminator |
|||
|
|||
bool foundEscapeTarget = false; |
|||
int length = strlen(input); |
|||
for (int pos = 0; pos < length; pos += 1) { |
|||
foundEscapeTarget = false; |
|||
for (int i = 0; i < sizeof(JSON_STRING_NORMAL); i += 1) { |
|||
if (input[pos] == JSON_STRING_NORMAL[i][0]) { |
|||
size += 2; |
|||
foundEscapeTarget = true; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (foundEscapeTarget) { |
|||
continue; |
|||
} |
|||
|
|||
if (input[pos] < 0x80) { |
|||
size += 1; |
|||
continue; |
|||
} |
|||
|
|||
// consume the ascii bytes of the next utf8 character |
|||
int ascii_size; |
|||
json_ascii_to_utf8(input[pos], length - pos, ascii_size); |
|||
if (ascii_size <= 0) { |
|||
continue; |
|||
} |
|||
|
|||
pos += ascii_size - 1; |
|||
size += 6; // for unicode escape is \uXXXX |
|||
} |
|||
|
|||
return size; |
|||
} |
|||
|
|||
/** |
|||
* Generates the JSON cell representation of a string. |
|||
* |
|||
* @param input Value to generate output for. |
|||
* @param output String buffer to store output. |
|||
* @param max_size Maximum size of string buffer. |
|||
*/ |
|||
stock void json_cell_string(const char[] input, char[] output, int max_size) |
|||
{ |
|||
// add input string to output, offset for start/end quotes |
|||
strcopy(output[1], max_size - 2, input); |
|||
|
|||
// escape the output |
|||
json_escape_string(output[1], max_size - 2); |
|||
|
|||
// surround output with quotations |
|||
output[0] = '"'; |
|||
StrCat(output, max_size, "\""); |
|||
} |
|||
@ -0,0 +1,222 @@ |
|||
/** |
|||
* vim: set ts=4 : |
|||
* ============================================================================= |
|||
* sm-json |
|||
* A pure SourcePawn JSON encoder/decoder. |
|||
* https://github.com/clugg/sm-json |
|||
* |
|||
* sm-json (C)2022 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 _typedstringmap_included |
|||
#endinput |
|||
#endif |
|||
#define _typedstringmap_included |
|||
|
|||
#include <string> |
|||
|
|||
/** @see StringMap.ContainsKey */ |
|||
#define TRIE_SUPPORTS_CONTAINSKEY SOURCEMOD_V_MAJOR >= 1 \ |
|||
&& SOURCEMOD_V_MINOR >= 11 \ |
|||
&& SOURCEMOD_V_REV >= 6646 |
|||
|
|||
/** |
|||
* A StringMap with typed getters and setters. |
|||
*/ |
|||
methodmap TypedStringMap < StringMap |
|||
{ |
|||
/** |
|||
* @section Properies |
|||
*/ |
|||
|
|||
/** @see StringMap.Size */ |
|||
property int Length { |
|||
public get() |
|||
{ |
|||
return this.Size; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @section Misc |
|||
*/ |
|||
|
|||
/** @see StringMap.ContainsKey */ |
|||
public bool HasKey(const char[] key) |
|||
{ |
|||
#if TRIE_SUPPORTS_CONTAINSKEY |
|||
return this.ContainsKey(key); |
|||
#else |
|||
int dummy_int; |
|||
char dummy_str[1]; |
|||
|
|||
return this.GetValue(key, dummy_int) |
|||
|| this.GetString(key, dummy_str, sizeof(dummy_str)); |
|||
#endif |
|||
} |
|||
|
|||
/** |
|||
* @section Getters |
|||
*/ |
|||
|
|||
// GetValue is implemented natively by StringMap |
|||
|
|||
/** |
|||
* Retrieves the value stored at a key. |
|||
* @internal |
|||
* |
|||
* @param key Key to retrieve value for. |
|||
* @param default_value Value to return if the key does not exist. |
|||
* @return Value stored at key. |
|||
*/ |
|||
public any GetOptionalValue(const char[] key, any default_value = -1) |
|||
{ |
|||
any value; |
|||
return this.GetValue(key, value) ? value : default_value; |
|||
} |
|||
|
|||
// GetString is implemented natively by StringMap |
|||
|
|||
/** |
|||
* Retrieves the int stored at a key. |
|||
* |
|||
* @param key Key to retrieve int value for. |
|||
* @param default_value Value to return if the key does not exist. |
|||
* @return Value stored at key. |
|||
*/ |
|||
public int GetInt(const char[] key, int default_value = -1) |
|||
{ |
|||
return view_as<int>(this.GetOptionalValue(key, default_value)); |
|||
} |
|||
|
|||
/** |
|||
* Retrieves the float stored at a key. |
|||
* |
|||
* @param key Key to retrieve float value for. |
|||
* @param default_value Value to return if the key does not exist. |
|||
* @return Value stored at key. |
|||
*/ |
|||
public float GetFloat(const char[] key, float default_value = -1.0) |
|||
{ |
|||
return view_as<float>(this.GetOptionalValue(key, default_value)); |
|||
} |
|||
|
|||
/** |
|||
* Retrieves the bool stored at a key. |
|||
* |
|||
* @param key Key to retrieve bool value for. |
|||
* @param default_value Value to return if the key does not exist. |
|||
* @return Value stored at key. |
|||
*/ |
|||
public bool GetBool(const char[] key, bool default_value = false) |
|||
{ |
|||
return view_as<bool>(this.GetOptionalValue(key, default_value)); |
|||
} |
|||
|
|||
/** |
|||
* Retrieves the handle stored at a key. |
|||
* |
|||
* @param key Key to retrieve handle value for. |
|||
* @param default_value Value to return if the key does not exist. |
|||
* @return Value stored at key. |
|||
*/ |
|||
public Handle GetHandle( |
|||
const char[] key, |
|||
Handle default_value = null |
|||
) { |
|||
return view_as<Handle>(this.GetOptionalValue(key, default_value)); |
|||
} |
|||
|
|||
/** |
|||
* @section Setters |
|||
*/ |
|||
|
|||
// SetValue is implemented natively by StringMap |
|||
|
|||
// SetString is implemented natively by StringMap |
|||
|
|||
/** |
|||
* Sets the int stored at a key. |
|||
* |
|||
* @param key Key to set to int value. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetInt(const char[] key, int value) |
|||
{ |
|||
return this.SetValue(key, value); |
|||
} |
|||
|
|||
/** |
|||
* Sets the float stored at a key. |
|||
* |
|||
* @param key Key to set to float value. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetFloat(const char[] key, float value) |
|||
{ |
|||
return this.SetValue(key, value); |
|||
} |
|||
|
|||
/** |
|||
* Sets the bool stored at a key. |
|||
* |
|||
* @param key Key to set to bool value. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetBool(const char[] key, bool value) |
|||
{ |
|||
return this.SetValue(key, value); |
|||
} |
|||
|
|||
/** |
|||
* Sets the handle stored at a key. |
|||
* |
|||
* @param key Key to set to object value. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetHandle(const char[] key, Handle value) |
|||
{ |
|||
return this.SetValue(key, value); |
|||
} |
|||
|
|||
/** |
|||
* @section Constructor |
|||
*/ |
|||
|
|||
/** |
|||
* Creates a new TypedStringMap. |
|||
* |
|||
* @return A new TypedStringMap. |
|||
*/ |
|||
public TypedStringMap() |
|||
{ |
|||
return view_as<TypedStringMap>(CreateTrie()); |
|||
} |
|||
}; |
|||
@ -0,0 +1,185 @@ |
|||
/** |
|||
* vim: set ts=4 : |
|||
* ============================================================================= |
|||
* sm-json |
|||
* A pure SourcePawn JSON encoder/decoder. |
|||
* https://github.com/clugg/sm-json |
|||
* |
|||
* sm-json (C)2022 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_helpers_unicode_included |
|||
#endinput |
|||
#endif |
|||
#define _json_helpers_unicode_included |
|||
|
|||
// most of the code here is adapted from https://dev.w3.org/XML/encoding.c |
|||
|
|||
/** |
|||
* Calculates how many bytes will be required to store the ASCII |
|||
* representation of a UTF-8 character. |
|||
* |
|||
* @param c The UTF-8 character. |
|||
* @return The number of bytes required, or -1 if c is invalid. |
|||
*/ |
|||
stock int json_utf8_to_ascii_size(int c) |
|||
{ |
|||
if (c < 0 || c > 0x10FFFF) { |
|||
return -1; |
|||
} |
|||
|
|||
if (c < 0x80) { |
|||
return 1; |
|||
} else if (c < 0x800) { |
|||
return 2; |
|||
} else if (c < 0x10000) { |
|||
if (c >= 0xD800 && c <= 0xDFFF) { |
|||
// high surrogate |
|||
return -1; |
|||
} |
|||
|
|||
return 3; |
|||
} |
|||
|
|||
return 4; |
|||
} |
|||
|
|||
/** |
|||
* Converts a UTF-8 character to its ASCII representation. |
|||
* |
|||
* @param c The UTF-8 character. |
|||
* @param output String buffer to store output. |
|||
* @param max_size Maximum size of string buffer. |
|||
* @return The number of bytes written, or -1 if c is invalid. |
|||
*/ |
|||
stock int json_utf8_to_ascii(int c, char[] output, int max_size) |
|||
{ |
|||
if (max_size < 1) { |
|||
return 0; |
|||
} |
|||
|
|||
if (c < 0 || c > 0x10FFFF) { |
|||
return -1; |
|||
} |
|||
|
|||
int size = 0; |
|||
if (c < 0x80) { |
|||
size = 1; |
|||
output[0] = c; |
|||
} else if (c < 0x800) { |
|||
size = 2; |
|||
output[0] = ((c >> 6) & 0x1F) | 0xC0; |
|||
} else if (c < 0x10000) { |
|||
if (c >= 0xD800 && c <= 0xDFFF) { |
|||
// high surrogate |
|||
return -1; |
|||
} |
|||
|
|||
size = 3; |
|||
output[0] = ((c >> 12) & 0x0F) | 0xE0; |
|||
} else { |
|||
size = 4; |
|||
output[0] = ((c >> 18) & 0x07) | 0xF0; |
|||
} |
|||
|
|||
if (size >= max_size) { |
|||
return -1; |
|||
} |
|||
|
|||
// first byte has already been calculated, calculate the rest |
|||
int i; |
|||
for (i = 1; i < size; i += 1) { |
|||
output[i] = ((c >> ((size - i - 1) * 6)) & 0x3F) | 0x80; |
|||
} |
|||
|
|||
return i; |
|||
} |
|||
|
|||
/** |
|||
* Converts bytes to their UTF-8 int representation. |
|||
* |
|||
* @param ascii The ascii/bytes to convert. |
|||
* @param max_size Maximum size of ascii. |
|||
* @return The UTF-8 int representation. |
|||
*/ |
|||
stock int json_ascii_to_utf8(const char[] ascii, int max_size, int &size) |
|||
{ |
|||
size = 0; |
|||
if (max_size < 1) { |
|||
return -1; |
|||
} |
|||
|
|||
int c = 0; |
|||
if ((ascii[0] & 0x80) != 0) { |
|||
if (max_size < 2) { |
|||
return -1; |
|||
} |
|||
|
|||
if ((ascii[1] & 0xC0) != 0x80) { |
|||
return -1; |
|||
} |
|||
|
|||
if ((ascii[0] & 0xE0) == 0xE0) { |
|||
if (max_size < 3) { |
|||
return -1; |
|||
} |
|||
|
|||
if ((ascii[2] & 0xC0) != 0x80) { |
|||
return -1; |
|||
} |
|||
|
|||
if ((ascii[0] & 0xF0) == 0xF0) { |
|||
if (max_size < 4) { |
|||
return -1; |
|||
} |
|||
|
|||
if ((ascii[0] & 0xF8) != 0xF0 || (ascii[3] & 0xC0) != 0x80) { |
|||
return -1; |
|||
} |
|||
|
|||
size = 4; |
|||
c = (ascii[0] & 0x07) << 18; |
|||
} else { |
|||
size = 3; |
|||
c = (ascii[0] & 0x0F) << 12; |
|||
} |
|||
} else { |
|||
size = 2; |
|||
c = (ascii[0] & 0x1F) << 6; |
|||
} |
|||
} else { |
|||
size = 1; |
|||
c = ascii[0]; |
|||
} |
|||
|
|||
// first byte has already been calculated, calculate the rest |
|||
int i; |
|||
for (i = 1; i < size; i += 1) { |
|||
c |= (ascii[i] & 0x3F) << ((size - i - 1) * 6); |
|||
} |
|||
|
|||
return c; |
|||
} |
|||
@ -0,0 +1,724 @@ |
|||
/** |
|||
* vim: set ts=4 : |
|||
* ============================================================================= |
|||
* sm-json |
|||
* A pure SourcePawn JSON encoder/decoder. |
|||
* https://github.com/clugg/sm-json |
|||
* |
|||
* sm-json (C)2022 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_object_included |
|||
#endinput |
|||
#endif |
|||
#define _json_object_included |
|||
|
|||
#include <string> |
|||
#include <json/definitions> |
|||
#include <json/helpers/errors> |
|||
#include <json/helpers/metastringmap> |
|||
|
|||
methodmap JSON_Object < MetaStringMap |
|||
{ |
|||
/** |
|||
* @section Properties |
|||
*/ |
|||
|
|||
/** |
|||
* Views the instance as its superclass to access overridden methods. |
|||
*/ |
|||
property MetaStringMap Super |
|||
{ |
|||
public get() |
|||
{ |
|||
return view_as<MetaStringMap>(this); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Whether the current object is an array. |
|||
*/ |
|||
property bool IsArray { |
|||
public get() |
|||
{ |
|||
return this.Meta.GetBool(JSON_ARRAY_KEY); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @section Iteration Helpers |
|||
*/ |
|||
|
|||
/** |
|||
* @section Metadata Getters |
|||
*/ |
|||
|
|||
/** |
|||
* Gets the requested meta info for a key. |
|||
* @internal |
|||
* |
|||
* @param key Key to get meta info for. |
|||
* @param meta Meta info to get. |
|||
* @param default_value Value to return if meta does not exist. |
|||
* @return The meta value. |
|||
*/ |
|||
public any GetMeta( |
|||
const char[] key, |
|||
JSONMetaInfo meta, |
|||
any default_value |
|||
) { |
|||
int max_size = json_meta_key_length(key); |
|||
char[] meta_key = new char[max_size]; |
|||
json_format_meta_key(meta_key, max_size, key, meta); |
|||
|
|||
return this.Meta.GetOptionalValue(meta_key, default_value); |
|||
} |
|||
|
|||
/** |
|||
* Gets the cell type stored at a key. |
|||
* |
|||
* @param key Key to get value type for. |
|||
* @return Value type for key provided, |
|||
* or JSON_Type_Invalid if it does not exist. |
|||
*/ |
|||
public JSONCellType GetType(const char[] key) |
|||
{ |
|||
return view_as<JSONCellType>( |
|||
this.GetMeta(key, JSON_Meta_Type, JSON_Type_Invalid) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Gets the size of the string stored at a key. |
|||
* |
|||
* @param key Key to get buffer size for. |
|||
* @return Buffer size for string at key provided, |
|||
* or -1 if it is not a string/does not exist. |
|||
*/ |
|||
public int GetSize(const char[] key) |
|||
{ |
|||
return view_as<int>(this.GetMeta(key, JSON_Meta_Size, -1)); |
|||
} |
|||
|
|||
/** |
|||
* Gets whether the key should be hidden from encoding. |
|||
* |
|||
* @param key Key to get hidden state for. |
|||
* @return Whether or not the key should be hidden. |
|||
*/ |
|||
public bool GetHidden(const char[] key) |
|||
{ |
|||
return view_as<bool>(this.GetMeta(key, JSON_Meta_Hidden, false)); |
|||
} |
|||
|
|||
/** |
|||
* Gets the index of a key. |
|||
* |
|||
* @param key Key to get index of. |
|||
* @return Index of the key provided, or -1 if it does not exist. |
|||
*/ |
|||
public int GetIndex(const char[] key) |
|||
{ |
|||
return view_as<int>(this.GetMeta(key, JSON_Meta_Index, -1)); |
|||
} |
|||
|
|||
/** |
|||
* Gets the key stored at an index. |
|||
* If an array, will convert the index to its string value. |
|||
* If an array, will return false if the index is not between [0, length]. |
|||
* |
|||
* @param index Index of key. |
|||
* @param value Buffer to store key at. |
|||
* @param max_size Maximum size of value buffer. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool GetKey(int index, char[] value, int max_size) |
|||
{ |
|||
char[] index_key = new char[JSON_INT_BUFFER_SIZE]; |
|||
if (IntToString(index, index_key, JSON_INT_BUFFER_SIZE) == 0) { |
|||
return false; |
|||
} |
|||
|
|||
if (this.IsArray) { |
|||
// allow access of one past last index for intermediary operations |
|||
if (index < 0 || index > this.Length) { |
|||
return false; |
|||
} |
|||
|
|||
strcopy(value, max_size, index_key); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
return this.Meta.GetString(index_key, value, max_size); |
|||
} |
|||
|
|||
/** |
|||
* Returns the buffer size required to store the key at the specified index. |
|||
* |
|||
* @param index Index of key. |
|||
* @return Buffer size required to store key. |
|||
*/ |
|||
public int GetKeySize(int index) |
|||
{ |
|||
if (this.IsArray) { |
|||
return JSON_INT_BUFFER_SIZE; |
|||
} |
|||
|
|||
int max_size = JSON_INT_BUFFER_SIZE + 4; |
|||
char[] index_size_key = new char[max_size]; |
|||
FormatEx(index_size_key, max_size, "%d:len", index); |
|||
|
|||
return this.Meta.GetInt(index_size_key); |
|||
} |
|||
|
|||
/** |
|||
* @section Metadata Setters |
|||
*/ |
|||
|
|||
/** |
|||
* Sets meta info on a key. |
|||
* @internal |
|||
* |
|||
* @param key Key to set meta info for. |
|||
* @param meta Meta info to set. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetMeta(const char[] key, JSONMetaInfo meta, any value) |
|||
{ |
|||
int max_size = json_meta_key_length(key); |
|||
char[] meta_key = new char[max_size]; |
|||
json_format_meta_key(meta_key, max_size, key, meta); |
|||
|
|||
return this.Meta.SetValue(meta_key, value); |
|||
} |
|||
|
|||
/** |
|||
* Removes meta info from a key. |
|||
* @internal |
|||
* |
|||
* @param key Key to remove meta info from. |
|||
* @param meta Meta info to remove. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool RemoveMeta(const char[] key, JSONMetaInfo meta) |
|||
{ |
|||
int max_size = json_meta_key_length(key); |
|||
char[] meta_key = new char[max_size]; |
|||
json_format_meta_key(meta_key, max_size, key, meta); |
|||
|
|||
return this.Meta.Remove(meta_key); |
|||
} |
|||
|
|||
/** |
|||
* Sets whether the key should be hidden from encoding. |
|||
* |
|||
* @param key Key to set hidden state for. |
|||
* @param hidden Whether or not the key should be hidden. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetHidden(const char[] key, bool hidden) |
|||
{ |
|||
return hidden |
|||
? this.SetMeta(key, JSON_Meta_Hidden, hidden) |
|||
: this.RemoveMeta(key, JSON_Meta_Hidden); |
|||
} |
|||
|
|||
/** |
|||
* Tracks a key, setting it's type and index where necessary. |
|||
* @internal |
|||
* |
|||
* @param key Key to track. If the key already |
|||
* exists, it's index will not be changed. |
|||
* @param type Type to set key to. If a valid type is |
|||
* provided, the key's type will be updated. |
|||
* @param index Index to set key to. If the index is not |
|||
* provided (-1), the object length will be used. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool TrackKey( |
|||
const char[] key, |
|||
JSONCellType type = JSON_Type_Invalid, |
|||
int index = -1 |
|||
) { |
|||
// track type if provided |
|||
if (type != JSON_Type_Invalid) { |
|||
this.SetMeta(key, JSON_Meta_Type, type); |
|||
} |
|||
|
|||
if (this.IsArray) { |
|||
return true; |
|||
} |
|||
|
|||
if (index == -1) { |
|||
index = this.Length; |
|||
|
|||
// skip tracking index if we're pushing to end & key already exists |
|||
if (this.HasKey(key)) { |
|||
if (type != JSON_Type_Invalid && type != JSON_Type_String) { |
|||
// remove any existing size |
|||
this.RemoveMeta(key, JSON_Meta_Size); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
char[] index_key = new char[JSON_INT_BUFFER_SIZE]; |
|||
IntToString(index, index_key, JSON_INT_BUFFER_SIZE); |
|||
|
|||
int max_size = JSON_INT_BUFFER_SIZE + 8; |
|||
char[] index_size_key = new char[max_size]; |
|||
FormatEx(index_size_key, max_size, "%d:len", index); |
|||
|
|||
return this.Meta.SetString(index_key, key) |
|||
&& this.Meta.SetInt(index_size_key, strlen(key) + 1) |
|||
&& this.SetMeta(key, JSON_Meta_Index, index); |
|||
} |
|||
|
|||
/** |
|||
* Untracks a key, cleaning up all it's meta and indexing data. |
|||
* @internal |
|||
* |
|||
* @param key Key to untrack. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool UntrackKey(const char[] key) |
|||
{ |
|||
int index = this.GetIndex(key); |
|||
|
|||
for (int i = 0; i < sizeof(JSON_ALL_METADATA); i += 1) { |
|||
this.RemoveMeta(key, JSON_ALL_METADATA[i]); |
|||
} |
|||
|
|||
if (this.IsArray) { |
|||
return true; |
|||
} |
|||
|
|||
if (index == -1) { |
|||
return false; |
|||
} |
|||
|
|||
char[] index_key = new char[JSON_INT_BUFFER_SIZE]; |
|||
IntToString(index, index_key, JSON_INT_BUFFER_SIZE); |
|||
|
|||
int max_size = JSON_INT_BUFFER_SIZE + 8; |
|||
char[] index_size_key = new char[max_size]; |
|||
FormatEx(index_size_key, max_size, "%d:len", index); |
|||
|
|||
if (! this.Meta.Remove(index_key) || ! this.Meta.Remove(index_size_key)) { |
|||
return false; |
|||
} |
|||
|
|||
int length = this.Length; |
|||
int last_index = length - 1; |
|||
if (index < last_index) { |
|||
for (int i = index + 1; i < length; i += 1) { |
|||
int new_key_size = this.GetKeySize(i); |
|||
char[] new_key = new char[new_key_size]; |
|||
this.GetKey(i, new_key, new_key_size); |
|||
|
|||
this.TrackKey(new_key, JSON_Type_Invalid, i - 1); |
|||
} |
|||
|
|||
IntToString(last_index, index_key, JSON_INT_BUFFER_SIZE); |
|||
FormatEx(index_size_key, max_size, "%d:len", last_index); |
|||
|
|||
this.Meta.Remove(index_key); |
|||
this.Meta.Remove(index_size_key); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* @section Getters |
|||
*/ |
|||
|
|||
#if SM_INT64_SUPPORTED |
|||
/** |
|||
* Retrieves the int64 stored at a key. |
|||
* |
|||
* @param key Key to retrieve int64 value for. |
|||
* @param value Int buffer to store output. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool GetInt64(const char[] key, int value[2]) |
|||
{ |
|||
return this.Data.GetArray(key, value, 2); |
|||
} |
|||
#endif |
|||
|
|||
/** |
|||
* Retrieves the JSON object stored at a key. |
|||
* |
|||
* @param key Key to retrieve object value for. |
|||
* @param default_value Value to return if the key does not exist. |
|||
* @return Value stored at key. |
|||
*/ |
|||
public JSON_Object GetObject( |
|||
const char[] key, |
|||
JSON_Object default_value = null |
|||
) { |
|||
return view_as<JSON_Object>(this.GetHandle(key, default_value)); |
|||
} |
|||
|
|||
/** |
|||
* @section Setters |
|||
*/ |
|||
|
|||
/** |
|||
* Sets the string stored at a key. |
|||
* |
|||
* @param key Key to set to string value. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetString(const char[] key, const char[] value) |
|||
{ |
|||
return this.TrackKey(key, JSON_Type_String) |
|||
&& this.Super.SetString(key, value) |
|||
&& this.SetMeta(key, JSON_Meta_Size, strlen(value) + 1); |
|||
} |
|||
|
|||
/** |
|||
* Sets the int stored at a key. |
|||
* |
|||
* @param key Key to set to int value. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetInt(const char[] key, int value) |
|||
{ |
|||
return this.TrackKey(key, JSON_Type_Int) |
|||
&& this.Super.SetInt(key, value); |
|||
} |
|||
|
|||
#if SM_INT64_SUPPORTED |
|||
/** |
|||
* Sets the int64 stored at a key. |
|||
* |
|||
* @param key Key to set to int64 value. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetInt64(const char[] key, int value[2]) |
|||
{ |
|||
return this.TrackKey(key, JSON_Type_Int64) |
|||
&& this.Data.SetArray(key, value, 2); |
|||
} |
|||
#endif |
|||
|
|||
/** |
|||
* Sets the float stored at a key. |
|||
* |
|||
* @param key Key to set to float value. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetFloat(const char[] key, float value) |
|||
{ |
|||
return this.TrackKey(key, JSON_Type_Float) |
|||
&& this.Super.SetFloat(key, value); |
|||
} |
|||
|
|||
/** |
|||
* Sets the bool stored at a key. |
|||
* |
|||
* @param key Key to set to bool value. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetBool(const char[] key, bool value) |
|||
{ |
|||
return this.TrackKey(key, JSON_Type_Bool) |
|||
&& this.Super.SetBool(key, value); |
|||
} |
|||
|
|||
/** |
|||
* Sets the JSON object stored at a key. |
|||
* |
|||
* @param key Key to set to object value. |
|||
* @param value Value to set. |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool SetObject(const char[] key, JSON_Object value) |
|||
{ |
|||
return this.TrackKey(key, JSON_Type_Object) |
|||
&& this.Super.SetHandle(key, value); |
|||
} |
|||
|
|||
/** |
|||
* @section Misc |
|||
*/ |
|||
|
|||
/** |
|||
* Removes an item from the object by key. |
|||
* |
|||
* @param key Key of object to remove. |
|||
* @return True on success, false if the value was never set. |
|||
*/ |
|||
public bool Remove(const char[] key) |
|||
{ |
|||
return this.UntrackKey(key) && this.Super.Remove(key); |
|||
} |
|||
|
|||
/** |
|||
* Renames the key of an existing item in the object. |
|||
* |
|||
* @param from Existing key to rename. |
|||
* @param to New key. |
|||
* @param replace Should the 'to' key should be replaced if it exists? |
|||
* @return True on success, false otherwise. |
|||
*/ |
|||
public bool Rename( |
|||
const char[] from, |
|||
const char[] to, |
|||
bool replace = true |
|||
) { |
|||
JSONCellType type = this.GetType(from); |
|||
if (type == JSON_Type_Invalid) { |
|||
return false; |
|||
} |
|||
|
|||
if (StrEqual(from, to, true)) { |
|||
return true; |
|||
} |
|||
|
|||
bool toExists = this.HasKey(to); |
|||
if (toExists) { |
|||
if (! replace) { |
|||
return false; |
|||
} |
|||
|
|||
this.Remove(to); |
|||
} |
|||
|
|||
switch (type) { |
|||
case JSON_Type_String: { |
|||
int length = this.GetSize(from); |
|||
char[] value = new char[length]; |
|||
this.GetString(from, value, length); |
|||
this.SetString(to, value); |
|||
} |
|||
case JSON_Type_Int: { |
|||
this.SetInt(to, this.GetInt(from)); |
|||
} |
|||
#if SM_INT64_SUPPORTED |
|||
case JSON_Type_Int64: { |
|||
int value[2]; |
|||
this.GetInt64(from, value); |
|||
this.SetInt64(to, value); |
|||
} |
|||
#endif |
|||
case JSON_Type_Float: { |
|||
this.SetFloat(to, this.GetFloat(from)); |
|||
} |
|||
case JSON_Type_Bool: { |
|||
this.SetBool(to, this.GetBool(from)); |
|||
} |
|||
case JSON_Type_Object: { |
|||
this.SetObject(to, this.GetObject(from)); |
|||
} |
|||
} |
|||
|
|||
this.SetHidden(to, this.GetHidden(from)); |
|||
|
|||
this.Remove(from); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Merges in the entries from the specified object, |
|||
* optionally replacing existing entries with the same key. |
|||
* |
|||
* @param from Object to merge entries from. |
|||
* @param options Bitwise combination of `JSON_MERGE_*` options. |
|||
* @return True on success, false otherwise. |
|||
* @error If the object being merged is an array, |
|||
* an error will be thrown. |
|||
*/ |
|||
public bool Merge(JSON_Object from, int options = JSON_MERGE_REPLACE) |
|||
{ |
|||
if (this.IsArray || from.IsArray) { |
|||
json_set_last_error("attempted to merge using an array"); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
bool replace = (options & JSON_MERGE_REPLACE) > 0; |
|||
bool autocleanup = (options & JSON_MERGE_CLEANUP) > 0; |
|||
|
|||
int json_size = from.Length; |
|||
int key_length = 0; |
|||
for (int i = 0; i < json_size; i += 1) { |
|||
key_length = from.GetKeySize(i); |
|||
char[] key = new char[key_length]; |
|||
from.GetKey(i, key, key_length); |
|||
|
|||
// skip already existing keys if we aren't in replace mode |
|||
bool key_already_exists = this.HasKey(key); |
|||
if (! replace && key_already_exists) { |
|||
continue; |
|||
} |
|||
|
|||
JSONCellType type = from.GetType(key); |
|||
// skip keys of unknown type |
|||
if (type == JSON_Type_Invalid) { |
|||
continue; |
|||
} |
|||
|
|||
// merge value onto structure |
|||
switch (type) { |
|||
case JSON_Type_String: { |
|||
int length = from.GetSize(key); |
|||
char[] value = new char[length]; |
|||
from.GetString(key, value, length); |
|||
|
|||
this.SetString(key, value); |
|||
} |
|||
case JSON_Type_Int: { |
|||
this.SetInt(key, from.GetInt(key)); |
|||
} |
|||
#if SM_INT64_SUPPORTED |
|||
case JSON_Type_Int64: { |
|||
int value[2]; |
|||
from.GetInt64(key, value); |
|||
this.SetInt64(key, value); |
|||
} |
|||
#endif |
|||
case JSON_Type_Float: { |
|||
this.SetFloat(key, from.GetFloat(key)); |
|||
} |
|||
case JSON_Type_Bool: { |
|||
this.SetBool(key, from.GetBool(key)); |
|||
} |
|||
case JSON_Type_Object: { |
|||
JSON_Object value = from.GetObject(key); |
|||
|
|||
if (autocleanup && key_already_exists) { |
|||
JSON_Object existing = this.GetObject(key); |
|||
if (existing != value) { |
|||
json_cleanup_and_delete(existing); |
|||
} |
|||
} |
|||
|
|||
this.SetObject(key, value); |
|||
} |
|||
} |
|||
|
|||
this.SetHidden(key, from.GetHidden(key)); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* @section json.inc Aliases |
|||
*/ |
|||
|
|||
/** |
|||
* Makes a global call with this |
|||
* instance passed as the object. |
|||
* |
|||
* @see json_encode_size |
|||
*/ |
|||
public int EncodeSize(int options = JSON_NONE) |
|||
{ |
|||
return json_encode_size(this, options); |
|||
} |
|||
|
|||
/** |
|||
* Makes a global call with this |
|||
* instance passed as the object. |
|||
* |
|||
* @see json_encode |
|||
*/ |
|||
public void Encode(char[] output, int max_size, int options = JSON_NONE) |
|||
{ |
|||
json_encode(this, output, max_size, options); |
|||
} |
|||
|
|||
/** |
|||
* Makes a global call with this |
|||
* instance passed as the object. |
|||
* |
|||
* @see json_write_to_file |
|||
*/ |
|||
public bool WriteToFile(const char[] path, int options = JSON_NONE) |
|||
{ |
|||
return json_write_to_file(this, path, options); |
|||
} |
|||
|
|||
/** |
|||
* Makes a global call with this |
|||
* instance passed as the object. |
|||
* |
|||
* @see json_copy_deep |
|||
*/ |
|||
public JSON_Object ShallowCopy() |
|||
{ |
|||
return json_copy_shallow(this); |
|||
} |
|||
|
|||
/** |
|||
* Makes a global call with this |
|||
* instance passed as the object. |
|||
* |
|||
* @see json_copy_deep |
|||
*/ |
|||
public JSON_Object DeepCopy() |
|||
{ |
|||
return json_copy_deep(this); |
|||
} |
|||
|
|||
/** |
|||
* Makes a global call with this |
|||
* instance passed as the object. |
|||
* |
|||
* @see json_cleanup |
|||
*/ |
|||
public void Cleanup() |
|||
{ |
|||
json_cleanup(this); |
|||
} |
|||
|
|||
/** |
|||
* @section Constructor |
|||
*/ |
|||
|
|||
/** |
|||
* Creates a new JSON_Object. |
|||
* |
|||
* @return A new JSON_Object. |
|||
*/ |
|||
public JSON_Object() |
|||
{ |
|||
return view_as<JSON_Object>(new MetaStringMap()); |
|||
} |
|||
}; |
|||
@ -0,0 +1,26 @@ |
|||
#if defined _ripext_included_ |
|||
#endinput |
|||
#endif |
|||
#define _ripext_included_ |
|||
|
|||
#include <ripext/json> |
|||
#include <ripext/http> |
|||
|
|||
/** |
|||
* Do not edit below this line! |
|||
*/ |
|||
public Extension __ext_rip = |
|||
{ |
|||
name = "REST in Pawn", |
|||
file = "rip.ext", |
|||
#if defined AUTOLOAD_EXTENSIONS |
|||
autoload = 1, |
|||
#else |
|||
autoload = 0, |
|||
#endif |
|||
#if defined REQUIRE_EXTENSIONS |
|||
required = 1, |
|||
#else |
|||
required = 0, |
|||
#endif |
|||
}; |
|||
@ -0,0 +1,359 @@ |
|||
enum HTTPStatus |
|||
{ |
|||
HTTPStatus_Invalid = 0, |
|||
|
|||
// 1xx Informational |
|||
HTTPStatus_Continue = 100, |
|||
HTTPStatus_SwitchingProtocols = 101, |
|||
|
|||
// 2xx Success |
|||
HTTPStatus_OK = 200, |
|||
HTTPStatus_Created = 201, |
|||
HTTPStatus_Accepted = 202, |
|||
HTTPStatus_NonAuthoritativeInformation = 203, |
|||
HTTPStatus_NoContent = 204, |
|||
HTTPStatus_ResetContent = 205, |
|||
HTTPStatus_PartialContent = 206, |
|||
|
|||
// 3xx Redirection |
|||
HTTPStatus_MultipleChoices = 300, |
|||
HTTPStatus_MovedPermanently = 301, |
|||
HTTPStatus_Found = 302, |
|||
HTTPStatus_SeeOther = 303, |
|||
HTTPStatus_NotModified = 304, |
|||
HTTPStatus_UseProxy = 305, |
|||
HTTPStatus_TemporaryRedirect = 307, |
|||
HTTPStatus_PermanentRedirect = 308, |
|||
|
|||
// 4xx Client Error |
|||
HTTPStatus_BadRequest = 400, |
|||
HTTPStatus_Unauthorized = 401, |
|||
HTTPStatus_PaymentRequired = 402, |
|||
HTTPStatus_Forbidden = 403, |
|||
HTTPStatus_NotFound = 404, |
|||
HTTPStatus_MethodNotAllowed = 405, |
|||
HTTPStatus_NotAcceptable = 406, |
|||
HTTPStatus_ProxyAuthenticationRequired = 407, |
|||
HTTPStatus_RequestTimeout = 408, |
|||
HTTPStatus_Conflict = 409, |
|||
HTTPStatus_Gone = 410, |
|||
HTTPStatus_LengthRequired = 411, |
|||
HTTPStatus_PreconditionFailed = 412, |
|||
HTTPStatus_RequestEntityTooLarge = 413, |
|||
HTTPStatus_RequestURITooLong = 414, |
|||
HTTPStatus_UnsupportedMediaType = 415, |
|||
HTTPStatus_RequestedRangeNotSatisfiable = 416, |
|||
HTTPStatus_ExpectationFailed = 417, |
|||
HTTPStatus_MisdirectedRequest = 421, |
|||
HTTPStatus_TooEarly = 425, |
|||
HTTPStatus_UpgradeRequired = 426, |
|||
HTTPStatus_PreconditionRequired = 428, |
|||
HTTPStatus_TooManyRequests = 429, |
|||
HTTPStatus_RequestHeaderFieldsTooLarge = 431, |
|||
HTTPStatus_UnavailableForLegalReasons = 451, |
|||
|
|||
// 5xx Server Error |
|||
HTTPStatus_InternalServerError = 500, |
|||
HTTPStatus_NotImplemented = 501, |
|||
HTTPStatus_BadGateway = 502, |
|||
HTTPStatus_ServiceUnavailable = 503, |
|||
HTTPStatus_GatewayTimeout = 504, |
|||
HTTPStatus_HTTPVersionNotSupported = 505, |
|||
HTTPStatus_VariantAlsoNegotiates = 506, |
|||
HTTPStatus_NotExtended = 510, |
|||
HTTPStatus_NetworkAuthenticationRequired = 511, |
|||
}; |
|||
|
|||
typeset HTTPRequestCallback |
|||
{ |
|||
function void (HTTPResponse response, any value); |
|||
function void (HTTPResponse response, any value, const char[] error); |
|||
}; |
|||
|
|||
typeset HTTPFileCallback |
|||
{ |
|||
function void (HTTPStatus status, any value); |
|||
function void (HTTPStatus status, any value, const char[] error); |
|||
}; |
|||
|
|||
methodmap HTTPRequest < Handle |
|||
{ |
|||
// Creates an HTTP request. |
|||
// |
|||
// The Handle is automatically freed when the request is performed. |
|||
// Otherwise, the Handle must be freed via delete or CloseHandle(). |
|||
// |
|||
// @param url URL to the REST API endpoint. |
|||
public native HTTPRequest(const char[] url); |
|||
|
|||
// Appends a parameter to the form data. |
|||
// |
|||
// The parameter name and value are encoded according to RFC 3986. |
|||
// |
|||
// @param name Parameter name. |
|||
// @param format Formatting rules. |
|||
// @param ... Variable number of format parameters. |
|||
public native void AppendFormParam(const char[] name, const char[] format, any ...); |
|||
|
|||
// Appends a query parameter to the URL. |
|||
// |
|||
// The parameter name and value are encoded according to RFC 3986. |
|||
// |
|||
// @param name Parameter name. |
|||
// @param format Formatting rules. |
|||
// @param ... Variable number of format parameters. |
|||
public native void AppendQueryParam(const char[] name, const char[] format, any ...); |
|||
|
|||
// Sets the credentials for HTTP Basic authentication. |
|||
// |
|||
// @param username Username to use. |
|||
// @param password Password to use. |
|||
public native void SetBasicAuth(const char[] username, const char[] password); |
|||
|
|||
// Sets an HTTP header. |
|||
// |
|||
// @param name Header name. |
|||
// @param format Formatting rules. |
|||
// @param ... Variable number of format parameters. |
|||
public native void SetHeader(const char[] name, const char[] format, any ...); |
|||
|
|||
// Performs an HTTP GET request. |
|||
// |
|||
// This function closes the request Handle after completing. |
|||
// |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
public native void Get(HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Performs an HTTP POST request. |
|||
// |
|||
// This function closes the request Handle after completing. |
|||
// |
|||
// @param data JSON data to send. |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
public native void Post(JSON data, HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Performs an HTTP PUT request. |
|||
// |
|||
// This function closes the request Handle after completing. |
|||
// |
|||
// @param data JSON data to send. |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
public native void Put(JSON data, HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Performs an HTTP PATCH request. |
|||
// |
|||
// This function closes the request Handle after completing. |
|||
// |
|||
// @param data JSON data to send. |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
public native void Patch(JSON data, HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Performs an HTTP DELETE request. |
|||
// |
|||
// This function closes the request Handle after completing. |
|||
// |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
public native void Delete(HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Downloads a file. |
|||
// |
|||
// This function closes the request Handle after completing. |
|||
// |
|||
// @param path File path to write to. |
|||
// @param callback A function to use as a callback when the download has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
public native void DownloadFile(const char[] path, HTTPFileCallback callback, any value = 0); |
|||
|
|||
// Uploads a file. |
|||
// |
|||
// This function performs an HTTP PUT request. The file contents are sent in the request body. |
|||
// This function closes the request Handle after completing. |
|||
// |
|||
// @param path File path to read from. |
|||
// @param callback A function to use as a callback when the upload has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
public native void UploadFile(const char[] path, HTTPFileCallback callback, any value = 0); |
|||
|
|||
// Performs an HTTP POST request with form data. |
|||
// |
|||
// This function closes the request Handle after completing. |
|||
// |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
public native void PostForm(HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Connect timeout in seconds. Defaults to 10. |
|||
property int ConnectTimeout { |
|||
public native get(); |
|||
public native set(int connectTimeout); |
|||
} |
|||
|
|||
// Maximum number of redirects to follow. Defaults to 5. |
|||
property int MaxRedirects { |
|||
public native get(); |
|||
public native set(int maxRedirects); |
|||
} |
|||
|
|||
// Maximum download speed in bytes per second. Defaults to unlimited speed. |
|||
property int MaxRecvSpeed { |
|||
public native get(); |
|||
public native set(int maxSpeed); |
|||
} |
|||
|
|||
// Maximum upload speed in bytes per second. Defaults to unlimited speed. |
|||
property int MaxSendSpeed { |
|||
public native get(); |
|||
public native set(int maxSpeed); |
|||
} |
|||
|
|||
// Timeout in seconds. Defaults to 30. |
|||
property int Timeout { |
|||
public native get(); |
|||
public native set(int timeout); |
|||
} |
|||
} |
|||
|
|||
methodmap HTTPResponse |
|||
{ |
|||
// Retrieves an HTTP header from the response. |
|||
// |
|||
// @param name Header name. |
|||
// @param buffer String buffer to store value. |
|||
// @param maxlength Maximum length of the string buffer. |
|||
// @return True on success, false if the header was not found. |
|||
public native bool GetHeader(const char[] name, char[] buffer, int maxlength); |
|||
|
|||
// Retrieves the JSON data of the response. |
|||
// |
|||
// @error Invalid JSON response. |
|||
property JSON Data { |
|||
public native get(); |
|||
} |
|||
|
|||
// Retrieves the HTTP status of the response. |
|||
property HTTPStatus Status { |
|||
public native get(); |
|||
} |
|||
}; |
|||
|
|||
// Deprecated. Use HTTPRequest instead. |
|||
methodmap HTTPClient < Handle |
|||
{ |
|||
// Creates an HTTP client. |
|||
// |
|||
// The HTTPClient must be freed via delete or CloseHandle(). |
|||
// |
|||
// @param baseURL Base URL to the REST API. |
|||
#pragma deprecated Use HTTPRequest instead. |
|||
public native HTTPClient(const char[] baseURL); |
|||
|
|||
// Sets an HTTP header to be used for all requests. |
|||
// |
|||
// @param name Header name. |
|||
// @param value String value to set. |
|||
#pragma deprecated Use HTTPRequest.SetHeader() instead. |
|||
public native void SetHeader(const char[] name, const char[] value); |
|||
|
|||
// Performs an HTTP GET request. |
|||
// |
|||
// @param endpoint API endpoint to request. |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
#pragma deprecated Use HTTPRequest.Get() instead. |
|||
public native void Get(const char[] endpoint, HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Performs an HTTP POST request. |
|||
// |
|||
// @param endpoint API endpoint to request. |
|||
// @param data JSON data to send. |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
#pragma deprecated Use HTTPRequest.Post() instead. |
|||
public native void Post(const char[] endpoint, JSON data, HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Performs an HTTP PUT request. |
|||
// |
|||
// @param endpoint API endpoint to request. |
|||
// @param data JSON data to send. |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
#pragma deprecated Use HTTPRequest.Put() instead. |
|||
public native void Put(const char[] endpoint, JSON data, HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Performs an HTTP PATCH request. |
|||
// |
|||
// @param endpoint API endpoint to request. |
|||
// @param data JSON data to send. |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
#pragma deprecated Use HTTPRequest.Patch() instead. |
|||
public native void Patch(const char[] endpoint, JSON data, HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Performs an HTTP DELETE request. |
|||
// |
|||
// @param endpoint API endpoint to request. |
|||
// @param callback A function to use as a callback when the request has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
#pragma deprecated Use HTTPRequest.Delete() instead. |
|||
public native void Delete(const char[] endpoint, HTTPRequestCallback callback, any value = 0); |
|||
|
|||
// Downloads a file. |
|||
// |
|||
// @param endpoint API endpoint to download from. |
|||
// @param path File path to write to. |
|||
// @param callback A function to use as a callback when the download has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
#pragma deprecated Use HTTPRequest.DownloadFile() instead. |
|||
public native void DownloadFile(const char[] endpoint, const char[] path, HTTPFileCallback callback, any value = 0); |
|||
|
|||
// Uploads a file. |
|||
// |
|||
// This function performs an HTTP PUT request. The file contents are sent in the request body. |
|||
// |
|||
// @param endpoint API endpoint to upload to. |
|||
// @param path File path to read from. |
|||
// @param callback A function to use as a callback when the upload has finished. |
|||
// @param value Optional value to pass to the callback function. |
|||
#pragma deprecated Use HTTPRequest.UploadFile() instead. |
|||
public native void UploadFile(const char[] endpoint, const char[] path, HTTPFileCallback callback, any value = 0); |
|||
|
|||
// Connect timeout in seconds. Defaults to 10. |
|||
#pragma deprecated Use HTTPRequest.ConnectTimeout instead. |
|||
property int ConnectTimeout { |
|||
public native get(); |
|||
public native set(int connectTimeout); |
|||
} |
|||
|
|||
// Follow HTTP 3xx redirects. Defaults to true. |
|||
#pragma deprecated Use HTTPRequest.MaxRedirects instead. |
|||
property bool FollowLocation { |
|||
public native get(); |
|||
public native set(bool followLocation); |
|||
} |
|||
|
|||
// Timeout in seconds. Defaults to 30. |
|||
#pragma deprecated Use HTTPRequest.Timeout instead. |
|||
property int Timeout { |
|||
public native get(); |
|||
public native set(int timeout); |
|||
} |
|||
|
|||
// Maximum upload speed in bytes per second. Defaults to unlimited speed. |
|||
#pragma deprecated Use HTTPRequest.MaxSendSpeed instead. |
|||
property int MaxSendSpeed { |
|||
public native get(); |
|||
public native set(int speed); |
|||
} |
|||
|
|||
// Maximum download speed in bytes per second. Defaults to unlimited speed. |
|||
#pragma deprecated Use HTTPRequest.MaxRecvSpeed instead. |
|||
property int MaxRecvSpeed { |
|||
public native get(); |
|||
public native set(int speed); |
|||
} |
|||
}; |
|||
@ -0,0 +1,400 @@ |
|||
// Decoding flags |
|||
enum |
|||
{ |
|||
JSON_REJECT_DUPLICATES = 0x1, /**< Error if any JSON object contains duplicate keys */ |
|||
JSON_DISABLE_EOF_CHECK = 0x2, /**< Allow extra data after a valid JSON array or object */ |
|||
JSON_DECODE_ANY = 0x4, /**< Decode any value */ |
|||
JSON_DECODE_INT_AS_REAL = 0x8, /**< Interpret all numbers as floats */ |
|||
JSON_ALLOW_NUL = 0x10 /**< Allow \u0000 escape inside string values */ |
|||
}; |
|||
|
|||
// Encoding flags |
|||
enum |
|||
{ |
|||
JSON_COMPACT = 0x20, /**< Compact representation */ |
|||
JSON_ENSURE_ASCII = 0x40, /**< Escape all Unicode characters outside the ASCII range */ |
|||
JSON_SORT_KEYS = 0x80, /**< Sort object keys */ |
|||
JSON_ENCODE_ANY = 0x200, /**< Encode any value */ |
|||
JSON_ESCAPE_SLASH = 0x400, /**< Escape / with \/ */ |
|||
JSON_EMBED = 0x10000 /**< Omit opening and closing braces of the top-level object */ |
|||
}; |
|||
|
|||
// Maximum indentation |
|||
static const int JSON_MAX_INDENT = 0x1F; |
|||
|
|||
// Pretty-print the result, indenting with n spaces |
|||
stock int JSON_INDENT(int n) |
|||
{ |
|||
return n & JSON_MAX_INDENT; |
|||
} |
|||
|
|||
// Output floats with at most n digits of precision |
|||
stock int JSON_REAL_PRECISION(int n) |
|||
{ |
|||
return (n & 0x1F) << 11; |
|||
} |
|||
|
|||
// Generic type for encoding JSON. |
|||
methodmap JSON < Handle |
|||
{ |
|||
// Writes the JSON string representation to a file. |
|||
// |
|||
// @param file File to write to. |
|||
// @param flags Encoding flags. |
|||
// @return True on success, false on failure. |
|||
public native bool ToFile(const char[] file, int flags = 0); |
|||
|
|||
// Retrieves the JSON string representation. |
|||
// |
|||
// @param buffer String buffer to write to. |
|||
// @param maxlength Maximum length of the string buffer. |
|||
// @param flags Encoding flags. |
|||
// @return True on success, false on failure. |
|||
public native bool ToString(char[] buffer, int maxlength, int flags = 0); |
|||
}; |
|||
|
|||
methodmap JSONObject < JSON |
|||
{ |
|||
// Creates a JSON object. A JSON object maps strings (called "keys") to values. Keys in a |
|||
// JSON object are unique. That is, there is at most one entry in the map for a given key. |
|||
// |
|||
// The JSONObject must be freed via delete or CloseHandle(). |
|||
public native JSONObject(); |
|||
|
|||
// Loads a JSON object from a file. |
|||
// |
|||
// @param file File to read from. |
|||
// @param flags Decoding flags. |
|||
// @return Object handle, or null on failure. |
|||
// @error Invalid JSON. |
|||
public static native JSONObject FromFile(const char[] file, int flags = 0); |
|||
|
|||
// Loads a JSON object from a string. |
|||
// |
|||
// @param buffer String buffer to load into the JSON object. |
|||
// @param flags Decoding flags. |
|||
// @return Object handle, or null on failure. |
|||
// @error Invalid JSON. |
|||
public static native JSONObject FromString(const char[] buffer, int flags = 0); |
|||
|
|||
// Retrieves an array or object value from the object. |
|||
// |
|||
// The JSON must be freed via delete or CloseHandle(). |
|||
// |
|||
// @param key Key string. |
|||
// @return Value read. |
|||
// @error Invalid key. |
|||
public native JSON Get(const char[] key); |
|||
|
|||
// Retrieves a boolean value from the object. |
|||
// |
|||
// @param key Key string. |
|||
// @return Value read. |
|||
// @error Invalid key. |
|||
public native bool GetBool(const char[] key); |
|||
|
|||
// Retrieves a float value from the object. |
|||
// |
|||
// @param key Key string. |
|||
// @return Value read. |
|||
// @error Invalid key. |
|||
public native float GetFloat(const char[] key); |
|||
|
|||
// Retrieves an integer value from the object. |
|||
// |
|||
// @param key Key string. |
|||
// @return Value read. |
|||
// @error Invalid key. |
|||
public native int GetInt(const char[] key); |
|||
|
|||
// Retrieves a 64-bit integer value from the object. |
|||
// |
|||
// @param key Key string. |
|||
// @param buffer String buffer to store value. |
|||
// @param maxlength Maximum length of the string buffer. |
|||
// @return True on success, false if the key was not found. |
|||
public native bool GetInt64(const char[] key, char[] buffer, int maxlength); |
|||
|
|||
// Retrieves a string value from the object. |
|||
// |
|||
// @param key Key string. |
|||
// @param buffer String buffer to store value. |
|||
// @param maxlength Maximum length of the string buffer. |
|||
// @return True on success. False if the key was not found, or the value is not a string. |
|||
public native bool GetString(const char[] key, char[] buffer, int maxlength); |
|||
|
|||
// Returns whether or not a value in the object is null. |
|||
// |
|||
// @param key Key string. |
|||
// @return True if the value is null, false otherwise. |
|||
// @error Invalid key. |
|||
public native bool IsNull(const char[] key); |
|||
|
|||
// Returns whether or not a key exists in the object. |
|||
// |
|||
// @param key Key string. |
|||
// @return True if the key exists, false otherwise. |
|||
public native bool HasKey(const char[] key); |
|||
|
|||
// Sets an array or object value in the object, either inserting a new entry or replacing an old one. |
|||
// |
|||
// @param key Key string. |
|||
// @param value Value to store at this key. |
|||
// @return True on success, false on failure. |
|||
public native bool Set(const char[] key, JSON value); |
|||
|
|||
// Sets a boolean value in the object, either inserting a new entry or replacing an old one. |
|||
// |
|||
// @param key Key string. |
|||
// @param value Value to store at this key. |
|||
// @return True on success, false on failure. |
|||
public native bool SetBool(const char[] key, bool value); |
|||
|
|||
// Sets a float value in the object, either inserting a new entry or replacing an old one. |
|||
// |
|||
// @param key Key string. |
|||
// @param value Value to store at this key. |
|||
// @return True on success, false on failure. |
|||
public native bool SetFloat(const char[] key, float value); |
|||
|
|||
// Sets an integer value in the object, either inserting a new entry or replacing an old one. |
|||
// |
|||
// @param key Key string. |
|||
// @param value Value to store at this key. |
|||
// @return True on success, false on failure. |
|||
public native bool SetInt(const char[] key, int value); |
|||
|
|||
// Sets a 64-bit integer value in the object, either inserting a new entry or replacing an old one. |
|||
// |
|||
// @param key Key string. |
|||
// @param value Value to store at this key. |
|||
// @return True on success, false on failure. |
|||
public native bool SetInt64(const char[] key, const char[] value); |
|||
|
|||
// Sets a null value in the object, either inserting a new entry or replacing an old one. |
|||
// |
|||
// @param key Key string. |
|||
// @return True on success, false on failure. |
|||
public native bool SetNull(const char[] key); |
|||
|
|||
// Sets a string value in the object, either inserting a new entry or replacing an old one. |
|||
// |
|||
// @param key Key string. |
|||
// @param value Value to store at this key. |
|||
// @return True on success, false on failure. |
|||
public native bool SetString(const char[] key, const char[] value); |
|||
|
|||
// Removes an entry from the object. |
|||
// |
|||
// @param key Key string. |
|||
// @return True on success, false if the key was not found. |
|||
public native bool Remove(const char[] key); |
|||
|
|||
// Clears the object of all entries. |
|||
// @return True on success, false on failure. |
|||
public native bool Clear(); |
|||
|
|||
// Returns an iterator for the object's keys. See JSONObjectKeys. |
|||
public native JSONObjectKeys Keys(); |
|||
|
|||
// Retrieves the size of the object. |
|||
property int Size { |
|||
public native get(); |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* A JSONObjectKeys is created via JSONObject.Keys(). It allows the keys of an |
|||
* object to be iterated. The JSONObjectKeys must be freed with delete or |
|||
* CloseHandle(). |
|||
*/ |
|||
methodmap JSONObjectKeys < Handle |
|||
{ |
|||
// Reads an object key, then advances to the next key if any. |
|||
// |
|||
// @param buffer String buffer to store key. |
|||
// @param maxlength Maximum length of the string buffer. |
|||
// @return True on success, false if there are no more keys. |
|||
public native bool ReadKey(char[] buffer, int maxlength); |
|||
}; |
|||
|
|||
methodmap JSONArray < JSON |
|||
{ |
|||
// Creates a JSON array. |
|||
// |
|||
// The JSONArray must be freed via delete or CloseHandle(). |
|||
public native JSONArray(); |
|||
|
|||
// Loads a JSON array from a file. |
|||
// |
|||
// @param file File to read from. |
|||
// @param flags Decoding flags. |
|||
// @return Array handle, or null on failure. |
|||
// @error Invalid JSON. |
|||
public static native JSONArray FromFile(const char[] file, int flags = 0); |
|||
|
|||
// Loads a JSON array from a string. |
|||
// |
|||
// @param buffer String buffer to load into the JSON array. |
|||
// @param flags Decoding flags. |
|||
// @return Array handle, or null on failure. |
|||
// @error Invalid JSON. |
|||
public static native JSONArray FromString(const char[] buffer, int flags = 0); |
|||
|
|||
// Retrieves an array or object value from the array. |
|||
// |
|||
// The JSON must be freed via delete or CloseHandle(). |
|||
// |
|||
// @param index Index in the array. |
|||
// @return Value read. |
|||
// @error Invalid index. |
|||
public native JSON Get(int index); |
|||
|
|||
// Retrieves a boolean value from the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @return Value read. |
|||
// @error Invalid index. |
|||
public native bool GetBool(int index); |
|||
|
|||
// Retrieves a float value from the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @return Value read. |
|||
// @error Invalid index. |
|||
public native float GetFloat(int index); |
|||
|
|||
// Retrieves an integer value from the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @return Value read. |
|||
// @error Invalid index. |
|||
public native int GetInt(int index); |
|||
|
|||
// Retrieves an 64-bit integer value from the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @param buffer Buffer to copy to. |
|||
// @param maxlength Maximum size of the buffer. |
|||
// @error Invalid index. |
|||
public native void GetInt64(int index, char[] buffer, int maxlength); |
|||
|
|||
// Retrieves a string value from the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @param buffer Buffer to copy to. |
|||
// @param maxlength Maximum size of the buffer. |
|||
// @return True on success, false if the value is not a string. |
|||
// @error Invalid index. |
|||
public native bool GetString(int index, char[] buffer, int maxlength); |
|||
|
|||
// Returns whether or not a value in the array is null. |
|||
// |
|||
// @param index Index in the array. |
|||
// @return True if the value is null, false otherwise. |
|||
// @error Invalid index. |
|||
public native bool IsNull(int index); |
|||
|
|||
// Sets an array or object value in the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @param value Value to set. |
|||
// @return True on success, false on failure. |
|||
public native bool Set(int index, JSON value); |
|||
|
|||
// Sets a boolean value in the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @param value Value to set. |
|||
// @return True on success, false on failure. |
|||
public native bool SetBool(int index, bool value); |
|||
|
|||
// Sets a float value in the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @param value Value to set. |
|||
// @return True on success, false on failure. |
|||
public native bool SetFloat(int index, float value); |
|||
|
|||
// Sets an integer value in the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @param value Value to set. |
|||
// @return True on success, false on failure. |
|||
public native bool SetInt(int index, int value); |
|||
|
|||
// Sets a 64 bit integer value in the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @param value 64-bit integer value to set. |
|||
// @return True on success, false on failure. |
|||
public native bool SetInt64(int index, const char[] value); |
|||
|
|||
// Sets a null value in the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @return True on success, false on failure. |
|||
public native bool SetNull(int index); |
|||
|
|||
// Sets a string value in the array. |
|||
// |
|||
// @param index Index in the array. |
|||
// @param value String value to set. |
|||
// @return True on success, false on failure. |
|||
public native bool SetString(int index, const char[] value); |
|||
|
|||
// Pushes an array or object value onto the end of the array, adding a new index. |
|||
// |
|||
// @param value Value to push. |
|||
// @return True on success, false on failure. |
|||
public native bool Push(JSON value); |
|||
|
|||
// Pushes a boolean value onto the end of the array, adding a new index. |
|||
// |
|||
// @param value Value to push. |
|||
// @return True on success, false on failure. |
|||
public native bool PushBool(bool value); |
|||
|
|||
// Pushes a float value onto the end of the array, adding a new index. |
|||
// |
|||
// @param value Value to push. |
|||
// @return True on success, false on failure. |
|||
public native bool PushFloat(float value); |
|||
|
|||
// Pushes an integer value onto the end of the array, adding a new index. |
|||
// |
|||
// @param value Value to push. |
|||
// @return True on success, false on failure. |
|||
public native bool PushInt(int value); |
|||
|
|||
// Pushes a 64-bit integer value onto the end of the array, adding a new index. |
|||
// |
|||
// @param value 64-bit integer value to push. |
|||
// @return True on success, false on failure. |
|||
public native bool PushInt64(const char[] value); |
|||
|
|||
// Pushes a null value onto the end of the array, adding a new index. |
|||
// @return True on success, false on failure. |
|||
public native bool PushNull(); |
|||
|
|||
// Pushes a string value onto the end of the array, adding a new index. |
|||
// |
|||
// @param value String value to push. |
|||
// @return True on success, false on failure. |
|||
public native bool PushString(const char[] value); |
|||
|
|||
// Removes an entry from the array. |
|||
// |
|||
// @param index Index in the array to remove. |
|||
// @return True on success, false on invalid index. |
|||
public native bool Remove(int index); |
|||
|
|||
// Clears the array of all entries. |
|||
// @return True on success, false on failure. |
|||
public native bool Clear(); |
|||
|
|||
// Retrieves the size of the array. |
|||
property int Length { |
|||
public native get(); |
|||
} |
|||
}; |
|||
Loading…
Reference in new issue