JSON: Parse and Produce
The same round-trip in all eight languages: take the JSON text {"name":"ada","age":36}, parse it, bump age by one, and produce the JSON text back out - printing {"age":37,"name":"ada"} with keys in sorted order. Watch where JSON lives: a batteries-included standard module (Python, Go, Raku, Perl) versus an external library (Rust's serde_json, OCaml's Yojson), and - for Guji - a hand-rolled sum type rendered by match, since v0 has no JSON in its prelude. Notice too which languages give you sorted keys for free and which need an explicit nudge.
# Guji has no JSON codec in its prelude, so JSON is modeled as a sum type:
# a value "is one of" these shapes (§9), rendered back by `match`.
enum Json {
JNull
JBool($b: Bool)
JNum($n: Int)
JStr($s: Str)
}
# Produce: render one Json value to compact text.
sub render($j: Json): Str {
match $j {
JNull { "null" }
JBool($b) { if $b { "true" } else { "false" } }
JNum($n) { "$n" }
JStr($s) { "\"$s\"" }
}
}
sub main() {
$src = '{"name":"ada","age":36}'
# Parse: lift the two fields out with named-capture regexes (§13).
$name = match $src ~~ /"name":"(?<v>\w+)"/ {
Some($m) { $m<v>.unwrap_or("?") }
None { "?" }
}
$age = match $src ~~ /"age":(?<v>\d+)/ {
Some($m) { $m<v>.unwrap_or("0").parse_int().unwrap_or(0) }
None { 0 }
}
# Build a map of typed Json values and bump the age by one.
%doc = {"name": JStr($name), "age": JNum($age + 1)}
# Produce: sort the keys explicitly, render each value, join.
@parts = %doc.keys().sort().map(sub($k) {
"\"$k\":" ~ render(%doc{$k})
})
print("\{" ~ @parts.join(",") ~ "}")
}Guji's prelude (§15) ships no JSON codec, so the idiomatic move is to model JSON as an enum - the natural "is one of these shapes" sum type (§9) - and render it with an exhaustive match. Parsing leans on Guji's signature text feature: named-capture regex literals (§13) yield Option[Match], so each lookup is a match you must handle, and .parse_int() turns the captured digits into an Int. The two typed values go into a real Map[Str, Json], and producing sorted output is an explicit nudge - %doc.keys().sort() - mirroring the dict-based languages here. Note "\{" escapes the brace so it is a literal, not the start of a { … } interpolation (§12). Guji is a compiled, statically typed language: this runs identically on the reference interpreter (guji file.guji) and as a native binary (guji build).
package main
import (
"encoding/json"
"fmt"
)
func main() {
src := `{"name":"ada","age":36}`
// Parse into a generic map of string -> any.
var doc map[string]any
if err := json.Unmarshal([]byte(src), &doc); err != nil {
panic(err)
}
// JSON numbers decode as float64; bump age by one.
doc["age"] = doc["age"].(float64) + 1
// Produce: Marshal sorts map keys alphabetically.
out, _ := json.Marshal(doc)
fmt.Println(string(out))
}Go's encoding/json is in the standard library. Unmarshalling into a map[string]any gives a generic tree, but every JSON number arrives as a float64, so reading age needs a type assertion .(float64) before the increment. json.Marshal emits a compact object and - conveniently for this task - sorts string map keys alphabetically by default, so {"age":37,"name":"ada"} falls out without any extra sorting step. Decoding into a typed struct instead would skip the assertions but fix the shape ahead of time.
(* Uses the Yojson library: opam install yojson *)
let () =
let src = {|{"name":"ada","age":36}|} in
(* Parse into Yojson's variant tree. *)
let doc = Yojson.Safe.from_string src in
let bumped =
match doc with
| `Assoc fields ->
let fields =
List.map
(function
| "age", `Int n -> ("age", `Int (n + 1))
| kv -> kv)
fields
in
(* Produce: sort the keys, then serialize. *)
`Assoc (List.sort (fun (a, _) (b, _) -> compare a b) fields)
| other -> other
in
print_endline (Yojson.Safe.to_string bumped)OCaml has no JSON in its standard library, so the idiomatic choice is the Yojson library, whose Yojson.Safe.t is a polymorphic-variant tree - `Assoc for objects, `Int, `String, and so on. Parsing and matching are one and the same: match doc with \Assoc fieldsdestructures the object as an association list, and a nested pattern rewrites theageentry. Yojson preserves insertion order, so producing sorted output is an explicitList.sorton the pairs beforeto_string`.
-- Uses the aeson library: cabal install aeson
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (Value (Number, Object), decode, encode)
import qualified Data.Aeson.KeyMap as KM
import qualified Data.ByteString.Lazy.Char8 as B
main :: IO ()
main = do
let src = "{\"name\":\"ada\",\"age\":36}"
-- Parse into aeson's Value tree (Maybe handles failure).
case decode src :: Maybe Value of
Just (Object o) -> do
-- Bump age by one, then re-encode (aeson sorts object keys).
let bumped = KM.adjust inc "age" o
B.putStrLn (encode (Object bumped))
_ -> putStrLn "parse error"
where
inc (Number n) = Number (n + 1)
inc v = vHaskell has no JSON in base, so the de-facto choice is the aeson library, whose Value is an algebraic data type with Object, Number, String, etc. decode returns a Maybe Value, so failure is handled by pattern-matching rather than exceptions, and KeyMap.adjust rewrites the age field functionally. Aeson's encode already emits object keys in sorted order, so {"age":37,"name":"ada"} falls out with no extra step; for a known schema you would derive FromJSON/ToJSON on a record instead of poking at a Value.
use strict;
use warnings;
use JSON::PP;
my $src = '{"name":"ada","age":36}';
# Parse: decode the JSON text into a Perl hash ref.
my $doc = decode_json($src);
$doc->{age} += 1;
# Produce: ->canonical sorts the keys for deterministic output.
my $json = JSON::PP->new->canonical;
print $json->encode($doc), "\n";JSON::PP ships with Perl, so no install is needed; decode_json turns JSON text into ordinary Perl data (a hash ref here), and Perl's automatic numeric context lets += 1 increment age with no casting. The default encode_json would emit keys in hash order, which is randomized, so the object form JSON::PP->new->canonical is used to sort keys for a stable result. The faster XS-backed JSON::XS (or the Cpanel::JSON::XS it descends from) shares this exact API.
use JSON::Fast;
my $src = '{"name":"ada","age":36}';
# Parse: from-json returns a Raku Hash of native values.
my %doc = from-json($src);
%doc<age> += 1;
# Produce: :sorted-keys orders the keys, :!pretty keeps it on one line.
say to-json(%doc, :sorted-keys, :!pretty);Raku reaches for the JSON::Fast module (the de-facto standard, a fast drop-in for JSON::Tiny): from-json parses JSON text into native Raku values - here a Hash - and %doc<age> += 1 increments in place. By default to-json pretty-prints over several lines, so :!pretty collapses it to one line, and :sorted-keys orders the keys for a deterministic {"age":37,"name":"ada"}. The whole round-trip is two library calls plus a single field update.
// Uses the serde_json crate: cargo add serde_json
use serde_json::{json, Value};
fn main() {
let src = r#"{"name":"ada","age":36}"#;
// Parse into an untyped serde_json Value tree.
let mut doc: Value = serde_json::from_str(src).unwrap();
// Bump age; as_i64 reads the number, then overwrite the field.
let age = doc["age"].as_i64().unwrap();
doc["age"] = json!(age + 1);
// Produce: serde_json's default Map is a BTreeMap, so keys sort.
println!("{}", serde_json::to_string(&doc).unwrap());
}Rust's JSON lives in the external serde_json crate. from_str parses into an untyped Value enum; indexing doc["age"] and .as_i64() reads the number as an Option (here .unwrap()'d), and the json! macro builds the replacement value. By default serde_json backs objects with a BTreeMap, so serialization sorts keys automatically - enabling the preserve_order feature would instead keep input order. For a known schema you would #[derive(Serialize, Deserialize)] a struct rather than poke at a Value.
import json
src = '{"name":"ada","age":36}'
# Parse: a JSON string becomes a dict of native Python values.
doc = json.loads(src)
doc["age"] += 1
# Produce: sort_keys + compact separators give deterministic output.
print(json.dumps(doc, separators=(",", ":"), sort_keys=True))Python's json module is standard and effortless: json.loads turns the text into a dict of native values, and doc["age"] += 1 increments the integer directly. json.dumps serializes back, where sort_keys=True orders the keys and separators=(",", ":") strips the default spaces for compact output. This three-line round-trip is the baseline the library-based languages here are measured against.