Introduction JSON extension
Features
- Relies on YYJSON which is High-performance JSON library written in C
- High-performance JSON parsing and serialization (Simple Performance Test Report )
- Support for JSON Pointer operations
- x64 support
- Easy-to-use API for both objects and arrays
- Pretty printing and writing support
- Array and object sorting support
- Iteration methods for arrays and objects
- Support for both mutable and immutable JSON documents
- Support SourceMod Extension API
Basic Examples
Working with Objects
// Create a JSON object
JSONObject obj = new JSONObject();
obj.SetInt("int", 1);
obj.SetInt64("int64", "9223372036854775800");
obj.SetFloat("float", 2.0);
obj.SetBool("bool", true);
obj.SetString("str", "Hello World");
obj.SetNull("null");
/* Output:
{
"int": 1,
"int64": 9223372036854775800,
"float": 2.0,
"bool": true,
"str": "Hello World",
"null": null
}
*/
delete obj;
Working with Arrays
// Create a JSON array
JSONArray arr = new JSONArray();
arr.PushInt(1);
arr.PushInt64("9223372036854775800");
arr.PushFloat(2.0);
arr.PushBool(true);
arr.PushString("Hello World");
arr.PushNull();
delete arr;
/* Output:
[
1,
9223372036854775800,
2.0,
true,
"Hello World",
null
]
*/
Advanced Features
Using JSON Pointer
// Create nested structures
JSONObject obj = new JSONObject();
obj.PtrSetInt("/a/b/c", 1);
delete obj;
/* Output:
{
"a": {
"b": {
"c": 1
}
}
}
*/
/* example.json:
{
"int": 1234,
"arr": [1, 1.2344, 3],
"nested": {
"obj": {
"value": 42
}
}
}
*/
JSONObject data = JSON.Parse("example.json", true);
// Access values using JSON Pointer
int value = data.PtrGetInt("/int"); // Returns: 1234
float fValue = data.PtrGetFloat("/arr/1"); // Returns: 1.2344
int nested = data.PtrGetInt("/nested/obj/value"); // Returns: 42
delete data;
Array and Object Iteration
// Object iteration
JSONObject obj = JSON.Parse("{\"a\": 1, \"b\": 2, \"c\": 3}");
char key[64];
// Method 1: Using Object Iterator (Recommended)
JSONObjIter iter = new JSONObjIter(obj);
while (iter.Next(key, sizeof(key))) {
PrintToServer("Key: %s", key);
}
delete iter;
// Method 2: Classic iteration
for (int i = 0; i < obj.Size; i++) {
obj.GetKey(i, key, sizeof(key));
JSON value = obj.GetValueAt(i);
delete value;
}
delete obj;
// Array iteration
JSONArray arr = JSON.Parse("[1, 2, 3, 4, 5]");
JSON value;
// Method 1: Using Array Iterator (Recommended)
JSONArrIter iter = new JSONArrIter(arr);
while ((value = iter.Next) != null) {
PrintToServer("Index: %d", iter.Index);
delete value;
}
delete iter;
// Method 2: Classic iteration
for (int i = 0; i < arr.Length; i++) {
value = arr.Get(i);
delete value;
}
delete arr;
Array Search Operations
// Create a test array
JSONArray arr = JSON.Parse(
"[42, true, \"hello\", 3.14, \"world\", false, 42]"
);
// Search for values (returns first occurrence)
int index;
index = arr.IndexOfInt(42); // Returns 0
index = arr.IndexOfBool(true); // Returns 1
index = arr.IndexOfString("hello"); // Returns 2
index = arr.IndexOfFloat(3.14); // Returns 3
index = arr.IndexOfString("world"); // Returns 4
index = arr.IndexOfBool(false); // Returns 5
// Search for non-existent values
index = arr.IndexOfInt(999); // Returns -1
index = arr.IndexOfString("missing"); // Returns -1
index = arr.IndexOfFloat(2.718); // Returns -1
delete arr;
Sorting Arrays and Objects
// Array sorting
JSONArray arr = JSON.Parse(
"[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]", .is_mutable_doc = true
);
arr.Sort(); // Ascending (default)
// [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
arr.Sort(YYJSON_SORT_DESC); // Descending
// [9, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1]
arr.Sort(YYJSON_SORT_RANDOM); // Random
// [5, 2, 9, 1, 6, 3, 4, 5, 1, 3, 5] (example output)
// Mixed type array sorting
JSONArray mixed = JSON.Parse(
"[true, 42, \"hello\", 1.23, false, \"world\"]", .is_mutable_doc = true
);
mixed.Sort();
// [false, true, 1.23, 42, "hello", "world"]
// Object sorting by keys
JSONObject obj = JSON.Parse(
"{\"zebra\": 1, \"alpha\": 2, \"beta\": 3, \"gamma\": 4}", .is_mutable_doc = true
);
obj.Sort(); // Ascending (default)
// {"alpha": 2, "beta": 3, "gamma": 4, "zebra": 1}
obj.Sort(YYJSON_SORT_DESC); // Descending
// {"zebra": 1, "gamma": 4, "beta": 3, "alpha": 2}
obj.Sort(YYJSON_SORT_RANDOM); // Random
// {"beta": 3, "zebra": 1, "alpha": 2, "gamma": 4} (example output)
delete arr;
delete mixed;
delete obj;
Using FromStrings
// Create object from key-value string arrays
char pairs[][] = {"name", "test", "type", "demo", "version", "1.0.0"};
JSONObject obj = JSONObject.FromStrings(pairs, sizeof(pairs));
/* Output:
{
"name": "test",
"type": "demo",
"version": "1.0.0"
}
*/
// Create array from string array
char items[][] = {"apple", "banana", "orange"};
JSONArray arr = JSONArray.FromStrings(items, sizeof(items));
delete obj;
delete arr;
/* Output:
[
"apple",
"banana",
"orange"
]
*/
Using JSON Pack
// Create object with mixed types
JSON packed = JSON.Pack("{s:s,s:i,s:f,s:b,s:n}",
"name", "John",
"age", 25,
"height", 1.75,
"active", true,
"extra"
);
delete packed;
/* Output:
{
"name": "John",
"age": 25,
"height": 1.75,
"active": true,
"extra": null
}
*/
// Create nested structures
JSON nested = JSON.Pack("{s:{s:s,s:[iii]}}",
"user",
"name", "John",
"scores", 85, 90, 95
);
delete nested;
/* Output:
{
"user": {
"name": "John",
"scores": [85, 90, 95]
}
}
*/
// Create array with mixed types
JSON array = JSON.Pack("[sifbn]",
"test", 42, 3.14, true
);
delete array;
/* Output:
[
"test",
42,
3.14,
true,
null
]
*/
Working with Immutable Documents
When parsing JSON documents, you can choose whether to create a mutable or immutable document:
// Create an immutable document (read-only)
JSONObject obj = JSON.Parse("example.json", true);
// Create a mutable document (read-write)
JSONObject obj = JSON.Parse("example.json", true, true);
Immutable documents:
- Are read-only and cannot be modified
- Use less memory
- Throw errors when attempting modification operations
Operations on Immutable Documents
Immutable documents support a variety of read operations:
- Type Checking: You can check the type of values within the document.
- Value Retrieval: You can retrieve values using keys or indices.
- Iteration: You can iterate over arrays and objects.
- Comparison: You can compare immutable documents with other documents.
Example of operations with immutable documents:
// Create an immutable document
JSONObject obj = JSON.Parse("example.json", true);
// Reading is allowed
int value = obj.GetInt("key"); // Works fine
float fValue = obj.GetFloat("key2"); // Works fine
// Modifications will fail with clear error messages
obj.SetInt("key", 123); // Error: Cannot set value in an immutable JSON object
obj.Remove("key"); // Error: Cannot remove value from an immutable JSON object
obj.Sort(); // Error: Cannot sort an immutable JSON object
delete obj;
Converting Between Mutable and Immutable
You can convert between mutable and immutable documents using deep copy:
// Create an immutable document
JSONObject immutable = JSON.Parse("example.json", true);
// Create a mutable copy
JSONObject mutable = immutable.ToMutable();
// Now you can modify the mutable copy
mutable.SetInt("key", 123);
delete mutable;
delete immutable;
Hello - Have you spoken with anyone prior to proposing this?
Sounds like it's a good extension, but not one that's ubiquitous enough to be included with sourcemod
Hello - Have you spoken with anyone prior to proposing this?
Sounds like it's a good extension, but not one that's ubiquitous enough to be included with sourcemod
Not yet. I'm suggesting that SourceMod should have a modern, official JSON extension, considering AMXX has long included this capability https://github.com/alliedmodders/amxmodx/pull/379
The extension looks useful and well-designed. It seems particularly valuable for exchanging data between external services. Perhaps an extension like REST in Pawn could complement it, although I believe that has already been suggested.
Throwing in my two cents. This looks nice, but much like headline said I think this should have been talked over before opening a PR. Historically when sourcemod takes in new extensions, the accepted extension is already well established into the plugin ecosystem. This has two benefits :
- We know it's robust, because it has been field tested for years
- We don't cause any plugins rewrite
REST in Pawn has been mentioned, and it is indeed (at least to me) the go-to JSON extension for plugins. Accepting this extension instead would mean that the plugin authors who care about JSON capabilities will have to migrate to this API (and maybe uncover bugs in the process). I don't think it's a dealbreaker, but I'm mentioning why accepting could cause troubles.
Last, and this is mostly a nitpick. I don't think it should ever be leaked to plugins what kind of json's flavor they're using.
YYJson > Json or SMJson or something
If YYJson is ever revealed as problematic, or we want rapidjson or nlohmann instead, we don't want to cause confusion. So let's keep the name anonymous.
Throwing in my two cents. This looks nice, but much like headline said I think this should have been talked over before opening a PR. Historically when sourcemod takes in new extensions, the accepted extension is already well established into the plugin ecosystem. This has two benefits :
- We know it's robust, because it has been field tested for years
- We don't cause any plugins rewrite
REST in Pawn has been mentioned, and it is indeed (at least to me) the go-to JSON extension for plugins. Accepting this extension instead would mean that the plugin authors who care about JSON capabilities will have to migrate to this API (and maybe uncover bugs in the process). I don't think it's a dealbreaker, but I'm mentioning why accepting could cause troubles.
Last, and this is mostly a nitpick. I don't think it should ever be leaked to plugins what kind of json's flavor they're using.
YYJson>JsonorSMJsonor something If YYJson is ever revealed as problematic, or we want rapidjson or nlohmann instead, we don't want to cause confusion. So let's keep the name anonymous.
You're right - I should have discussed this with the community first. I got overly excited about the technical implementation and jumped into the PR stage too early.
- I completely agree about minimizing ecosystem disruption. My goal isn't to replace ripext in Pawn, but rather to provide a modern, high-performance alternative for new development. Perhaps we could position it as a complementary option rather than a replacement?
- Regarding the naming convention, I initially chose "yyjson" specifically to avoid conflicts with the ripext extension. However, I recognize the value of keeping the implementation abstracted. I'll gladly adopt thsi suggestion and rename to "JSON" - this will indeed make future maintenance and potential library migrations much smoother.
- As for migration: Since this extension provides external interfaces, other extensions can migrate to this library very easily. My websocket extension has already done exactly this and demonstrates how straightforward the integration can be.