5.1.30. JSON

This tutorial covers daslib/json and daslib/json_boost — parsing, building, writing, and querying JSON data in daslang.

json provides the core parser, writer, and JsValue variant type. json_boost adds safe access operators, struct serialization, and the %json~ reader macro.

5.1.30.1. Parsing JSON

read_json takes a string and returns JsonValue?. If parsing fails, the error string is set and null is returned:

var error : string
var js = read_json("{ \"name\": \"Alice\", \"age\": 30 }", error)
if (js != null) {
    print("parsed OK\n")
}

The JsonValue struct wraps a JsValue variant with these cases:

  • _objecttable<string; JsonValue?>

  • _arrayarray<JsonValue?>

  • _stringstring

  • _numberdouble (floating point)

  • _longintint64 (integer)

  • _boolbool

  • _nullvoid?

5.1.30.2. Building JSON with JV

JV() constructors wrap native values into JsonValue?:

var str_val  = JV("hello")   // _string
var int_val  = JV(42)        // _longint
var flt_val  = JV(3.14)      // _number
var bool_val = JV(true)      // _bool
var null_val = JVNull()      // _null

Multi-value JV creates an array:

var arr = JV(1, "two", true)   // [1, "two", true]

5.1.30.3. Writing JSON

write_json converts a JsonValue? back to a string:

var js = JV(42)
print(write_json(js))   // 42

var empty : JsonValue?
print(write_json(empty))  // null

5.1.30.4. Safe access operators

json_boost provides ?., ?[], and ?? for convenient access. These never crash — they return null or a default value on missing keys:

var js = read_json("{ \"user\": { \"name\": \"Bob\" } }", error)

let name = js?.user?.name ?? "unknown"     // "Bob"
let age  = js?.user?.age ?? 0              // 0 (missing key)

// Array indexing
var arr = read_json("[10, 20, 30]", error)
let first = arr?[0] ?? -1                  // 10

// Chained access on deeply missing paths returns the default
let deep = js?.a?.b?.c ?? -1               // -1

// null pointer is safe
var nothing : JsonValue?
let safe = nothing?.foo ?? "safe"          // "safe"

5.1.30.5. Variant checks with is/as

json_boost rewrites is and as on JsonValue? to check the underlying JsValue variant:

var js = JV("hello")
print("{js is _string}\n")     // true
print("{js as _string}\n")     // hello

var jn = JV(42)
print("{jn is _longint}\n")    // true

5.1.30.6. Struct serialization

json_boost provides generic JV() and from_JV() that convert structs to/from JSON using compile-time reflection:

struct Player {
    name  : string
    hp    : int
    speed : float
}

let p = Player(name = "Hero", hp = 100, speed = 5.5)
var js = JV(p)
// {"speed":5.5, "name":"Hero", "hp":100}

var p2 = from_JV(parsed, type<Player>)
print("{p2.name}, hp={p2.hp}\n")

5.1.30.7. Enum serialization

Enums serialize as strings by default:

enum Weapon { sword; bow; staff }

var js = JV(Weapon.bow)      // "bow"
var w  = from_JV(js, type<Weapon>)   // Weapon.bow

5.1.30.8. Reader macro

The %json~ reader macro embeds JSON directly in daslang code. It parses at compile time and creates a JsonValue? at runtime:

var settings = %json~
{
    "resolution": [1920, 1080],
    "fullscreen": true,
    "title": "My Game"
}
%%

let title = settings?.title ?? "untitled"   // "My Game"

5.1.30.9. Writer settings

Global settings control write_json behavior:

  • set_no_trailing_zeros(true) — omit trailing zeros from round doubles (1.01)

  • set_no_empty_arrays(true) — skip empty array fields in objects

let old = set_no_trailing_zeros(true)
print(write_json(JV(1.0lf)))  // 1
set_no_trailing_zeros(old)

5.1.30.10. Collections

JV and from_JV work with array<T> and table<string;T>:

var arr <- array<int>(10, 20, 30)
var js = JV(arr)           // [10, 20, 30]
var back = from_JV(js, type<array<int>>)

var tab <- { "x" => 1, "y" => 2 }
var jt = JV(tab)           // {"x":1, "y":2}

Vector types (float2/3/4, int2/3/4) serialize as objects with x, y, z, w keys:

var js = JV(float3(1.0, 2.0, 3.0))
// {"x":1, "y":2, "z":3}

Tuples serialize with _0, _1, … keys. Variants include a $variant field.

