JSON
Parse and read JSON through Source2Mod, without vendoring a JSON library or agreeing on its ABI.
ISource2ModJSON parses JSON text inside Source2Mod and hands you a read-only
tree to walk. Nothing library-specific crosses the plugin boundary, so your
plugin does not need its own JSON library for reading configs or HTTP
responses — and if it has one, the two never have to match.
#include "source2mod/ISource2ModJSON.h"#include <source2mod/api> // the whole API, json.inc includedThe interface is g_pSource2ModJSON, filled in by SOURCE2MOD_SAVEVARS() like the
others; a SourcePawn script calls Source2ModJSON directly. The same value type,
ISource2ModJSONValue, is what HTTP responses carry in
m_pJson (Json() in SourcePawn).
Parsing
ISource2ModJSONDocument* Parse(const char* pszText, int nLength = -1);Parse() never returns nullptr. On a syntax error the document reports
IsValid() == false, GetError() returns a short message, and its root reads as null — so
walking a broken document is safe, you just get defaults. You own the
document: call Release() when you are done.
In SourcePawn it is Source2ModJSON.Parse(text), and delete doc releases
it. There Root() returns null for a broken document, and so do Get() and
At() when there is nothing, so check a handle before calling methods on it.
ISource2ModJSONDocument* doc = g_pSource2ModJSON->Parse(text.c_str());
if (!doc->IsValid())
g_Source2ModAPI->ConPrintf("Bad config: %s\n", doc->GetError());
const ISource2ModJSONValue* root = doc->Root();
m_Config.Prefix = root->GetString("prefix", "[Server]"); // copied into std::string
m_Config.MaxHp = root->GetInt("max_hp", 100);
m_Config.Enabled = root->GetBool("enabled", true);
doc->Release();char g_Prefix[64];
int g_MaxHp;
bool g_Enabled;
void ParseConfig(const char[] text)
{
ISource2ModJSONDocument doc = Source2ModJSON.Parse(text);
if (!doc.IsValid())
{
char error[128];
doc.GetError(error, sizeof(error));
Source2ModApi.Log("Bad config: %s", error);
}
ISource2ModJSONValue root = doc.Root(); // null when the document is not valid
if (root != null)
{
root.GetString("prefix", g_Prefix, sizeof(g_Prefix), "[Server]"); // copied into the buffer
g_MaxHp = root.GetInt("max_hp", 100);
g_Enabled = root.GetBool("enabled", true);
}
delete doc;
}Reading values
Every accessor is total: asking for the wrong type, a missing key or an out-of-range index returns the default you passed instead of throwing.
| Kind | Calls |
|---|---|
| Type checks | IsNull(), IsObject(), IsArray(), IsString(), IsNumber(), IsBool() |
| This node's value | AsString(def = ""), AsInt(def = 0), AsInt64(def = 0), AsDouble(def = 0.0), AsFloat(def = 0.0f), AsBool(def = false) |
| A member's value | GetString(key, def), GetInt(key, def), GetInt64(key, def), GetDouble(key, def), GetFloat(key, def), GetBool(key, def) |
| Objects | Has(key), Get(key) (null when absent), Count(), KeyAt(i) |
| Arrays | Count(), At(i) (null when out of range) |
| Serialising | Dump(nIndent = -1) — compact by default |
SourcePawn has the same methods with three differences: text comes back
through a buffer (GetString(key, buffer, maxlen, def), AsString(),
KeyAt(), Dump()), 64-bit integers are two cells (int value[2], low then
high), and doubles come back as float.
Arrays and objects
// { "admins": [ { "steamid": 76561198000000000, "flags": "z" }, ... ] }
const ISource2ModJSONValue* admins = root->Get("admins");
for (int i = 0; admins && i < admins->Count(); i++)
{
const ISource2ModJSONValue* admin = admins->At(i);
AddAdmin(admin->GetInt64("steamid"), admin->GetString("flags"));
}
// Walking an object's members
const ISource2ModJSONValue* colors = root->Get("colors");
for (int i = 0; colors && i < colors->Count(); i++)
{
const char* key = colors->KeyAt(i);
const char* value = colors->Get(key)->AsString();
}// { "admins": [ { "steamid": 76561198000000000, "flags": "z" }, ... ] }
ISource2ModJSONValue admins = root.Get("admins");
for (int i = 0; admins != null && i < admins.Count(); i++)
{
ISource2ModJSONValue admin = admins.At(i);
int steamid[2]; // 64-bit: low and high cell
char flags[32];
admin.GetInt64("steamid", steamid);
admin.GetString("flags", flags, sizeof(flags));
AddAdmin(steamid, flags);
}
// Walking an object's members
ISource2ModJSONValue colors = root.Get("colors");
for (int i = 0; colors != null && i < colors.Count(); i++)
{
char key[64], value[64];
colors.KeyAt(i, key, sizeof(key));
colors.Get(key).AsString(value, sizeof(value));
}GetInt64() on a string such as "76561198000000000" returns the default —
it does not convert. Store large IDs as numbers, or read them with
GetString() and convert yourself.
Lifetime
- Every node and every string belongs to the document. Pointers returned by
Get(),At(),AsString(),GetString(),KeyAt()andDump()are valid untilRelease(). Copy intostd::stringwhatever you keep. In SourcePawn value handles need nodeleteand are gone with their document; text is already copied into your buffers. - For
m_pJson(Json()in SourcePawn) in an HTTP callback Source2Mod owns the document and releases it after the callback returns — do not callRelease()ordeleteon it. - Release (
delete) every document you parse; a document is not tied to your plugin, so one you forget is not freed on unload.
Reference
ISource2ModJSONAPI reference- HTTP
- Paths — where to find your plugin's config file