# `PtcRunner.Lisp.Runtime.Json`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/lisp/runtime/json.ex#L1)

JSON parsing and generation for PTC-Lisp.

Implements `(json/parse-string s)`, `(json/parse-lines s)`, and
`(json/generate-string v)` — Cheshire-shaped builtins. Parse helpers
return `nil` on failure rather than raising, matching the DIV-* convention (see
`docs/clojure-conformance-gaps.md` DIV-23, DIV-24): no try/catch
in the sandbox means raising = unrecoverable program crash.

See `Plans/json-support.md` §4 for the full spec, including the
string-keyed-only round-trip property and the integer-key /
special-float carve-outs (§4.3).

# `generate_string`

```elixir
@spec generate_string(term()) :: String.t() | nil
```

Encode an Elixir value as a JSON string.

Returns the encoded string on success; `nil` on any failure
(non-encodable input). Encoder pre-validation (per spec §4.4) runs
*before* `Jason.encode/1` is invoked — without it, `Jason` would
silently coerce non-boolean atoms (e.g. PTC-Lisp keywords like
`:fs`) into JSON strings, eroding the wire-boundary type signal.

Map keys are restricted to strings and integers — atoms (including
`true` / `false` / `nil`), floats, and other key types fail the
walk and produce `nil` (§4.2 / §4.3).

## Examples

    iex> PtcRunner.Lisp.Runtime.Json.generate_string(nil)
    "null"

    iex> PtcRunner.Lisp.Runtime.Json.generate_string([1, 2, 3])
    "[1,2,3]"

    iex> PtcRunner.Lisp.Runtime.Json.generate_string("hello")
    "\"hello\""

    iex> PtcRunner.Lisp.Runtime.Json.generate_string(%{"server" => :fs})
    nil

    iex> PtcRunner.Lisp.Runtime.Json.generate_string(%{:server => "fs"})
    nil

    iex> PtcRunner.Lisp.Runtime.Json.generate_string({:ok, 1})
    nil

    iex> PtcRunner.Lisp.Runtime.Json.generate_string(%{1 => "a"})
    "{\"1\":\"a\"}"

# `parse_lines`

```elixir
@spec parse_lines(term()) :: [term()] | nil
```

Parse line-delimited JSON into a list.

Blank or whitespace-only lines are skipped. Each remaining line is
parsed with `parse_string/1`, so malformed lines and valid JSON
literal `null` lines both produce `nil`.

## Examples

    iex> PtcRunner.Lisp.Runtime.Json.parse_lines("{\"a\":1}\n[2,3]\n")
    [%{"a" => 1}, [2, 3]]

    iex> PtcRunner.Lisp.Runtime.Json.parse_lines("null\nnot json\n")
    [nil, nil]

    iex> PtcRunner.Lisp.Runtime.Json.parse_lines("  \n{\"ok\":true}\n")
    [%{"ok" => true}]

    iex> PtcRunner.Lisp.Runtime.Json.parse_lines(42)
    nil

# `parse_string`

```elixir
@spec parse_string(term()) :: term() | nil
```

Parse a JSON string into an Elixir value.

Returns the parsed value on success; `nil` on any failure
(invalid JSON, non-binary input, `nil` input). Map keys are
decoded as **strings** (no atom keys) to avoid atom memory
leaks on untrusted input.

## Examples

    iex> PtcRunner.Lisp.Runtime.Json.parse_string(~S|{"a": 1, "b": [2, 3]}|)
    %{"a" => 1, "b" => [2, 3]}

    iex> PtcRunner.Lisp.Runtime.Json.parse_string("[1, 2, 3]")
    [1, 2, 3]

    iex> PtcRunner.Lisp.Runtime.Json.parse_string("null")
    nil

    iex> PtcRunner.Lisp.Runtime.Json.parse_string("not json")
    nil

    iex> PtcRunner.Lisp.Runtime.Json.parse_string(nil)
    nil

    iex> PtcRunner.Lisp.Runtime.Json.parse_string(42)
    nil

---

*Consult [api-reference.md](api-reference.md) for complete listing*