5.1.30.11. Broken JSON repair

try_fixing_broken_json attempts to fix common issues from LLM output:

  • String concatenation: "hello" + "world""helloworld"

  • Trailing commas: [1, 2, ][1, 2]

  • Nested quotes: "she said "hi"""she said \"hi\""

let bad = "{ \"msg\": \"hello\", }"
let fixed = try_fixing_broken_json(bad)
var js = read_json(fixed, error)

5.1.30.12. sprint_json

sprint_json is a builtin function that serializes any daslang value directly to a JSON string — no JsonValue? intermediate. It handles structs, classes, variants, tuples, tables, arrays, enums, pointers, and all basic types:

struct Record {
    id     : int
    tag    : string
    data   : Payload
    values : array<int>
    meta   : table<string; int>
    coords : tuple<int; float>
    ptr    : void?
}

var r <- Record(uninitialized
    id = 1, tag = "test",
    data = Payload(uninitialized code = 42),
    values = [1, 2, 3],
    meta <- { "x" => 10 },
    coords = (7, 3.14),
    ptr = null
)
let compact = sprint_json(r, false)
// {"id":1,"tag":"test","data":{"code":42},"values":[1,2,3],...}

let pretty = sprint_json(r, true)
// human-readable with indentation

The second argument controls human-readable formatting. Works with simple values too:

sprint_json(42, false)          // 42
sprint_json("hello", false)     // "hello"
sprint_json([10, 20, 30], false) // [10,20,30]

5.1.30.13. Field annotations

Struct field annotations control how sprint_json serializes fields. These require options rtti to be enabled.

  • @optional — skip the field if it has a default or empty value

  • @embed — embed a string field as raw JSON (no extra quotes)

  • @unescape — don’t escape special characters in the string

  • @enum_as_int — serialize an enum as its integer value, not a string

  • @rename="key" — use key as the JSON field name instead of the daslang field name

struct AnnotatedConfig {
    name : string
    @optional debug : bool          // omitted when false
    @optional tags : array<string>  // omitted when empty
    @embed raw_data : string        // embedded as raw JSON
    @unescape raw_path : string     // no escaping of special chars
    pri : Priority                  // serialized as string
    @enum_as_int level : Priority   // serialized as integer
    @rename="type" _type : string   // appears as "type" in JSON
}

var c <- AnnotatedConfig(uninitialized
    name = "app", debug = false,
    raw_data = "[1,2,3]",
    raw_path = "C:\\Users\\test",
    pri = Priority.high,
    level = Priority.medium,
    _type = "widget"
)
let json_str = sprint_json(c, false)
// {"name":"app","raw_data":[1,2,3],"raw_path":"C:\Users\test","pri":"high","level":1,"type":"widget"}

In this example: debug and tags are omitted (@optional), raw_data is embedded as [1,2,3] not "[1,2,3]" (@embed), raw_path keeps backslashes unescaped (@unescape), level is 1 instead of "medium" (@enum_as_int), and _type appears as "type" in the output (@rename).

5.1.30.14. @rename annotation

Use @rename="json_key" when the JSON key is a daslang reserved word or doesn’t follow daslang naming conventions. The field keeps a safe name in code (e.g. _type) but serializes as the desired key. @rename works with sprint_json, JV, and from_JV:

struct ApiResponse {
    @rename="type" _type : string
    @rename="class" _class : int
    value : float
}

var resp = ApiResponse(_type = "widget", _class = 3, value = 1.5)
sprint_json(resp, false)
// {"type":"widget","class":3,"value":1.5}

// from_JV maps renamed keys back to struct fields
var js = read_json("{\"type\":\"button\",\"class\":5,\"value\":2.0}", error)
var result = from_JV(js, type<ApiResponse>)
// result._type == "button", result._class == 5

5.1.30.15. Class serialization

Both JV/from_JV and sprint_json work with classes. Classes serialize their fields just like structs:

class Animal {
    species : string
    legs : int
}

var a = new Animal(species = "cat", legs = 4)
let json_str = sprint_json(*a, false)
// {"species":"cat","legs":4}

var js = JV(*a)
print(write_json(js))
// {"legs":4,"species":"cat"}

See also

Full source: tutorials/language/30_json.das

Next tutorial: Regular expressions.

Functional programming tutorial (previous tutorial).

JSON manipulation library — core JSON module reference.

Boost package for JSON — JSON boost module reference.