Guides

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.

plugin.cpp
#include "source2mod/ISource2ModJSON.h"

The 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.

plugin.cpp
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();

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.

KindCalls
Type checksIsNull(), IsObject(), IsArray(), IsString(), IsNumber(), IsBool()
This node's valueAsString(def = ""), AsInt(def = 0), AsInt64(def = 0), AsDouble(def = 0.0), AsFloat(def = 0.0f), AsBool(def = false)
A member's valueGetString(key, def), GetInt(key, def), GetInt64(key, def), GetDouble(key, def), GetFloat(key, def), GetBool(key, def)
ObjectsHas(key), Get(key) (null when absent), Count(), KeyAt(i)
ArraysCount(), At(i) (null when out of range)
SerialisingDump(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();
}

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() and Dump() are valid until Release(). Copy into std::string whatever you keep. In SourcePawn value handles need no delete and 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 call Release() or delete on 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

On this page