feat: enhance JSON handling with performance and memory improvements

- Add WriteToStringPtr for optimized serialization without buffer allocations
- Implement reference counting with RefCounted/RefPtr for better memory management
- Support JSON Patch (RFC 6902) and Merge Patch (RFC 7386) operations
- Improve floating-point serialization with adjusted precision limits (1-7)
- Enhance iterators with reset capability and better error handling
- Update documentation and tests for new features
This commit is contained in:
ProjectSky 2025-11-16 10:37:57 +08:00
parent 0ad8d15d1a
commit f596135996
8 changed files with 1952 additions and 450 deletions

View File

@ -1,5 +1,5 @@
#ifndef _INCLUDE_SM_JSON_IJSONMANAGER_H_
#define _INCLUDE_SM_JSON_IJSONMANAGER_H_
#ifndef _INCLUDE_IJSONMANAGER_H_
#define _INCLUDE_IJSONMANAGER_H_
#include <IHandleSys.h>
#include <variant>
@ -15,7 +15,7 @@ class JsonArrIter;
class JsonObjIter;
#define SMINTERFACE_JSONMANAGER_NAME "IJsonManager"
#define SMINTERFACE_JSONMANAGER_VERSION 1
#define SMINTERFACE_JSONMANAGER_VERSION 2
#define JSON_PACK_ERROR_SIZE 256
#define JSON_ERROR_BUFFER_SIZE 256
#define JSON_INT64_BUFFER_SIZE 32
@ -99,10 +99,69 @@ public:
*
* @note The out_size parameter returns the size including null terminator
* @note Use GetSerializedSize() with the same write_flg to determine buffer size
* @warning This method performs multiple memory allocations and copies, resulting in poor performance.
* For better performance, use WriteToStringPtr() instead, which avoids intermediate buffers
*/
virtual bool WriteToString(JsonValue* handle, char* buffer, size_t buffer_size,
uint32_t write_flg = 0, size_t* out_size = nullptr) = 0;
/**
* Write JSON to string and return allocated string (performance-optimized version)
* @param handle JSON value
* @param write_flg Write flags (YYJSON_WRITE_FLAG values, default: 0)
* @param out_size Pointer to receive actual size written (including null terminator) optional
* @return Allocated string pointer on success, nullptr on error. Caller must free() the returned pointer
*
* @note This is the recommended method for serialization as it avoids intermediate buffer allocations
*/
virtual char* WriteToStringPtr(JsonValue* handle, uint32_t write_flg = 0, size_t* out_size = nullptr) = 0;
/**
* Apply JSON Patch (RFC 6902) and return a new JSON value
* @param target Target JSON value
* @param patch JSON Patch document
* @param result_mutable true to return mutable result, false for immutable
* @param error Error buffer (optional)
* @param error_size Error buffer size
* @return Patched JSON value on success, nullptr on failure
*/
virtual JsonValue* ApplyJsonPatch(JsonValue* target, JsonValue* patch, bool result_mutable,
char* error = nullptr, size_t error_size = 0) = 0;
/**
* Apply JSON Patch in place (target must be mutable)
* @param target Target JSON value (mutable document)
* @param patch JSON Patch document
* @param error Error buffer (optional)
* @param error_size Error buffer size
* @return true on success, false on failure
*/
virtual bool JsonPatchInPlace(JsonValue* target, JsonValue* patch,
char* error = nullptr, size_t error_size = 0) = 0;
/**
* Apply JSON Merge Patch (RFC 7396) and return a new JSON value
* @param target Target JSON value
* @param patch JSON Merge Patch document
* @param result_mutable true to return mutable result, false for immutable
* @param error Error buffer (optional)
* @param error_size Error buffer size
* @return Patched JSON value on success, nullptr on failure
*/
virtual JsonValue* ApplyMergePatch(JsonValue* target, JsonValue* patch, bool result_mutable,
char* error = nullptr, size_t error_size = 0) = 0;
/**
* Apply JSON Merge Patch in place (target must be mutable)
* @param target Target JSON value (mutable document)
* @param patch JSON Merge Patch document
* @param error Error buffer (optional)
* @param error_size Error buffer size
* @return true on success, false on failure
*/
virtual bool MergePatchInPlace(JsonValue* target, JsonValue* patch,
char* error = nullptr, size_t error_size = 0) = 0;
/**
* Write JSON to file
* @param handle JSON value
@ -316,6 +375,14 @@ public:
*/
virtual size_t GetReadSize(JsonValue* handle) = 0;
/**
* Get the reference count of the document
* @param handle JSON value
* @return Reference count of the document (number of JsonValue objects sharing this document)
* @note Returns 0 if handle is null or has no document
*/
virtual size_t GetRefCount(JsonValue* handle) = 0;
/**
* Create an empty mutable JSON object
* @return New mutable JSON object or nullptr on failure
@ -964,10 +1031,10 @@ public:
* Remove range of elements (mutable only)
* @param handle JSON array
* @param start_index Start index (inclusive)
* @param end_index End index (exclusive)
* @param count Number of elements to remove
* @return true on success
*/
virtual bool ArrayRemoveRange(JsonValue* handle, size_t start_index, size_t end_index) = 0;
virtual bool ArrayRemoveRange(JsonValue* handle, size_t start_index, size_t count) = 0;
/**
* Remove all elements (mutable only)
@ -1001,20 +1068,12 @@ public:
virtual int ArrayIndexOfInt(JsonValue* handle, int search_value) = 0;
/**
* Find index of 64-bit integer value
* Find index of 64-bit integer value (auto-detects signed/unsigned)
* @param handle JSON array
* @param search_value 64-bit integer value to search for
* @param search_value 64-bit integer value to search for (std::variant<int64_t, uint64_t>)
* @return Index of first match, or -1 if not found
*/
virtual int ArrayIndexOfInt64(JsonValue* handle, int64_t search_value) = 0;
/**
* Find index of 64-bit unsigned integer value
* @param handle JSON array
* @param search_value 64-bit unsigned integer value to search for
* @return Index of first match, or -1 if not found
*/
virtual int ArrayIndexOfUint64(JsonValue* handle, uint64_t search_value) = 0;
virtual int ArrayIndexOfInt64(JsonValue* handle, std::variant<int64_t, uint64_t> search_value) = 0;
/**
* Find index of float value
@ -1538,6 +1597,8 @@ public:
* Initialize an array iterator (same as ArrIterWith but returns pointer)
* @param handle JSON array value
* @return New array iterator or nullptr on error
* @note Caller must release the iterator using ReleaseArrIter() once finished
* @note Iterators are single-pass; once ArrIterNext() returns nullptr, create a new iterator or call ArrIterReset() to iterate again
*/
virtual JsonArrIter* ArrIterInit(JsonValue* handle) = 0;
@ -1545,9 +1606,18 @@ public:
* Create an array iterator with an array
* @param handle JSON array value
* @return New array iterator or nullptr on error
* @note Caller must release the iterator using ReleaseArrIter() once finished
* @note Iterators are single-pass; once ArrIterNext() returns nullptr, create a new iterator or call ArrIterReset() to iterate again
*/
virtual JsonArrIter* ArrIterWith(JsonValue* handle) = 0;
/**
* Reset an array iterator to the beginning
* @param iter Array iterator
* @return true on success, false if iterator is invalid or reset failed
*/
virtual bool ArrIterReset(JsonArrIter* iter) = 0;
/**
* Get next element from array iterator
* @param iter Array iterator
@ -1580,6 +1650,8 @@ public:
* Initialize an object iterator (same as ObjIterWith but returns pointer)
* @param handle JSON object value
* @return New object iterator or nullptr on error
* @note Caller must release the iterator using ReleaseObjIter() once finished
* @note Iterators are single-pass; once ObjIterNext() returns nullptr, create a new iterator or call ObjIterReset() to iterate again
*/
virtual JsonObjIter* ObjIterInit(JsonValue* handle) = 0;
@ -1587,9 +1659,18 @@ public:
* Create an object iterator with an object
* @param handle JSON object value
* @return New object iterator or nullptr on error
* @note Caller must release the iterator using ReleaseObjIter() once finished
* @note Iterators are single-pass; once ObjIterNext() returns nullptr, create a new iterator or call ObjIterReset() to iterate again
*/
virtual JsonObjIter* ObjIterWith(JsonValue* handle) = 0;
/**
* Reset an object iterator to the beginning
* @param iter Object iterator
* @return true on success, false if iterator is invalid or reset failed
*/
virtual bool ObjIterReset(JsonObjIter* iter) = 0;
/**
* Get next key from object iterator
* @param iter Object iterator
@ -1707,16 +1788,18 @@ public:
* @param flt true to use single-precision (float), false to use double-precision (double)
* @return true on success, false if handle is not a floating-point number
* @note Only works on floating-point numbers (not integers)
* @note This affects how the number is serialized in all write operations
*/
virtual bool SetFpToFloat(JsonValue* handle, bool flt) = 0;
/**
* Set floating-point number's output format to fixed-point notation
* @param handle JSON floating-point number value
* @param prec Precision (1-15), similar to ECMAScript Number.prototype.toFixed(prec) but with trailing zeros removed
* @param prec Precision (1-15), similar to ECMAScript `Number.prototype.toFixed(prec)` but with trailing zeros removed
* @return true on success, false if handle is not a floating-point number or prec is out of range
* @note Only works on floating-point numbers (not integers)
* @note This will produce shorter output but may lose some precision
* @note This affects how the number is serialized in all write operations
*/
virtual bool SetFpToFixed(JsonValue* handle, int prec) = 0;
@ -1794,4 +1877,4 @@ public:
char* error = nullptr, size_t error_size = 0) = 0;
};
#endif // _INCLUDE_SM_JSON_IJSONMANAGER_H_
#endif // _INCLUDE_IJSONMANAGER_H_

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
#ifndef _INCLUDE_SM_JSON_JSONMANAGER_H_
#define _INCLUDE_SM_JSON_JSONMANAGER_H_
#ifndef _INCLUDE_JSONMANAGER_H_
#define _INCLUDE_JSONMANAGER_H_
#include <IJsonManager.h>
#include <yyjson.h>
@ -7,6 +7,172 @@
#include <memory>
#include <charconv>
/**
* @brief Base class for intrusive reference counting
*
* Objects inheriting from this class can be managed by RefPtr.
* Reference count starts at 0 and is incremented when RefPtr takes ownership.
*/
class RefCounted {
private:
mutable size_t ref_count_ = 0;
protected:
virtual ~RefCounted() = default;
RefCounted() = default;
RefCounted(const RefCounted &) = delete;
RefCounted &operator=(const RefCounted &) = delete;
RefCounted(RefCounted &&) noexcept : ref_count_(0) {}
RefCounted &operator=(RefCounted &&) noexcept { return *this; }
public:
void add_ref() const noexcept { ++ref_count_; }
void release() const noexcept {
assert(ref_count_ > 0 && "Reference count underflow");
if (--ref_count_ == 0) {
delete this;
}
}
size_t use_count() const noexcept { return ref_count_; }
};
/**
* @brief Smart pointer for intrusive reference counting
*
* Similar to std::shared_ptr but uses intrusive reference counting.
* Automatically manages object lifetime through add_ref() and release().
* @warning This implementation is not thread-safe. It must only be used
* in single-threaded contexts or with external synchronization.
*/
template <typename T> class RefPtr {
private:
T *ptr_;
public:
RefPtr() noexcept : ptr_(nullptr) {}
explicit RefPtr(T *p) noexcept : ptr_(p) {
if (ptr_)
ptr_->add_ref();
}
RefPtr(const RefPtr &other) noexcept : ptr_(other.ptr_) {
if (ptr_)
ptr_->add_ref();
}
RefPtr(RefPtr &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
~RefPtr() {
if (ptr_)
ptr_->release();
}
RefPtr &operator=(const RefPtr &other) noexcept {
if (ptr_ != other.ptr_) {
if (other.ptr_)
other.ptr_->add_ref();
if (ptr_)
ptr_->release();
ptr_ = other.ptr_;
}
return *this;
}
RefPtr &operator=(RefPtr &&other) noexcept {
if (this != &other) {
if (ptr_)
ptr_->release();
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
return *this;
}
RefPtr &operator=(T *p) noexcept {
reset(p);
return *this;
}
T *operator->() const noexcept { return ptr_; }
T &operator*() const noexcept { return *ptr_; }
T *get() const noexcept { return ptr_; }
explicit operator bool() const noexcept { return ptr_ != nullptr; }
size_t use_count() const noexcept { return ptr_ ? ptr_->use_count() : 0; }
void reset(T *p = nullptr) noexcept {
if (ptr_ != p) {
if (p)
p->add_ref();
if (ptr_)
ptr_->release();
ptr_ = p;
}
}
bool operator==(const RefPtr &other) const noexcept { return ptr_ == other.ptr_; }
bool operator!=(const RefPtr &other) const noexcept { return ptr_ != other.ptr_; }
bool operator==(std::nullptr_t) const noexcept { return ptr_ == nullptr; }
bool operator!=(std::nullptr_t) const noexcept { return ptr_ != nullptr; }
};
/**
* @brief Factory function to create RefPtr from new object
*/
template <typename T, typename... Args> RefPtr<T> make_ref(Args &&...args) {
return RefPtr<T>(new T(std::forward<Args>(args)...));
}
/**
* @brief Wrapper for yyjson_mut_doc with intrusive reference counting
*/
class RefCountedMutDoc : public RefCounted {
private:
yyjson_mut_doc *doc_;
public:
explicit RefCountedMutDoc(yyjson_mut_doc *doc) noexcept : doc_(doc) {}
RefCountedMutDoc(const RefCountedMutDoc &) = delete;
RefCountedMutDoc &operator=(const RefCountedMutDoc &) = delete;
~RefCountedMutDoc() noexcept override {
if (doc_) {
yyjson_mut_doc_free(doc_);
}
}
yyjson_mut_doc *get() const noexcept { return doc_; }
};
/**
* @brief Wrapper for yyjson_doc with intrusive reference counting
*/
class RefCountedImmutableDoc : public RefCounted {
private:
yyjson_doc *doc_;
public:
explicit RefCountedImmutableDoc(yyjson_doc *doc) noexcept : doc_(doc) {}
RefCountedImmutableDoc(const RefCountedImmutableDoc &) = delete;
RefCountedImmutableDoc &operator=(const RefCountedImmutableDoc &) = delete;
~RefCountedImmutableDoc() noexcept override {
if (doc_) {
yyjson_doc_free(doc_);
}
}
yyjson_doc *get() const noexcept { return doc_; }
};
/**
* @brief JSON value wrapper
*
@ -16,14 +182,7 @@
class JsonValue {
public:
JsonValue() = default;
~JsonValue() {
if (m_pDocument_mut.use_count() == 1) {
yyjson_mut_doc_free(m_pDocument_mut.get());
}
if (m_pDocument.use_count() == 1) {
yyjson_doc_free(m_pDocument.get());
}
}
~JsonValue() = default;
JsonValue(const JsonValue&) = delete;
JsonValue& operator=(const JsonValue&) = delete;
@ -45,12 +204,21 @@ public:
return m_pDocument != nullptr;
}
size_t GetDocumentRefCount() const {
if (m_pDocument_mut) {
return m_pDocument_mut.use_count();
} else if (m_pDocument) {
return m_pDocument.use_count();
}
return 0;
}
// Mutable document
std::shared_ptr<yyjson_mut_doc> m_pDocument_mut;
RefPtr<RefCountedMutDoc> m_pDocument_mut;
yyjson_mut_val* m_pVal_mut{ nullptr };
// Immutable document
std::shared_ptr<yyjson_doc> m_pDocument;
RefPtr<RefCountedImmutableDoc> m_pDocument;
yyjson_val* m_pVal{ nullptr };
// Mutable document iterators
@ -84,12 +252,13 @@ public:
return m_isMutable;
}
std::shared_ptr<yyjson_mut_doc> m_pDocument_mut;
std::shared_ptr<yyjson_doc> m_pDocument;
RefPtr<RefCountedMutDoc> m_pDocument_mut;
RefPtr<RefCountedImmutableDoc> m_pDocument;
yyjson_mut_arr_iter m_iterMut;
yyjson_arr_iter m_iterImm;
yyjson_mut_val* m_rootMut{ nullptr };
yyjson_val* m_rootImm{ nullptr };
Handle_t m_handle{ BAD_HANDLE };
bool m_isMutable{ false };
@ -113,12 +282,13 @@ public:
return m_isMutable;
}
std::shared_ptr<yyjson_mut_doc> m_pDocument_mut;
std::shared_ptr<yyjson_doc> m_pDocument;
RefPtr<RefCountedMutDoc> m_pDocument_mut;
RefPtr<RefCountedImmutableDoc> m_pDocument;
yyjson_mut_obj_iter m_iterMut;
yyjson_obj_iter m_iterImm;
yyjson_mut_val* m_rootMut{ nullptr };
yyjson_val* m_rootImm{ nullptr };
void* m_currentKey{ nullptr };
@ -139,6 +309,15 @@ public:
yyjson_read_flag read_flg, char* error, size_t error_size) override;
virtual bool WriteToString(JsonValue* handle, char* buffer, size_t buffer_size,
yyjson_write_flag write_flg, size_t* out_size) override;
virtual char* WriteToStringPtr(JsonValue* handle, yyjson_write_flag write_flg, size_t* out_size) override;
virtual JsonValue* ApplyJsonPatch(JsonValue* target, JsonValue* patch, bool result_mutable,
char* error, size_t error_size) override;
virtual bool JsonPatchInPlace(JsonValue* target, JsonValue* patch,
char* error, size_t error_size) override;
virtual JsonValue* ApplyMergePatch(JsonValue* target, JsonValue* patch, bool result_mutable,
char* error, size_t error_size) override;
virtual bool MergePatchInPlace(JsonValue* target, JsonValue* patch,
char* error, size_t error_size) override;
virtual bool WriteToFile(JsonValue* handle, const char* path, yyjson_write_flag write_flg,
char* error, size_t error_size) override;
virtual bool Equals(JsonValue* handle1, JsonValue* handle2) override;
@ -166,6 +345,7 @@ public:
virtual bool IsMutable(JsonValue* handle) override;
virtual bool IsImmutable(JsonValue* handle) override;
virtual size_t GetReadSize(JsonValue* handle) override;
virtual size_t GetRefCount(JsonValue* handle) override;
// ========== Object Operations ==========
virtual JsonValue* ObjectInit() override;
@ -250,13 +430,12 @@ public:
virtual bool ArrayRemove(JsonValue* handle, size_t index) override;
virtual bool ArrayRemoveFirst(JsonValue* handle) override;
virtual bool ArrayRemoveLast(JsonValue* handle) override;
virtual bool ArrayRemoveRange(JsonValue* handle, size_t start_index, size_t end_index) override;
virtual bool ArrayRemoveRange(JsonValue* handle, size_t start_index, size_t count) override;
virtual bool ArrayClear(JsonValue* handle) override;
virtual int ArrayIndexOfBool(JsonValue* handle, bool search_value) override;
virtual int ArrayIndexOfString(JsonValue* handle, const char* search_value) override;
virtual int ArrayIndexOfInt(JsonValue* handle, int search_value) override;
virtual int ArrayIndexOfInt64(JsonValue* handle, int64_t search_value) override;
virtual int ArrayIndexOfUint64(JsonValue* handle, uint64_t search_value) override;
virtual int ArrayIndexOfInt64(JsonValue* handle, std::variant<int64_t, uint64_t> search_value) override;
virtual int ArrayIndexOfFloat(JsonValue* handle, double search_value) override;
virtual bool ArraySort(JsonValue* handle, JSON_SORT_ORDER sort_mode) override;
@ -317,6 +496,7 @@ public:
// ========== Array Iterator Operations ==========
virtual JsonArrIter* ArrIterInit(JsonValue* handle) override;
virtual JsonArrIter* ArrIterWith(JsonValue* handle) override;
virtual bool ArrIterReset(JsonArrIter* iter) override;
virtual JsonValue* ArrIterNext(JsonArrIter* iter) override;
virtual bool ArrIterHasNext(JsonArrIter* iter) override;
virtual size_t ArrIterGetIndex(JsonArrIter* iter) override;
@ -325,6 +505,7 @@ public:
// ========== Object Iterator Operations ==========
virtual JsonObjIter* ObjIterInit(JsonValue* handle) override;
virtual JsonObjIter* ObjIterWith(JsonValue* handle) override;
virtual bool ObjIterReset(JsonObjIter* iter) override;
virtual void* ObjIterNext(JsonObjIter* iter) override;
virtual bool ObjIterHasNext(JsonObjIter* iter) override;
virtual JsonValue* ObjIterGetVal(JsonObjIter* iter, void* key) override;
@ -378,10 +559,11 @@ private:
// Helper methods
static std::unique_ptr<JsonValue> CreateWrapper();
static std::shared_ptr<yyjson_mut_doc> WrapDocument(yyjson_mut_doc* doc);
static std::shared_ptr<yyjson_mut_doc> CopyDocument(yyjson_doc* doc);
static std::shared_ptr<yyjson_mut_doc> CreateDocument();
static std::shared_ptr<yyjson_doc> WrapImmutableDocument(yyjson_doc* doc);
static RefPtr<RefCountedMutDoc> WrapDocument(yyjson_mut_doc* doc);
static RefPtr<RefCountedMutDoc> CopyDocument(yyjson_doc* doc);
static RefPtr<RefCountedMutDoc> CreateDocument();
static RefPtr<RefCountedImmutableDoc> WrapImmutableDocument(yyjson_doc* doc);
static RefPtr<RefCountedMutDoc> CloneValueToMutable(JsonValue* value);
// Pack helper methods
static const char* SkipSeparators(const char* ptr);
@ -398,4 +580,4 @@ private:
static PtrGetValueResult PtrGetValueInternal(JsonValue* handle, const char* path);
};
#endif // _INCLUDE_SM_JSON_JSONMANAGER_H_
#endif // _INCLUDE_JSONMANAGER_H_

View File

@ -147,7 +147,14 @@ static cell_t CreateAndReturnObjIterHandle(IPluginContext* pContext, JsonObjIter
return handle;
}
static cell_t json_pack(IPluginContext* pContext, const cell_t* params) {
static cell_t json_pack(IPluginContext* pContext, const cell_t* params)
{
// SourcePawn has a limit of 32 parameters (defined SP_MAX_EXEC_PARAMS)
// including the format string, so we need to check if the number of parameters is less than the limit
if (params[0] > SP_MAX_EXEC_PARAMS - 1) {
return pContext->ThrowNativeError("Too many parameters (max %d)", SP_MAX_EXEC_PARAMS - 1);
}
char* fmt;
pContext->LocalToString(params[1], &fmt);
@ -170,7 +177,7 @@ static cell_t json_doc_parse(IPluginContext* pContext, const cell_t* params)
bool is_file = params[2];
bool is_mutable_doc = params[3];
yyjson_read_flag read_flg = static_cast<yyjson_read_flag>(params[4]);
uint32_t read_flg = static_cast<uint32_t>(params[4]);
char error[JSON_ERROR_BUFFER_SIZE];
JsonValue* pJSONValue = g_pJsonManager->ParseJSON(str, is_file, is_mutable_doc, read_flg, error, sizeof(error));
@ -224,7 +231,7 @@ static cell_t json_obj_parse_str(IPluginContext* pContext, const cell_t* params)
{
char* str;
pContext->LocalToString(params[1], &str);
yyjson_read_flag read_flg = static_cast<yyjson_read_flag>(params[2]);
uint32_t read_flg = static_cast<uint32_t>(params[2]);
char error[JSON_PACK_ERROR_SIZE];
JsonValue* pJSONValue = g_pJsonManager->ObjectParseString(str, read_flg, error, sizeof(error));
@ -240,7 +247,7 @@ static cell_t json_obj_parse_file(IPluginContext* pContext, const cell_t* params
{
char* path;
pContext->LocalToString(params[1], &path);
yyjson_read_flag read_flg = static_cast<yyjson_read_flag>(params[2]);
uint32_t read_flg = static_cast<uint32_t>(params[2]);
char error[JSON_PACK_ERROR_SIZE];
JsonValue* pJSONValue = g_pJsonManager->ObjectParseFile(path, read_flg, error, sizeof(error));
@ -256,7 +263,7 @@ static cell_t json_arr_parse_str(IPluginContext* pContext, const cell_t* params)
{
char* str;
pContext->LocalToString(params[1], &str);
yyjson_read_flag read_flg = static_cast<yyjson_read_flag>(params[2]);
uint32_t read_flg = static_cast<uint32_t>(params[2]);
char error[JSON_PACK_ERROR_SIZE];
JsonValue* pJSONValue = g_pJsonManager->ArrayParseString(str, read_flg, error, sizeof(error));
@ -272,7 +279,7 @@ static cell_t json_arr_parse_file(IPluginContext* pContext, const cell_t* params
{
char* path;
pContext->LocalToString(params[1], &path);
yyjson_read_flag read_flg = static_cast<yyjson_read_flag>(params[2]);
uint32_t read_flg = static_cast<uint32_t>(params[2]);
char error[JSON_PACK_ERROR_SIZE];
JsonValue* pJSONValue = g_pJsonManager->ArrayParseFile(path, read_flg, error, sizeof(error));
@ -325,35 +332,14 @@ static cell_t json_arr_index_of_integer64(IPluginContext* pContext, const cell_t
char* searchStr;
pContext->LocalToString(params[2], &searchStr);
char* endptr;
errno = 0;
long long searchValue = strtoll(searchStr, &endptr, 10);
std::variant<int64_t, uint64_t> variant_value;
char error[JSON_ERROR_BUFFER_SIZE];
if (errno == ERANGE || *endptr != '\0') {
return pContext->ThrowNativeError("Invalid integer64 value: %s", searchStr);
if (!g_pJsonManager->ParseInt64Variant(searchStr, &variant_value, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
}
return g_pJsonManager->ArrayIndexOfInt64(handle, searchValue);
}
static cell_t json_arr_index_of_uint64(IPluginContext* pContext, const cell_t* params)
{
JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]);
if (!handle) return 0;
char* searchStr;
pContext->LocalToString(params[2], &searchStr);
char* endptr;
errno = 0;
unsigned long long searchValue = strtoull(searchStr, &endptr, 10);
if (errno == ERANGE || *endptr != '\0') {
return pContext->ThrowNativeError("Invalid unsigned integer64 value: %s", searchStr);
}
return g_pJsonManager->ArrayIndexOfUint64(handle, searchValue);
return g_pJsonManager->ArrayIndexOfInt64(handle, variant_value);
}
static cell_t json_arr_index_of_float(IPluginContext* pContext, const cell_t* params)
@ -362,7 +348,7 @@ static cell_t json_arr_index_of_float(IPluginContext* pContext, const cell_t* pa
if (!handle) return 0;
double searchValue = static_cast<double>(sp_ctof(params[2]));
float searchValue = sp_ctof(params[2]);
return g_pJsonManager->ArrayIndexOfFloat(handle, searchValue);
}
@ -597,6 +583,7 @@ static cell_t json_create_integer64(IPluginContext* pContext, const cell_t* para
std::variant<int64_t, uint64_t> variant_value;
char error[JSON_ERROR_BUFFER_SIZE];
if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
}
@ -694,8 +681,8 @@ static cell_t json_get_str(IPluginContext* pContext, const cell_t* params)
if (!handle) return 0;
const char* str = nullptr;
size_t len = 0;
const char* str;
size_t len;
if (!g_pJsonManager->GetString(handle, &str, &len)) {
return pContext->ThrowNativeError("Type mismatch: expected string value");
@ -729,7 +716,7 @@ static cell_t json_get_serialized_size(IPluginContext* pContext, const cell_t* p
if (!handle) return 0;
yyjson_write_flag write_flg = static_cast<yyjson_write_flag>(params[2]);
uint32_t write_flg = static_cast<uint32_t>(params[2]);
size_t size = g_pJsonManager->GetSerializedSize(handle, write_flg);
return static_cast<cell_t>(size);
@ -747,6 +734,15 @@ static cell_t json_get_read_size(IPluginContext* pContext, const cell_t* params)
return static_cast<cell_t>(size);
}
static cell_t json_get_ref_count(IPluginContext* pContext, const cell_t* params)
{
JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]);
if (!handle) return 0;
return g_pJsonManager->GetRefCount(handle);
}
static cell_t json_create_null(IPluginContext* pContext, const cell_t* params)
{
JsonValue* pJSONValue = g_pJsonManager->CreateNull();
@ -890,7 +886,11 @@ static cell_t json_arr_get_val(IPluginContext* pContext, const cell_t* params)
if (!handle) return 0;
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
JsonValue* pJSONValue = g_pJsonManager->ArrayGet(handle, index);
@ -937,7 +937,11 @@ static cell_t json_arr_get_bool(IPluginContext* pContext, const cell_t* params)
if (!handle) return 0;
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
bool value;
if (!g_pJsonManager->ArrayGetBool(handle, index, &value)) {
@ -953,7 +957,11 @@ static cell_t json_arr_get_float(IPluginContext* pContext, const cell_t* params)
if (!handle) return 0;
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
double value;
if (!g_pJsonManager->ArrayGetFloat(handle, index, &value)) {
@ -969,7 +977,11 @@ static cell_t json_arr_get_integer(IPluginContext* pContext, const cell_t* param
if (!handle) return 0;
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
int value;
if (!g_pJsonManager->ArrayGetInt(handle, index, &value)) {
@ -985,9 +997,13 @@ static cell_t json_arr_get_integer64(IPluginContext* pContext, const cell_t* par
if (!handle) return 0;
size_t index = static_cast<size_t>(params[2]);
std::variant<int64_t, uint64_t> value;
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
std::variant<int64_t, uint64_t> value;
if (!g_pJsonManager->ArrayGetInt64(handle, index, &value)) {
return pContext->ThrowNativeError("Failed to get integer64 at index %d", index);
}
@ -1009,10 +1025,14 @@ static cell_t json_arr_get_str(IPluginContext* pContext, const cell_t* params)
if (!handle) return 0;
size_t index = static_cast<size_t>(params[2]);
const char* str = nullptr;
size_t len = 0;
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
const char* str;
size_t len;
if (!g_pJsonManager->ArrayGetString(handle, index, &str, &len)) {
return pContext->ThrowNativeError("Failed to get string at index %d", index);
}
@ -1033,7 +1053,11 @@ static cell_t json_arr_is_null(IPluginContext* pContext, const cell_t* params)
if (!handle) return 0;
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
return g_pJsonManager->ArrayIsNull(handle, index);
}
@ -1049,7 +1073,12 @@ static cell_t json_arr_replace_val(IPluginContext* pContext, const cell_t* param
return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array");
}
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
return g_pJsonManager->ArrayReplace(handle1, index, handle2);
}
@ -1063,7 +1092,12 @@ static cell_t json_arr_replace_bool(IPluginContext* pContext, const cell_t* para
return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array");
}
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
return g_pJsonManager->ArrayReplaceBool(handle, index, params[3]);
}
@ -1077,7 +1111,12 @@ static cell_t json_arr_replace_float(IPluginContext* pContext, const cell_t* par
return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array");
}
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
return g_pJsonManager->ArrayReplaceFloat(handle, index, sp_ctof(params[3]));
}
@ -1091,7 +1130,12 @@ static cell_t json_arr_replace_integer(IPluginContext* pContext, const cell_t* p
return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array");
}
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
return g_pJsonManager->ArrayReplaceInt(handle, index, params[3]);
}
@ -1110,11 +1154,17 @@ static cell_t json_arr_replace_integer64(IPluginContext* pContext, const cell_t*
std::variant<int64_t, uint64_t> variant_value;
char error[JSON_ERROR_BUFFER_SIZE];
if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
}
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
return g_pJsonManager->ArrayReplaceInt64(handle, index, variant_value);
}
@ -1128,7 +1178,12 @@ static cell_t json_arr_replace_null(IPluginContext* pContext, const cell_t* para
return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array");
}
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
return g_pJsonManager->ArrayReplaceNull(handle, index);
}
@ -1145,7 +1200,12 @@ static cell_t json_arr_replace_str(IPluginContext* pContext, const cell_t* param
char* val;
pContext->LocalToString(params[3], &val);
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
return g_pJsonManager->ArrayReplaceString(handle, index, val);
}
@ -1217,6 +1277,7 @@ static cell_t json_arr_append_integer64(IPluginContext* pContext, const cell_t*
std::variant<int64_t, uint64_t> variant_value;
char error[JSON_ERROR_BUFFER_SIZE];
if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
}
@ -1262,7 +1323,17 @@ static cell_t json_arr_insert(IPluginContext* pContext, const cell_t* params)
return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array");
}
size_t index = params[2];
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
size_t arr_size = g_pJsonManager->ArrayGetSize(handle);
if (index > arr_size) {
return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size);
}
JsonValue* value = g_pJsonManager->GetFromHandle(pContext, params[3]);
if (!value) return 0;
@ -1278,7 +1349,17 @@ static cell_t json_arr_insert_bool(IPluginContext* pContext, const cell_t* param
return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array");
}
size_t index = params[2];
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
size_t arr_size = g_pJsonManager->ArrayGetSize(handle);
if (index > arr_size) {
return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size);
}
bool value = params[3] != 0;
return g_pJsonManager->ArrayInsertBool(handle, index, value);
@ -1293,7 +1374,17 @@ static cell_t json_arr_insert_int(IPluginContext* pContext, const cell_t* params
return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array");
}
size_t index = params[2];
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
size_t arr_size = g_pJsonManager->ArrayGetSize(handle);
if (index > arr_size) {
return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size);
}
int value = params[3];
return g_pJsonManager->ArrayInsertInt(handle, index, value);
@ -1308,12 +1399,23 @@ static cell_t json_arr_insert_int64(IPluginContext* pContext, const cell_t* para
return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array");
}
size_t index = params[2];
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
size_t arr_size = g_pJsonManager->ArrayGetSize(handle);
if (index > arr_size) {
return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size);
}
char* value;
pContext->LocalToString(params[3], &value);
std::variant<int64_t, uint64_t> variant_value;
char error[JSON_ERROR_BUFFER_SIZE];
if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
}
@ -1330,8 +1432,18 @@ static cell_t json_arr_insert_float(IPluginContext* pContext, const cell_t* para
return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array");
}
size_t index = params[2];
double value = sp_ctof(params[3]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
size_t arr_size = g_pJsonManager->ArrayGetSize(handle);
if (index > arr_size) {
return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size);
}
float value = sp_ctof(params[3]);
return g_pJsonManager->ArrayInsertFloat(handle, index, value);
}
@ -1345,7 +1457,17 @@ static cell_t json_arr_insert_str(IPluginContext* pContext, const cell_t* params
return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array");
}
size_t index = params[2];
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
size_t arr_size = g_pJsonManager->ArrayGetSize(handle);
if (index > arr_size) {
return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size);
}
char* str;
pContext->LocalToString(params[3], &str);
@ -1361,7 +1483,17 @@ static cell_t json_arr_insert_null(IPluginContext* pContext, const cell_t* param
return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array");
}
size_t index = params[2];
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
size_t arr_size = g_pJsonManager->ArrayGetSize(handle);
if (index > arr_size) {
return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size);
}
return g_pJsonManager->ArrayInsertNull(handle, index);
}
@ -1423,6 +1555,7 @@ static cell_t json_arr_prepend_int64(IPluginContext* pContext, const cell_t* par
std::variant<int64_t, uint64_t> variant_value;
char error[JSON_ERROR_BUFFER_SIZE];
if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
}
@ -1439,7 +1572,7 @@ static cell_t json_arr_prepend_float(IPluginContext* pContext, const cell_t* par
return pContext->ThrowNativeError("Cannot prepend value to an immutable JSON array");
}
double value = sp_ctof(params[2]);
float value = sp_ctof(params[2]);
return g_pJsonManager->ArrayPrependFloat(handle, value);
}
@ -1481,7 +1614,12 @@ static cell_t json_arr_remove(IPluginContext* pContext, const cell_t* params)
return pContext->ThrowNativeError("Cannot remove value from an immutable JSON array");
}
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
return g_pJsonManager->ArrayRemove(handle, index);
}
@ -1521,10 +1659,18 @@ static cell_t json_arr_remove_range(IPluginContext* pContext, const cell_t* para
return pContext->ThrowNativeError("Cannot remove value from an immutable JSON array");
}
size_t start_index = static_cast<size_t>(params[2]);
size_t end_index = static_cast<size_t>(params[3]);
cell_t start_index_param = params[2];
cell_t count_param = params[3];
if (start_index_param < 0) {
return pContext->ThrowNativeError("Start index must be >= 0 (got %d)", start_index_param);
}
if (count_param < 0) {
return pContext->ThrowNativeError("Count must be >= 0 (got %d)", count_param);
}
return g_pJsonManager->ArrayRemoveRange(handle, start_index, end_index);
size_t start_index = static_cast<size_t>(start_index_param);
size_t count = static_cast<size_t>(count_param);
return g_pJsonManager->ArrayRemoveRange(handle, start_index, count);
}
static cell_t json_arr_clear(IPluginContext* pContext, const cell_t* params)
@ -1547,22 +1693,24 @@ static cell_t json_doc_write_to_str(IPluginContext* pContext, const cell_t* para
if (!handle) return 0;
size_t buffer_size = static_cast<size_t>(params[3]);
yyjson_write_flag write_flg = static_cast<yyjson_write_flag>(params[4]);
uint32_t write_flg = static_cast<uint32_t>(params[4]);
char* temp_buffer = (char*)malloc(buffer_size);
if (!temp_buffer) {
return pContext->ThrowNativeError("Failed to allocate buffer");
size_t json_size;
char* json_str = g_pJsonManager->WriteToStringPtr(handle, write_flg, &json_size);
if (!json_str) {
return pContext->ThrowNativeError("Failed to serialize JSON");
}
size_t output_len = 0;
if (!g_pJsonManager->WriteToString(handle, temp_buffer, buffer_size, write_flg, &output_len)) {
free(temp_buffer);
return pContext->ThrowNativeError("Buffer too small or write failed");
if (json_size > buffer_size) {
free(json_str);
return pContext->ThrowNativeError("Buffer too small (need %d, have %d)", json_size, buffer_size);
}
pContext->StringToLocalUTF8(params[2], buffer_size, temp_buffer, nullptr);
free(temp_buffer);
return static_cast<cell_t>(output_len);
pContext->StringToLocalUTF8(params[2], buffer_size, json_str, nullptr);
free(json_str);
return static_cast<cell_t>(json_size);
}
static cell_t json_doc_write_to_file(IPluginContext* pContext, const cell_t* params)
@ -1573,7 +1721,7 @@ static cell_t json_doc_write_to_file(IPluginContext* pContext, const cell_t* par
char* path;
pContext->LocalToString(params[2], &path);
yyjson_write_flag write_flg = static_cast<yyjson_write_flag>(params[3]);
uint32_t write_flg = static_cast<uint32_t>(params[3]);
char error[JSON_PACK_ERROR_SIZE];
if (!g_pJsonManager->WriteToFile(handle, path, write_flg, error, sizeof(error))) {
@ -1599,8 +1747,12 @@ static cell_t json_obj_get_key(IPluginContext* pContext, const cell_t* params)
if (!handle) return 0;
size_t index = static_cast<size_t>(params[2]);
const char* key = nullptr;
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
const char* key;
if (!g_pJsonManager->ObjectGetKey(handle, index, &key)) {
return pContext->ThrowNativeError("Index %d is out of bounds", index);
@ -1616,7 +1768,11 @@ static cell_t json_obj_get_val_at(IPluginContext* pContext, const cell_t* params
if (!handle) return 0;
size_t index = static_cast<size_t>(params[2]);
cell_t index_param = params[2];
if (index_param < 0) {
return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param);
}
size_t index = static_cast<size_t>(index_param);
JsonValue* pJSONValue = g_pJsonManager->ObjectGetValueAt(handle, index);
@ -1730,8 +1886,8 @@ static cell_t json_obj_get_str(IPluginContext* pContext, const cell_t* params)
char* key;
pContext->LocalToString(params[2], &key);
const char* str = nullptr;
size_t len = 0;
const char* str;
size_t len;
if (!g_pJsonManager->ObjectGetString(handle, key, &str, &len)) {
return pContext->ThrowNativeError("Failed to get string for key '%s'", key);
}
@ -1768,7 +1924,7 @@ static cell_t json_obj_is_null(IPluginContext* pContext, const cell_t* params)
char* key;
pContext->LocalToString(params[2], &key);
bool is_null = false;
bool is_null;
if (!g_pJsonManager->ObjectIsNull(handle, key, &is_null)) {
return pContext->ThrowNativeError("Key not found: %s", key);
}
@ -1896,6 +2052,7 @@ static cell_t json_obj_set_integer64(IPluginContext* pContext, const cell_t* par
std::variant<int64_t, uint64_t> variant_value;
char error[JSON_ERROR_BUFFER_SIZE];
if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
}
@ -2060,8 +2217,8 @@ static cell_t json_ptr_get_str(IPluginContext* pContext, const cell_t* params)
char* path;
pContext->LocalToString(params[2], &path);
const char* str = nullptr;
size_t len = 0;
const char* str;
size_t len;
char error[JSON_PACK_ERROR_SIZE];
if (!g_pJsonManager->PtrGetString(handle, path, &str, &len, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
@ -2214,6 +2371,7 @@ static cell_t json_ptr_set_integer64(IPluginContext* pContext, const cell_t* par
std::variant<int64_t, uint64_t> variant_value;
char error[JSON_PACK_ERROR_SIZE];
if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
}
@ -2369,6 +2527,7 @@ static cell_t json_ptr_add_integer64(IPluginContext* pContext, const cell_t* par
std::variant<int64_t, uint64_t> variant_value;
char error[JSON_PACK_ERROR_SIZE];
if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
}
@ -2554,8 +2713,8 @@ static cell_t json_ptr_try_get_str(IPluginContext* pContext, const cell_t* param
char* path;
pContext->LocalToString(params[2], &path);
const char* str = nullptr;
size_t len = 0;
const char* str;
size_t len;
if (!g_pJsonManager->PtrTryGetString(handle, path, &str, &len)) {
return 0;
@ -2576,8 +2735,8 @@ static cell_t json_obj_foreach(IPluginContext* pContext, const cell_t* params)
JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]);
if (!handle) return 0;
const char* key = nullptr;
JsonValue* pJSONValue = nullptr;
const char* key;
JsonValue* pJSONValue;
if (!g_pJsonManager->ObjectForeachNext(handle, &key, nullptr, &pJSONValue)) {
return false;
@ -2593,8 +2752,8 @@ static cell_t json_arr_foreach(IPluginContext* pContext, const cell_t* params)
JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]);
if (!handle) return 0;
size_t index = 0;
JsonValue* pJSONValue = nullptr;
size_t index;
JsonValue* pJSONValue;
if (!g_pJsonManager->ArrayForeachNext(handle, &index, &pJSONValue)) {
return false;
@ -2612,7 +2771,7 @@ static cell_t json_obj_foreach_key(IPluginContext* pContext, const cell_t* param
JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]);
if (!handle) return 0;
const char* key = nullptr;
const char* key;
if (!g_pJsonManager->ObjectForeachKeyNext(handle, &key, nullptr)) {
return false;
@ -2628,7 +2787,7 @@ static cell_t json_arr_foreach_index(IPluginContext* pContext, const cell_t* par
JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]);
if (!handle) return 0;
size_t index = 0;
size_t index;
if (!g_pJsonManager->ArrayForeachIndexNext(handle, &index)) {
return false;
@ -2715,6 +2874,84 @@ static cell_t json_doc_to_immutable(IPluginContext* pContext, const cell_t* para
return CreateAndReturnHandle(pContext, pJSONValue, "immutable JSON document");
}
static cell_t json_apply_json_patch(IPluginContext* pContext, const cell_t* params)
{
JsonValue* target = g_pJsonManager->GetFromHandle(pContext, params[1]);
JsonValue* patch = g_pJsonManager->GetFromHandle(pContext, params[2]);
if (!target || !patch) return 0;
bool resultMutable = params[3] != 0;
char error[JSON_ERROR_BUFFER_SIZE] = {0};
JsonValue* result = g_pJsonManager->ApplyJsonPatch(target, patch, resultMutable, error, sizeof(error));
if (!result) {
if (error[0] != '\0') {
return pContext->ThrowNativeError("%s", error);
}
return pContext->ThrowNativeError("Failed to apply JSON Patch");
}
return CreateAndReturnHandle(pContext, result, "JSON patch result");
}
static cell_t json_json_patch_in_place(IPluginContext* pContext, const cell_t* params)
{
JsonValue* target = g_pJsonManager->GetFromHandle(pContext, params[1]);
JsonValue* patch = g_pJsonManager->GetFromHandle(pContext, params[2]);
if (!target || !patch) return 0;
char error[JSON_ERROR_BUFFER_SIZE] = {0};
if (!g_pJsonManager->JsonPatchInPlace(target, patch, error, sizeof(error))) {
if (error[0] != '\0') {
return pContext->ThrowNativeError("%s", error);
}
return pContext->ThrowNativeError("Failed to apply JSON Patch in place");
}
return 1;
}
static cell_t json_apply_merge_patch(IPluginContext* pContext, const cell_t* params)
{
JsonValue* target = g_pJsonManager->GetFromHandle(pContext, params[1]);
JsonValue* patch = g_pJsonManager->GetFromHandle(pContext, params[2]);
if (!target || !patch) return 0;
bool resultMutable = params[3] != 0;
char error[JSON_ERROR_BUFFER_SIZE] = {0};
JsonValue* result = g_pJsonManager->ApplyMergePatch(target, patch, resultMutable, error, sizeof(error));
if (!result) {
if (error[0] != '\0') {
return pContext->ThrowNativeError("%s", error);
}
return pContext->ThrowNativeError("Failed to apply JSON Merge Patch");
}
return CreateAndReturnHandle(pContext, result, "JSON merge patch result");
}
static cell_t json_merge_patch_in_place(IPluginContext* pContext, const cell_t* params)
{
JsonValue* target = g_pJsonManager->GetFromHandle(pContext, params[1]);
JsonValue* patch = g_pJsonManager->GetFromHandle(pContext, params[2]);
if (!target || !patch) return 0;
char error[JSON_ERROR_BUFFER_SIZE] = {0};
if (!g_pJsonManager->MergePatchInPlace(target, patch, error, sizeof(error))) {
if (error[0] != '\0') {
return pContext->ThrowNativeError("%s", error);
}
return pContext->ThrowNativeError("Failed to apply JSON Merge Patch in place");
}
return 1;
}
static cell_t json_arr_iter_init(IPluginContext* pContext, const cell_t* params)
{
JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]);
@ -2769,6 +3006,14 @@ static cell_t json_arr_iter_remove(IPluginContext* pContext, const cell_t* param
return removed != nullptr;
}
static cell_t json_arr_iter_reset(IPluginContext* pContext, const cell_t* params)
{
JsonArrIter* iter = g_pJsonManager->GetArrIterFromHandle(pContext, params[1]);
if (!iter) return 0;
return g_pJsonManager->ArrIterReset(iter);
}
static cell_t json_obj_iter_init(IPluginContext* pContext, const cell_t* params)
{
JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]);
@ -2787,12 +3032,8 @@ static cell_t json_obj_iter_next(IPluginContext* pContext, const cell_t* params)
if (!key) return 0;
const char* key_str;
if (iter->IsMutable()) {
key_str = yyjson_mut_get_str(reinterpret_cast<yyjson_mut_val*>(key));
} else {
key_str = yyjson_get_str(reinterpret_cast<yyjson_val*>(key));
}
size_t key_len;
g_pJsonManager->GetString(reinterpret_cast<JsonValue*>(key), &key_str, &key_len);
pContext->StringToLocalUTF8(params[2], params[3], key_str, nullptr);
return 1;
}
@ -2810,13 +3051,7 @@ static cell_t json_obj_iter_get_val(IPluginContext* pContext, const cell_t* para
JsonObjIter* iter = g_pJsonManager->GetObjIterFromHandle(pContext, params[1]);
if (!iter) return 0;
void* key = nullptr;
if (iter->IsMutable()) {
key = iter->m_currentKey;
} else {
key = iter->m_currentKey;
}
void* key = iter->m_currentKey;
if (!key) {
return pContext->ThrowNativeError("Iterator not positioned at a valid key (call Next() first)");
}
@ -2871,14 +3106,22 @@ static cell_t json_obj_iter_remove(IPluginContext* pContext, const cell_t* param
return removed != nullptr;
}
static cell_t json_obj_iter_reset(IPluginContext* pContext, const cell_t* params)
{
JsonObjIter* iter = g_pJsonManager->GetObjIterFromHandle(pContext, params[1]);
if (!iter) return 0;
return g_pJsonManager->ObjIterReset(iter);
}
static cell_t json_read_number(IPluginContext* pContext, const cell_t* params)
{
char* dat;
pContext->LocalToString(params[1], &dat);
yyjson_read_flag read_flg = static_cast<yyjson_read_flag>(params[2]);
uint32_t read_flg = static_cast<uint32_t>(params[2]);
char error[JSON_ERROR_BUFFER_SIZE];
size_t consumed = 0;
size_t consumed;
JsonValue* pJSONValue = g_pJsonManager->ReadNumber(dat, read_flg, error, sizeof(error), &consumed);
if (!pJSONValue) {
@ -2901,7 +3144,12 @@ static cell_t json_write_number(IPluginContext* pContext, const cell_t* params)
JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]);
if (!handle) return 0;
size_t buffer_size = static_cast<size_t>(params[3]);
cell_t buffer_size_param = params[3];
if (buffer_size_param <= 0) {
return pContext->ThrowNativeError("Buffer size must be > 0 (got %d)", buffer_size_param);
}
size_t buffer_size = static_cast<size_t>(buffer_size_param);
char* temp_buffer = (char*)malloc(buffer_size);
if (!temp_buffer) {
return pContext->ThrowNativeError("Failed to allocate buffer");
@ -2946,8 +3194,12 @@ static cell_t json_set_fp_to_fixed(IPluginContext* pContext, const cell_t* param
if (!handle) return 0;
int prec = params[2];
if (prec < 1 || prec > 7) {
return pContext->ThrowNativeError("Precision out of range (1-7)");
}
if (!g_pJsonManager->SetFpToFixed(handle, prec)) {
return pContext->ThrowNativeError("Failed to set floating-point format to fixed (value is not a floating-point number or precision out of range 1-15)");
return pContext->ThrowNativeError("Failed to set floating-point format to fixed (value is not a floating-point number or precision out of range 1-7)");
}
return 1;
@ -2989,6 +3241,7 @@ static cell_t json_set_int64(IPluginContext* pContext, const cell_t* params)
std::variant<int64_t, uint64_t> variant_value;
char error[JSON_ERROR_BUFFER_SIZE];
if (!g_pJsonManager->ParseInt64Variant(str, &variant_value, error, sizeof(error))) {
return pContext->ThrowNativeError("%s", error);
}
@ -3126,7 +3379,6 @@ const sp_nativeinfo_t g_JsonNatives[] =
{"JSONArray.IndexOfString", json_arr_index_of_str},
{"JSONArray.IndexOfInt", json_arr_index_of_int},
{"JSONArray.IndexOfInt64", json_arr_index_of_integer64},
{"JSONArray.IndexOfUint64", json_arr_index_of_uint64},
{"JSONArray.IndexOfFloat", json_arr_index_of_float},
{"JSONArray.Sort", json_arr_sort},
@ -3140,6 +3392,7 @@ const sp_nativeinfo_t g_JsonNatives[] =
{"JSON.GetTypeDesc", json_get_type_desc},
{"JSON.GetSerializedSize", json_get_serialized_size},
{"JSON.ReadSize.get", json_get_read_size},
{"JSON.RefCount.get", json_get_ref_count},
{"JSON.Type.get", json_get_type},
{"JSON.SubType.get", json_get_subtype},
{"JSON.IsArray.get", json_is_array},
@ -3163,6 +3416,10 @@ const sp_nativeinfo_t g_JsonNatives[] =
{"JSON.ForeachIndex", json_arr_foreach_index},
{"JSON.ToMutable", json_doc_to_mutable},
{"JSON.ToImmutable", json_doc_to_immutable},
{"JSON.ApplyJsonPatch", json_apply_json_patch},
{"JSON.JsonPatchInPlace", json_json_patch_in_place},
{"JSON.ApplyMergePatch", json_apply_merge_patch},
{"JSON.MergePatchInPlace", json_merge_patch_in_place},
{"JSON.ReadNumber", json_read_number},
{"JSON.WriteNumber", json_write_number},
{"JSON.SetFpToFloat", json_set_fp_to_float},
@ -3225,6 +3482,7 @@ const sp_nativeinfo_t g_JsonNatives[] =
{"JSONArrIter.HasNext.get", json_arr_iter_has_next},
{"JSONArrIter.Index.get", json_arr_iter_get_index},
{"JSONArrIter.Remove", json_arr_iter_remove},
{"JSONArrIter.Reset", json_arr_iter_reset},
// JSONObjIter
{"JSONObjIter.JSONObjIter", json_obj_iter_init},
@ -3234,6 +3492,7 @@ const sp_nativeinfo_t g_JsonNatives[] =
{"JSONObjIter.Get", json_obj_iter_get},
{"JSONObjIter.Index.get", json_obj_iter_get_index},
{"JSONObjIter.Remove", json_obj_iter_remove},
{"JSONObjIter.Reset", json_obj_iter_reset},
{nullptr, nullptr}
};

View File

@ -42,7 +42,6 @@ bool JsonExtension::SDK_OnLoad(char* error, size_t maxlen, bool late)
return false;
}
// Delete the existing instance if it exists
if (g_pJsonManager) {
delete g_pJsonManager;
g_pJsonManager = nullptr;

View File

@ -3,7 +3,7 @@
#define SMEXT_CONF_NAME "SourceMod JSON Extension"
#define SMEXT_CONF_DESCRIPTION "Provide JSON Native"
#define SMEXT_CONF_VERSION "1.1.5b"
#define SMEXT_CONF_VERSION "1.1.5e"
#define SMEXT_CONF_AUTHOR "ProjectSky"
#define SMEXT_CONF_URL "https://github.com/ProjectSky/sm-ext-yyjson"
#define SMEXT_CONF_LOGTAG "json"

View File

@ -71,12 +71,17 @@ enum JSON_WRITE_FLAG
JSON_WRITE_FP_TO_FLOAT = 1 << 27 // Write floating-point numbers using single-precision (float)
}
/** Write floating-point number using fixed-point notation
- This is similar to ECMAScript Number.prototype.toFixed(prec) but with trailing zeros removed. The prec ranges from 1 to 15
- This will produce shorter output but may lose some precision
*/
/**
* Write floating-point number using fixed-point notation
* This is similar to ECMAScript Number.prototype.toFixed(prec) but with trailing zeros removed
*
* @param n Precision digits (1-7)
* @return JSON write flag with precision setting
* @note This will produce shorter output but may lose some precision
*/
stock JSON_WRITE_FLAG JSON_WRITE_FP_TO_FIXED(int n)
{
n = (n < 1) ? 1 : (n > 7 ? 7 : n);
return view_as<JSON_WRITE_FLAG>(n << 28);
}
@ -103,6 +108,10 @@ methodmap JSON < Handle
* - [: start array
* - ]: end array
*
* @note Needs to be freed using delete or CloseHandle()
* @note There is a limit of 32 parameters (defined in SourcePawn)
* including the format string, so the number of arguments must be less than or equal to 31
*
* @param format Format string
* @param ... Arguments based on format string
*
@ -178,6 +187,8 @@ methodmap JSON < Handle
/**
* Converts an immutable JSON document to a mutable one
*
* @note Needs to be freed using delete or CloseHandle()
*
* @return Handle to the new mutable JSON document, INVALID_HANDLE on failure
* @error If the document is already mutable
*/
@ -186,11 +197,59 @@ methodmap JSON < Handle
/**
* Converts a mutable JSON document to an immutable one
*
* @note Needs to be freed using delete or CloseHandle()
*
* @return Handle to the new immutable JSON document, INVALID_HANDLE on failure
* @error If the document is already immutable
*/
public native any ToImmutable();
/**
* Apply a JSON Patch (RFC 6902) to this value and return a new JSON handle
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param patch JSON Patch document
* @param resultMutable True to return a mutable result, false for immutable
*
* @return New JSON handle on success
* @error Throws if the patch cannot be applied
*/
public native any ApplyJsonPatch(const JSON patch, bool resultMutable = false);
/**
* Apply a JSON Patch (RFC 6902) to this value in place
*
* @param patch JSON Patch document
*
* @return True on success, false otherwise
* @error Throws if this value is immutable or patch failed
*/
public native bool JsonPatchInPlace(const JSON patch);
/**
* Apply a JSON Merge Patch (RFC 7396) to this value and return a new JSON handle
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param patch JSON Merge Patch document
* @param resultMutable True to return a mutable result, false for immutable
*
* @return New JSON handle on success
* @error Throws if the merge patch cannot be applied
*/
public native any ApplyMergePatch(const JSON patch, bool resultMutable = false);
/**
* Apply a JSON Merge Patch (RFC 7396) to this value in place
*
* @param patch JSON Merge Patch document
*
* @return True on success, false otherwise
* @error Throws if this value is immutable or merge patch failed
*/
public native bool MergePatchInPlace(const JSON patch);
/**
* Write a document to JSON file with options
*
@ -233,7 +292,7 @@ methodmap JSON < Handle
* Set floating-point number's output format to single-precision
*
* @note Only works on floating-point numbers (not integers)
* @note This affects how the number is serialized when using ToString()
* @note This affects how the number is serialized in all write operations
*
* @param flt True to use single-precision (float), false to use double-precision (double)
*
@ -248,12 +307,12 @@ methodmap JSON < Handle
* @note Only works on floating-point numbers (not integers)
* @note This is similar to ECMAScript Number.prototype.toFixed(prec) but with trailing zeros removed
* @note This will produce shorter output but may lose some precision
* @note This affects how the number is serialized when using ToString()
* @note This affects how the number is serialized in all write operations
*
* @param prec Precision (1-15)
* @param prec Precision (1-7)
*
* @return True on success, false if handle is not a floating-point number or prec is out of range
* @error Invalid handle, handle is not a floating-point number, or precision out of range (1-15)
* @error Invalid handle, handle is not a floating-point number, or precision out of range (1-7)
*/
public native bool SetFpToFixed(int prec);
@ -381,14 +440,13 @@ methodmap JSON < Handle
/**
* Returns the JSON value's type description
*
* @param value JSON handle
* @param buffer String buffer to write to
* @param maxlength Maximum length of the string buffer
*
* @return The return value should be one of these strings: "raw", "null", "string",
* "array", "object", "true", "false", "uint", "sint", "real", "unknown"
*/
public static native void GetTypeDesc(const JSON value, char[] buffer, int maxlength);
public native void GetTypeDesc(char[] buffer, int maxlength);
/**
* Returns whether two JSON values are equal (deep compare)
@ -396,8 +454,8 @@ methodmap JSON < Handle
* @note This function is recursive and may cause a stack overflow if the object level is too deep
* @note the result may be inaccurate if object has duplicate keys
*
* @param value1 JSON handle
* @param value2 JSON handle
* @param value1 First JSON value to compare
* @param value2 Second JSON value to compare
*
* @return True if they are the same, false otherwise
*/
@ -537,6 +595,7 @@ methodmap JSON < Handle
* Get value by a JSON Pointer
*
* @note Needs to be freed using delete or CloseHandle()
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
*
@ -547,6 +606,8 @@ methodmap JSON < Handle
/**
* Get boolean value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
*
* @return boolean value referenced by the JSON pointer
@ -556,6 +617,8 @@ methodmap JSON < Handle
/**
* Get float value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
*
* @return float value referenced by the JSON pointer
@ -565,6 +628,8 @@ methodmap JSON < Handle
/**
* Get integer value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
*
* @return integer value referenced by the JSON pointer
@ -574,17 +639,21 @@ methodmap JSON < Handle
/**
* Get integer64 value by a JSON Pointer (auto-detects signed/unsigned)
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param buffer Buffer to copy to
* @param maxlength Maximum size of the buffer
*
* @return integer64 value referenced by the JSON pointer
* @return True on success, false on failure
*/
public native bool PtrGetInt64(const char[] path, char[] buffer, int maxlength);
/**
* Get string value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param buffer Buffer to copy to
* @param maxlength Maximum size of the buffer
@ -596,6 +665,8 @@ methodmap JSON < Handle
/**
* Get value is null by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
*
* @return True if the value is null, false otherwise
@ -605,6 +676,7 @@ methodmap JSON < Handle
/**
* Get JSON content length (string length, array size, object size)
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
* @note For strings: returns string length including null-terminator
* @note For arrays/objects: returns number of elements
* @note Returns 0 if value is null or type is not string/array/object
@ -618,6 +690,7 @@ methodmap JSON < Handle
/**
* Set value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
* @note The parent nodes will be created if they do not exist.
* If the target value already exists, it will be replaced by the new value
*
@ -631,6 +704,7 @@ methodmap JSON < Handle
/**
* Set boolean value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
* @note The parent nodes will be created if they do not exist.
* If the target value already exists, it will be replaced by the new value
*
@ -644,6 +718,7 @@ methodmap JSON < Handle
/**
* Set float value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
* @note The parent nodes will be created if they do not exist.
* If the target value already exists, it will be replaced by the new value
*
@ -657,6 +732,7 @@ methodmap JSON < Handle
/**
* Set integer value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
* @note The parent nodes will be created if they do not exist.
* If the target value already exists, it will be replaced by the new value
*
@ -670,6 +746,7 @@ methodmap JSON < Handle
/**
* Set integer64 value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
* @note The parent nodes will be created if they do not exist.
* If the target value already exists, it will be replaced by the new value
* @note This function auto-detects whether the value is signed or unsigned
@ -684,6 +761,7 @@ methodmap JSON < Handle
/**
* Set string value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
* @note The parent nodes will be created if they do not exist.
* If the target value already exists, it will be replaced by the new value
*
@ -697,6 +775,7 @@ methodmap JSON < Handle
/**
* Set null value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
* @note The parent nodes will be created if they do not exist.
* If the target value already exists, it will be replaced by the new value
*
@ -709,6 +788,8 @@ methodmap JSON < Handle
/**
* Add (insert) value by a JSON pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param value The value to be added
*
@ -719,6 +800,8 @@ methodmap JSON < Handle
/**
* Add (insert) boolean value by a JSON pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param value The boolean value to be added
*
@ -729,6 +812,8 @@ methodmap JSON < Handle
/**
* Add (insert) float value by a JSON pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param value The float value to be added
*
@ -739,6 +824,8 @@ methodmap JSON < Handle
/**
* Add (insert) integer value by a JSON pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param value The int value to be added
*
@ -749,6 +836,8 @@ methodmap JSON < Handle
/**
* Add (insert) integer64 value by a JSON pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param value The integer64 value to be added
*
@ -759,6 +848,8 @@ methodmap JSON < Handle
/**
* Add (insert) string value by a JSON pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param value The str value to be added
*
@ -769,6 +860,8 @@ methodmap JSON < Handle
/**
* Add (insert) null value by a JSON pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
*
* @return true if JSON pointer is valid and new value is set, false otherwise
@ -778,6 +871,8 @@ methodmap JSON < Handle
/**
* Remove value by a JSON pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
*
* @return true if removed value, false otherwise
@ -787,6 +882,8 @@ methodmap JSON < Handle
/**
* Try to get value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param value Handle to store value
*
@ -797,6 +894,8 @@ methodmap JSON < Handle
/**
* Try to get boolean value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param value Store the boolean value
*
@ -807,6 +906,8 @@ methodmap JSON < Handle
/**
* Try to get float value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param value Store the float value
*
@ -817,6 +918,8 @@ methodmap JSON < Handle
/**
* Try to get integer value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param value Store the integer value
*
@ -827,6 +930,8 @@ methodmap JSON < Handle
/**
* Try to get integer64 value by a JSON Pointer (automatically detects signed/unsigned)
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param buffer Buffer to store the integer64 value
* @param maxlength Maximum length of the buffer
@ -838,6 +943,8 @@ methodmap JSON < Handle
/**
* Try to get string value by a JSON Pointer
*
* @note JSON Pointer paths are always resolved from the document root, not from the current value
*
* @param path The JSON pointer string
* @param buffer Buffer to store the string value
* @param maxlength Maximum length of the buffer
@ -978,6 +1085,17 @@ methodmap JSON < Handle
property int ReadSize {
public native get();
}
/**
* Get the reference count of the document (debugging purpose)
*
* @return Reference count (number of JSON objects sharing this document)
*
* @note Returns 0 if the handle is invalid
*/
property int RefCount {
public native get();
}
};
methodmap JSONObject < JSON
@ -995,6 +1113,8 @@ methodmap JSONObject < JSON
* The array must contain an even number of strings, where even indices are keys
* and odd indices are values. Keys cannot be empty strings
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param pairs Array of strings containing alternating keys and values
* @param size Total size of the array (must be even)
*
@ -1007,6 +1127,8 @@ methodmap JSONObject < JSON
/**
* Loads a JSON object from a file
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param file File to read from
* @param flag The JSON read options
*
@ -1017,6 +1139,8 @@ methodmap JSONObject < JSON
/**
* Loads a JSON object from a string
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param buffer String buffer to load into the JSON object
* @param flag The JSON read options
*
@ -1027,6 +1151,7 @@ methodmap JSONObject < JSON
/**
* Gets a value from the object
*
* @note Needs to be freed using delete or CloseHandle()
* @note This function takes a linear search time
*
* @param key Key name
@ -1262,6 +1387,8 @@ methodmap JSONArray < JSON
/**
* Creates a new JSON array from an array of strings
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param strings Array of strings to create array from
* @param size Size of the array
* @return New JSON array handle, or null if creation failed
@ -1271,6 +1398,8 @@ methodmap JSONArray < JSON
/**
* Creates a new JSON array from an array of integer values
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param values Array of int values to create array from
* @param size Size of the array
* @return New JSON array handle, or null if creation failed
@ -1280,6 +1409,8 @@ methodmap JSONArray < JSON
/**
* Creates a new JSON array from an array of 64-bit integer strings (auto-detects signed/unsigned)
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param values Array of int64 string values (can be signed or unsigned)
* @param size Size of the array
* @return New JSON array handle, or null if creation failed
@ -1290,6 +1421,8 @@ methodmap JSONArray < JSON
/**
* Creates a new JSON array from an array of boolean values
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param values Array of bool values to create array from
* @param size Size of the array
* @return New JSON array handle, or null if creation failed
@ -1299,6 +1432,8 @@ methodmap JSONArray < JSON
/**
* Creates a new JSON array from an array of float values
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param values Array of float values to create array from
* @param size Size of the array
* @return New JSON array handle, or null if creation failed
@ -1308,6 +1443,8 @@ methodmap JSONArray < JSON
/**
* Loads a JSON array from a file
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param file File to read from
* @param flag Read flag
* @return Array handle, or null on failure
@ -1317,6 +1454,8 @@ methodmap JSONArray < JSON
/**
* Loads a JSON array from a string
*
* @note Needs to be freed using delete or CloseHandle()
*
* @param buffer String buffer to load into the JSON array
* @param flag Read flag
* @return Array handle, or null on failure
@ -1326,6 +1465,7 @@ methodmap JSONArray < JSON
/**
* Gets a value from the array
*
* @note Needs to be freed using delete or CloseHandle()
* @note This function takes a linear search time
*
* @param index Position in the array (starting from 0)
@ -1368,9 +1508,9 @@ methodmap JSONArray < JSON
* @param buffer Buffer to copy to
* @param maxlength Maximum size of the buffer
*
* @return 64-bit integer
* @return True on success, false on failure
*/
public native void GetInt64(int index, char[] buffer, int maxlength);
public native bool GetInt64(int index, char[] buffer, int maxlength);
/**
* Gets string data from the array
@ -1470,7 +1610,7 @@ methodmap JSONArray < JSON
*
* @param value JSON handle to set
*
* @return The value to be inserted. Returns false if it is null
* @return True if succeed, false otherwise
*/
public native bool Push(const JSON value);
@ -1479,7 +1619,7 @@ methodmap JSONArray < JSON
*
* @param value Boolean value to set
*
* @return The value to be inserted. Returns false if it is null
* @return True if succeed, false otherwise
*/
public native bool PushBool(bool value);
@ -1488,7 +1628,7 @@ methodmap JSONArray < JSON
*
* @param value float to set
*
* @return The value to be inserted. Returns false if it is null
* @return True if succeed, false otherwise
*/
public native bool PushFloat(float value);
@ -1497,7 +1637,7 @@ methodmap JSONArray < JSON
*
* @param value integer to set
*
* @return The value to be inserted. Returns false if it is null
* @return True if succeed, false otherwise
*/
public native bool PushInt(int value);
@ -1506,7 +1646,7 @@ methodmap JSONArray < JSON
*
* @param value integer64 value
*
* @return The value to be inserted. Returns false if it is null
* @return True if succeed, false otherwise
*/
public native bool PushInt64(const char[] value);
@ -1515,20 +1655,22 @@ methodmap JSONArray < JSON
*
* @param value String to copy
*
* @return The value to be inserted. Returns false if it is null
* @return True if succeed, false otherwise
*/
public native bool PushString(const char[] value);
/**
* Inserts a null value at the end of the array
*
* @return The value to be inserted. Returns false if it is null
* @return True if succeed, false otherwise
*/
public native bool PushNull();
/**
* Inserts a JSON value at specific index
*
* @note This function takes a linear search time
*
* @param index Position to insert (0 to size, size means append)
* @param value JSON handle to insert
*
@ -1539,6 +1681,8 @@ methodmap JSONArray < JSON
/**
* Inserts a boolean value at specific index
*
* @note This function takes a linear search time
*
* @param index Position to insert
* @param value Boolean value
*
@ -1549,6 +1693,8 @@ methodmap JSONArray < JSON
/**
* Inserts an integer value at specific index
*
* @note This function takes a linear search time
*
* @param index Position to insert
* @param value Integer value
*
@ -1559,6 +1705,8 @@ methodmap JSONArray < JSON
/**
* Inserts an integer64 value at specific index
*
* @note This function takes a linear search time
*
* @param index Position to insert
* @param value Integer64 value (as string)
*
@ -1569,6 +1717,8 @@ methodmap JSONArray < JSON
/**
* Inserts a float value at specific index
*
* @note This function takes a linear search time
*
* @param index Position to insert
* @param value Float value
*
@ -1579,6 +1729,8 @@ methodmap JSONArray < JSON
/**
* Inserts a string value at specific index
*
* @note This function takes a linear search time
*
* @param index Position to insert
* @param value String value
*
@ -1589,6 +1741,8 @@ methodmap JSONArray < JSON
/**
* Inserts a null value at specific index
*
* @note This function takes a linear search time
*
* @param index Position to insert
*
* @return True if succeed, false otherwise
@ -1675,7 +1829,7 @@ methodmap JSONArray < JSON
public native bool RemoveFirst();
/**
* Removes and returns the last value in this array
* Removes the last value in this array
*
* @return True if succeed, false otherwise
*/
@ -1687,12 +1841,11 @@ methodmap JSONArray < JSON
* @note This function takes a linear search time
*
* @param start_index The start index of the range (0 is the first)
* @param end_index The number of items in the range (can be 0, but do nothing)
*
* @param count Number of items to remove (can be 0, in which case nothing happens)
*
* @return True if succeed, false otherwise
*/
public native bool RemoveRange(int start_index, int end_index);
public native bool RemoveRange(int start_index, int count);
/**
* Searches for a boolean value in the array and returns its index
@ -1802,6 +1955,8 @@ methodmap JSONArrIter < Handle
* Creates an array iterator from a JSON array value
*
* @note Needs to be freed using delete or CloseHandle()
* @note Iterators are single-pass. Once Next returns null, call Reset() or create
* a new iterator to traverse the array again
*
* @param array JSON array value to iterate
*
@ -1810,9 +1965,21 @@ methodmap JSONArrIter < Handle
*/
public native JSONArrIter(JSON array);
/**
* Resets the iterator to the beginning of the array
*
* @note Only resets the iterator, does not free the iterator handle
*
* @return True on success, false on failure
* @error Invalid iterator handle
*/
public native bool Reset();
/**
* Moves the iterator to the next element
*
* @note Needs to be freed using delete or CloseHandle()
*
* @return JSON value handle for the next element, or null if iteration is complete
* @error Invalid iterator handle
*/
@ -1858,6 +2025,8 @@ methodmap JSONObjIter < Handle
* Creates an object iterator from a JSON object value
*
* @note Needs to be freed using delete or CloseHandle()
* @note Iterators are single-pass. Once Next returns null, call Reset() or create
* a new iterator to traverse the object again
*
* @param obj JSON object value to iterate
*
@ -1866,6 +2035,16 @@ methodmap JSONObjIter < Handle
*/
public native JSONObjIter(JSON obj);
/**
* Resets the iterator to the beginning of the object
*
* @note Only resets the iterator, does not free the iterator handle
*
* @return True on success, false on failure
* @error Invalid iterator handle
*/
public native bool Reset();
/**
* Moves the iterator to the next key
*
@ -2055,6 +2234,7 @@ public void __pl_json_SetNTVOptional()
MarkNativeAsOptional("JSON.GetTypeDesc");
MarkNativeAsOptional("JSON.GetSerializedSize");
MarkNativeAsOptional("JSON.ReadSize.get");
MarkNativeAsOptional("JSON.RefCount.get");
MarkNativeAsOptional("JSON.Type.get");
MarkNativeAsOptional("JSON.SubType.get");
MarkNativeAsOptional("JSON.IsArray.get");
@ -2078,6 +2258,10 @@ public void __pl_json_SetNTVOptional()
MarkNativeAsOptional("JSON.ForeachIndex");
MarkNativeAsOptional("JSON.ToMutable");
MarkNativeAsOptional("JSON.ToImmutable");
MarkNativeAsOptional("JSON.ApplyJsonPatch");
MarkNativeAsOptional("JSON.JsonPatchInPlace");
MarkNativeAsOptional("JSON.ApplyMergePatch");
MarkNativeAsOptional("JSON.MergePatchInPlace");
// JSON CREATE & GET
MarkNativeAsOptional("JSON.Pack");
@ -2140,14 +2324,16 @@ public void __pl_json_SetNTVOptional()
MarkNativeAsOptional("JSONArrIter.HasNext.get");
MarkNativeAsOptional("JSONArrIter.Index.get");
MarkNativeAsOptional("JSONArrIter.Remove");
MarkNativeAsOptional("JSONArrIter.Reset");
// JSONObjIter
MarkNativeAsOptional("JSONObjIter.JSONObjIter");
MarkNativeAsOptional("JSONObjIter.Next");
MarkNativeAsOptional("JSONObjIter.HasNext");
MarkNativeAsOptional("JSONObjIter.HasNext.get");
MarkNativeAsOptional("JSONObjIter.Value.get");
MarkNativeAsOptional("JSONObjIter.Get");
MarkNativeAsOptional("JSONObjIter.Index.get");
MarkNativeAsOptional("JSONObjIter.Remove");
MarkNativeAsOptional("JSONObjIter.Reset");
}
#endif

View File

@ -405,19 +405,19 @@ void Test_BasicValues()
char buffer[32];
JSON.GetTypeDesc(boolVal, buffer, sizeof(buffer));
boolVal.GetTypeDesc(buffer, sizeof(buffer));
AssertStrEq(buffer, "true");
JSON.GetTypeDesc(intVal, buffer, sizeof(buffer));
intVal.GetTypeDesc(buffer, sizeof(buffer));
AssertTrue(strcmp(buffer, "uint") == 0 || strcmp(buffer, "sint") == 0);
JSON.GetTypeDesc(floatVal, buffer, sizeof(buffer));
floatVal.GetTypeDesc(buffer, sizeof(buffer));
AssertStrEq(buffer, "real");
JSON.GetTypeDesc(strVal, buffer, sizeof(buffer));
strVal.GetTypeDesc(buffer, sizeof(buffer));
AssertStrEq(buffer, "string");
JSON.GetTypeDesc(nullVal, buffer, sizeof(buffer));
nullVal.GetTypeDesc(buffer, sizeof(buffer));
AssertStrEq(buffer, "null");
delete boolVal;
@ -1547,7 +1547,7 @@ void Test_ParseAndSerialize()
TestStart("Parse_MutableDocument");
{
JSON json = JSON.Parse("{\"key\":\"value\"}", false, true);
JSON json = JSON.Parse("{\"key\":\"value\"}", .is_mutable_doc = true);
AssertValidHandle(json);
AssertTrue(json.IsMutable);
AssertFalse(json.IsImmutable);
@ -1607,7 +1607,7 @@ void Test_ParseAndSerialize()
// Test read flags
TestStart("Parse_WithTrailingCommas");
{
JSON json = JSON.Parse("[1,2,3,]", false, false, JSON_READ_ALLOW_TRAILING_COMMAS);
JSON json = JSON.Parse("[1,2,3,]", .flag = JSON_READ_ALLOW_TRAILING_COMMAS);
AssertValidHandle(json);
delete json;
}
@ -1615,7 +1615,7 @@ void Test_ParseAndSerialize()
TestStart("Parse_WithComments");
{
JSON json = JSON.Parse("/* comment */ {\"key\":\"value\"}", false, false, JSON_READ_ALLOW_COMMENTS);
JSON json = JSON.Parse("/* comment */ {\"key\":\"value\"}", .flag = JSON_READ_ALLOW_COMMENTS);
AssertValidHandle(json);
delete json;
}
@ -1687,16 +1687,30 @@ void Test_Iterators()
int count = 0;
char key[32];
JSON value;
JSONObjIter iter = new JSONObjIter(obj);
while (obj.ForeachObject(key, sizeof(key), value))
while (iter.Next(key, sizeof(key)))
{
count++;
JSON value = iter.Value;
AssertValidHandle(value);
delete value;
}
AssertFalse(iter.HasNext);
AssertEq(count, 3);
AssertTrue(iter.Reset());
count = 0;
while (iter.Next(key, sizeof(key)))
{
count++;
JSON value = iter.Value;
AssertValidHandle(value);
delete value;
}
AssertEq(count, 3);
delete iter;
delete obj;
}
TestEnd();
@ -1710,18 +1724,32 @@ void Test_Iterators()
arr.PushInt(30);
int count = 0;
int index;
JSON value;
JSONArrIter iter = new JSONArrIter(arr);
while (arr.ForeachArray(index, value))
while (iter.HasNext)
{
AssertEq(index, count);
JSON value = iter.Next;
AssertValidHandle(value);
AssertEq(iter.Index, count);
delete value;
count++;
}
AssertFalse(iter.HasNext);
AssertEq(count, 3);
AssertTrue(iter.Reset());
count = 0;
while (iter.HasNext)
{
JSON value = iter.Next;
AssertValidHandle(value);
AssertEq(iter.Index, count);
delete value;
count++;
}
AssertEq(count, 3);
delete iter;
delete arr;
}
TestEnd();
@ -1736,14 +1764,26 @@ void Test_Iterators()
int count = 0;
char key[32];
JSONObjIter iter = new JSONObjIter(obj);
while (obj.ForeachKey(key, sizeof(key)))
while (iter.Next(key, sizeof(key)))
{
AssertTrue(strlen(key) > 0);
count++;
}
AssertFalse(iter.HasNext);
AssertEq(count, 3);
AssertTrue(iter.Reset());
count = 0;
while (iter.Next(key, sizeof(key)))
{
AssertTrue(strlen(key) > 0);
count++;
}
AssertEq(count, 3);
delete iter;
delete obj;
}
TestEnd();
@ -1757,15 +1797,30 @@ void Test_Iterators()
arr.PushInt(3);
int count = 0;
int index;
JSONArrIter iter = new JSONArrIter(arr);
while (arr.ForeachIndex(index))
while (iter.HasNext)
{
AssertEq(index, count);
JSON value = iter.Next;
AssertEq(iter.Index, count);
delete value;
count++;
}
AssertFalse(iter.HasNext);
AssertEq(count, 3);
AssertTrue(iter.Reset());
count = 0;
while (iter.HasNext)
{
JSON value = iter.Next;
AssertEq(iter.Index, count);
delete value;
count++;
}
AssertEq(count, 3);
delete iter;
delete arr;
}
TestEnd();
@ -1774,10 +1829,12 @@ void Test_Iterators()
TestStart("Iterator_EmptyObject");
{
JSONObject obj = new JSONObject();
char key[32];
JSON value;
AssertFalse(obj.ForeachObject(key, sizeof(key), value));
JSONObjIter iter = new JSONObjIter(obj);
AssertFalse(iter.Next(key, sizeof(key)));
AssertTrue(iter.Reset());
AssertFalse(iter.Next(key, sizeof(key)));
delete iter;
delete obj;
}
@ -1786,10 +1843,11 @@ void Test_Iterators()
TestStart("Iterator_EmptyArray");
{
JSONArray arr = new JSONArray();
int index;
JSON value;
AssertFalse(arr.ForeachArray(index, value));
JSONArrIter iter = new JSONArrIter(arr);
AssertFalse(iter.HasNext);
AssertTrue(iter.Reset());
AssertFalse(iter.HasNext);
delete iter;
delete arr;
}
@ -2146,6 +2204,144 @@ void Test_AdvancedFeatures()
}
TestEnd();
// Test ApplyJsonPatch (new value, immutable result)
TestStart("Advanced_ApplyJsonPatch");
{
JSONObject original = new JSONObject();
original.SetInt("score", 10);
original.SetString("name", "bot");
JSON patch = JSON.Parse("[{\"op\":\"replace\",\"path\":\"/score\",\"value\":42}]");
AssertValidHandle(patch);
JSON result = original.ApplyJsonPatch(patch);
AssertValidHandle(result);
AssertTrue(result.IsImmutable);
AssertEq(result.PtrGetInt("/score"), 42);
char buffer[32];
result.PtrGetString("/name", buffer, sizeof(buffer));
AssertStrEq(buffer, "bot");
// ensure original unchanged
AssertEq(original.GetInt("score"), 10);
delete result;
delete patch;
delete original;
}
TestEnd();
// Test ApplyJsonPatch resultMutable = true
TestStart("Advanced_ApplyJsonPatch_MutableResult");
{
JSONObject original = new JSONObject();
original.SetInt("count", 1);
JSON patch = JSON.Parse("[{\"op\":\"add\",\"path\":\"/newField\",\"value\":\"hello\"}]");
AssertValidHandle(patch);
JSON result = original.ApplyJsonPatch(patch, true);
AssertValidHandle(result);
AssertTrue(result.IsMutable);
char buffer[16];
result.PtrGetString("/newField", buffer, sizeof(buffer));
AssertStrEq(buffer, "hello");
delete result;
delete patch;
delete original;
}
TestEnd();
// Test JsonPatchInPlace
TestStart("Advanced_JsonPatchInPlace");
{
JSONObject target = new JSONObject();
target.SetInt("score", 5);
target.SetInt("lives", 3);
JSON patch = JSON.Parse("[{\"op\":\"remove\",\"path\":\"/lives\"},{\"op\":\"replace\",\"path\":\"/score\",\"value\":9}]");
AssertValidHandle(patch);
AssertTrue(target.JsonPatchInPlace(patch));
AssertEq(target.GetInt("score"), 9);
AssertFalse(target.HasKey("lives"));
delete patch;
delete target;
}
TestEnd();
// Test ApplyMergePatch (immutable result)
TestStart("Advanced_ApplyMergePatch");
{
JSONObject original = new JSONObject();
original.PtrSetString("/settings/mode", "coop");
original.PtrSetInt("/settings/difficulty", 1);
JSON mergePatch = JSON.Parse("{\"settings\":{\"difficulty\":3,\"friendlyFire\":true}}");
AssertValidHandle(mergePatch);
JSON result = original.ApplyMergePatch(mergePatch);
AssertValidHandle(result);
AssertTrue(result.IsImmutable);
AssertEq(result.PtrGetInt("/settings/difficulty"), 3);
AssertTrue(result.PtrGetBool("/settings/friendlyFire"));
delete result;
delete mergePatch;
delete original;
}
TestEnd();
// Test ApplyMergePatch resultMutable = true
TestStart("Advanced_ApplyMergePatch_MutableResult");
{
JSONObject original = new JSONObject();
original.PtrSetString("/profile/name", "player");
JSON mergePatch = JSON.Parse("{\"profile\":{\"rank\":10}}");
AssertValidHandle(mergePatch);
JSON result = original.ApplyMergePatch(mergePatch, true);
AssertValidHandle(result);
AssertTrue(result.IsMutable);
AssertEq(result.PtrGetInt("/profile/rank"), 10);
char buffer[16];
result.PtrGetString("/profile/name", buffer, sizeof(buffer));
AssertStrEq(buffer, "player");
delete result;
delete mergePatch;
delete original;
}
TestEnd();
// Test MergePatchInPlace
TestStart("Advanced_MergePatchInPlace");
{
JSONObject target = new JSONObject();
target.PtrSetString("/config/mode", "coop");
target.PtrSetInt("/config/players", 4);
JSON mergePatch = JSON.Parse("{\"config\":{\"players\":6,\"region\":\"EU\"}}");
AssertValidHandle(mergePatch);
AssertTrue(target.MergePatchInPlace(mergePatch));
AssertEq(target.PtrGetInt("/config/players"), 6);
char buffer[16];
target.PtrGetString("/config/region", buffer, sizeof(buffer));
AssertStrEq(buffer, "EU");
delete mergePatch;
delete target;
}
TestEnd();
// Test Pack
TestStart("Advanced_Pack_SimpleObject");
{
@ -2210,7 +2406,7 @@ void Test_AdvancedFeatures()
// Test mixed type array sorting
TestStart("Advanced_MixedTypeSort");
{
JSONArray json = JSON.Parse("[true, 42, \"hello\", 1.5, false]", false, true);
JSONArray json = JSON.Parse("[true, 42, \"hello\", 1.5, false]", .is_mutable_doc = true);
JSONArray arr = json;
AssertTrue(arr.Sort(JSON_SORT_ASC));