x.json5 #
v-json5
JSON5 parsing, decoding and encoding for V.
JSON5 is a superset of JSON: everything that is valid JSON is valid JSON5, and JSON5 additionally allows comments, unquoted object keys, single-quoted strings, trailing commas, hexadecimal and leading- or trailing-dot numbers, a leading + on numbers, and Infinity/NaN.
Contents
- Install
- Parse
- Decode into V types
- Document access
- Encode
- Encode options
- Custom encoders
- Supported JSON5 syntax
- Errors
- Examples
Install
v install x.json5
Parse
parse returns the dynamic Any tree, and parse_text returns a Doc that also supports path lookup.
import x.json5
fn main() {
value := json5.parse('{ name: "V", tags: [1, 2], }') or { panic(err) }
println(value.as_map()['name'] or { json5.null }.string())
println(value.as_map()['tags'] or { json5.null }.array().len)
}
Decode into V types
decode[T] converts a JSON5 document into T.
import x.json5
struct Config {
name string
port int = 8080
ratio f64
}
fn main() {
cfg := json5.decode[Config]('{ name: "srv", ratio: .5 }') or { panic(err) }
println(cfg.name) // srv
println(cfg.port) // 8080, the default is kept for a missing key
}
Supported targets:
- structs, including embedded structs, which are flattened into the parent;
- enums, by name or by numeric value;
map[string]T,[]T,[N]T,?Tfields, and all integer and float widths;Anyitself, for a partially typed document.
A null value leaves a field at its default. A missing key also keeps the default, so a partially filled document decodes without error.
Field attributes
@[skip]ignores the field in both directions;@[json5: 'name']renames the field, and works on enum values too.
Hooks
A type can take over its own decoding. The hooks are tried in order and the first one defined wins:
fn (mut t T) from_json5(value Any);fn (mut t T) from_json5_string(raw string) !;fn (mut t T) from_json5_number(raw string) !;fn (mut t T) from_json5_boolean(raw bool) !;fn (mut t T) from_json5_null().
The string and number hooks receive the original literal source text, which means a hexadecimal literal such as 0x10 reaches the hook intact.
Document access
Doc keeps the parsed tree together with a small path query language.
import x.json5
fn main() {
doc := json5.parse_text('{ servers: [{ host: "a" }, { host: "b" }] }') or {
panic(err)
}
host := doc.get('servers[1].host') or { panic(err) }
println(host.string()) // b
// reflect fills what it can and leaves the rest at the defaults.
println(doc.reflect[Config]().name)
// decode reports a type error instead.
println(doc.decode[Config]() or { panic(err) }.name)
}
Encode
encode[T] writes compact output that is also valid JSON. encode_any writes an Any tree.
import x.json5
struct Point {
x int
y int
}
fn main() {
println(json5.encode(Point{x: 1, y: 2}))
println(json5.encode(json5.parse('{ a: 1 }') or { panic(err) }))
}
Encode options
encode_with_opts[T] takes an EncodeOpts value.
import x.json5
fn main() {
opts := json5.EncodeOpts{
indent: ' '
single_quotes: true
unquoted_keys: true
trailing_commas: true
}
println(json5.encode_with_opts(json5.parse('{ a: [1, 2] }') or { panic(err) },
opts))
}
indent: the indent unit; the zero value writes compact output;single_quotes: write strings with'instead of";unquoted_keys: write an object key bare when it is a valid identifier;trailing_commas: write a comma after the last array element and member.
Custom encoders
A type can control its own output with to_json5() string. The returned text is written verbatim, which is how a type controls quoting or emits a value that has no V equivalent.
import x.json5
struct Money {
cents int
}
fn (m Money) to_json5() string {
return '\$${m.cents / 100}.${m.cents % 100:02}'
}
fn main() {
println(json5.encode(Money{cents: 1999})) // $19.99
}
Supported JSON5 syntax
//line comments and/* */block comments;- unquoted object keys, restricted to identifiers and reserved words;
- single-quoted and double-quoted strings;
- trailing commas in objects and arrays;
0xhexadecimal integers, leading-dot and trailing-dot numbers, a leading+on numbers, andInfinity,-InfinityandNaN;- a leading byte order mark;
- escaped line continuations inside strings;
- JSON5 escape sequences in strings, including
\',\",\v,\0,\xNNand\uNNNN.
Hexadecimal numbers and escape sequences accept both uppercase A-F and lowercase a-f. Escaped UTF-16 surrogate pairs decode to a single Unicode character in strings and quoted keys. Unpaired surrogate escapes report a parse error instead of producing invalid UTF-8. When encoding optional values, present values keep their payload and none becomes null.
Errors
Parsing, decoding and encoding report typed errors that carry a source position:
ParseError, for malformed input;TypeError, for a value that does not fit the target type, including an integer that does not fit its width;NameError, for a string that matches no enum value;EnumError, for an out-of-range numeric enum value.
Examples
See the examples directory for complete runnable examples.
Constants #
const null = Any(Null{})
null is the Any value for a missing key, mirroring toml.null.
const default_encode_opts = EncodeOpts{}
default_encode_opts writes compact output that is also valid JSON.
fn decode #
fn decode[T](text string) !T
decode parses the JSON5 document in text and decodes it into T.
Supported targets are structs, enums, map[string]V, arrays (dynamic and fixed), options and all scalar types, plus the Any type itself. Struct fields are matched by name, or by an @[json5: 'name'] attribute when the document key differs; @[skip] leaves a field at its default value. An embedded struct is flattened, so its fields are read from the enclosing object.
A type may customise decoding by defining one of:
from_json5(value Any), to decode the whole valuefrom_json5_string(raw string) !,from_json5_number(raw string) !orfrom_json5_boolean(value bool), to decode a single scalar
A missing key leaves the field at its default. An explicit null clears an option field and leaves other fields at their default.
fn decode_any #
fn decode_any[T](value Any) !T
decode_any converts a parsed Any into T, applying the same rules as decode without re-parsing.
fn decode_file #
fn decode_file[T](path string) !T
decode_file reads the JSON5 document at path and decodes it into T.
fn encode #
fn encode[T](value T) string
encode writes value as compact JSON, which is also valid JSON5. A type may customise the output by defining to_json5() string.
fn encode_any #
fn encode_any(value Any, opts EncodeOpts) string
encode_any writes an Any tree as JSON5 text.
fn encode_with_opts #
fn encode_with_opts[T](value T, opts EncodeOpts) string
encode_with_opts writes value as JSON5 text shaped by opts.
fn new_doc #
fn new_doc(root Any) Doc
new_doc wraps an already parsed Any tree.
fn new_parser #
fn new_parser(text string) &Parser
new_parser creates a Parser over text.
fn new_scanner #
fn new_scanner(text string) &Scanner
new_scanner creates a Scanner over text, dropping a leading byte order mark.
fn parse #
fn parse(text string) !Any
parse parses the JSON5 document in text and returns its root Any. It is the shortest path from text to a dynamic value.
fn parse_file #
fn parse_file(path string) !Doc
parse_file reads and parses the JSON5 document at path.
fn parse_path #
fn parse_path(path string) ![]PathStep
parse_path splits a lookup path into steps. Quoted segments keep any dots they contain, so a."b.c" addresses the key b.c inside the object a.
The path is walked with an explicit index rather than a state machine, which keeps the "flush the pending key" logic in one place.
fn parse_text #
fn parse_text(text string) !Doc
parse_text parses the JSON5 document in text and returns a Doc.
fn quote_string #
fn quote_string(value string) string
quote_string returns value as a quoted JSON5 string.
fn reindent #
fn reindent(text string) !string
reindent reparses text and writes it back with two-space indentation, which is a convenience for tidying configuration files.
fn resolve_path #
fn resolve_path(value Any, steps []PathStep) Any
resolve_path walks steps through value, returning Null when a step does not resolve.
type Any #
type Any = Null
| Number
| Raw
| bool
| f64
| []Any
| map[string]Any
| string
Any is the dynamic representation of a parsed JSON5 value. It mirrors the shape of json2.Any, but numbers keep their literal source text so that Infinity, NaN and hexadecimal literals survive a round-trip.
fn (Any) str #
fn (a Any) str() string
str returns Any as JSON5 text.
fn (Any) string #
fn (a Any) string() string
string returns Any as a string. Non-string scalars are converted.
fn (Any) int #
fn (a Any) int() int
int returns Any as an int.
fn (Any) i64 #
fn (a Any) i64() i64
i64 returns Any as an i64.
fn (Any) u64 #
fn (a Any) u64() u64
u64 returns Any as a u64.
fn (Any) f64 #
fn (a Any) f64() f64
f64 returns Any as an f64.
fn (Any) bool #
fn (a Any) bool() bool
bool returns Any as a bool.
fn (Any) array #
fn (a Any) array() []Any
array returns Any as an array. An object is returned as its values, and any other value as a single-element array.
fn (Any) as_map #
fn (a Any) as_map() map[string]Any
as_map returns Any as an object. An array is keyed by its indices and any other value becomes a single-entry object under the key 0.
fn (Any) default_to #
fn (a Any) default_to(value Any) Any
default_to returns value if a is Null.
fn (Any) kind #
fn (a Any) kind() ValueKind
kind returns the JSON5 kind of a.
fn (Any) is_null #
fn (a Any) is_null() bool
is_null reports whether a is the JSON5 null literal.
fn (Any) is_number #
fn (a Any) is_number() bool
is_number reports whether a is a number.
fn (Any) is_string #
fn (a Any) is_string() bool
is_string reports whether a is a string.
fn ([]Any) as_strings #
fn (a []Any) as_strings() []string
as_strings returns the array's elements as strings.
fn (map[string]Any) as_strings #
fn (m map[string]Any) as_strings() map[string]string
as_strings returns the object's values as strings.
fn (map[string]Any) get #
fn (m map[string]Any) get(key string) ?Any
get returns the value stored under key, or none when it is absent.
fn (map[string]Any) has #
fn (m map[string]Any) has(key string) bool
has reports whether m contains key.
enum TokenKind #
enum TokenKind {
none
error
comment
str
ident
number
bool
null
infinity
nan
comma = 0x2C // ,
colon = 0x3A // :
lsbr = 0x5B // [
rsbr = 0x5D // ]
lcbr = 0x7B // {
rcbr = 0x7D // }
eof
}
TokenKind identifies the kind of a JSON5 token.
enum ValueKind #
enum ValueKind {
bool
number
string
null
array
object
}
ValueKind names the JSON5 kinds of value.
fn (ValueKind) str #
fn (k ValueKind) str() string
str returns the JSON5 kind name, for error messages.
struct Doc #
struct Doc {
pub:
root Any
}
Doc is a parsed JSON5 document. Use value, value_opt or decode to read from it.
fn (Doc) to_any #
fn (d Doc) to_any() Any
to_any returns the document root as a dynamic value.
fn (Doc) str #
fn (d Doc) str() string
str returns the document rendered as JSON5 text.
fn (Doc) value #
fn (d Doc) value(key string) Any
value queries a value with a small path syntax: dots separate object keys, brackets index arrays, and a key that contains a dot can be quoted, as in a."b.c". Returns Null when the path does not resolve.
fn (Doc) value_opt #
fn (d Doc) value_opt(key string) !Any
value_opt queries a value and returns an error when the path does not resolve, instead of Null.
fn (Doc) get #
fn (d Doc) get(key string) ?Any
get queries a value and returns an option, so none distinguishes a missing key from an explicit null.
fn (Doc) decode #
fn (d Doc) decode[T]() !T
decode decodes the document into T. It is the method form of the package-level decode and shares its attribute and hook support.
fn (Doc) reflect #
fn (d Doc) reflect[T]() T
reflect sets the fields of T from the matching document keys, leaving absent fields at their default value. Fields are matched by name or by a @[json5: 'name'] attribute. Unlike decode, a key that cannot be decoded into its field is silently skipped instead of reported, so a partially matching document still fills in what it can.
struct EncodeOpts #
struct EncodeOpts {
pub:
indent string // indent unit; the zero value writes compact output
single_quotes bool // write strings with `'` instead of `"`
unquoted_keys bool // write object keys bare when they are valid identifiers
trailing_commas bool // write a comma after the last array element and member
}
EncodeOpts controls how encode writes JSON5 text.
struct EnumError #
struct EnumError {
Error
pub:
enum_name string
value string
}
EnumError describes a string that does not name a variant of the target enum.
fn (EnumError) msg #
fn (e &EnumError) msg() string
msg formats an EnumError for IError.msg().
struct NameError #
struct NameError {
Error
pub:
key string
}
NameError describes a missing object key.
fn (NameError) msg #
fn (e &NameError) msg() string
msg formats a NameError for IError.msg().
struct Null #
struct Null {
}
Null is the JSON5 null literal.
fn (Null) str #
fn (n Null) str() string
str returns Null rendered as JSON5 text.
struct Number #
struct Number {
pub:
text string // literal source text, for example `0x1F` or `-Infinity`
}
Number is a JSON5 number. The literal source text is preserved so that 0x1F, +1, .5, 5., Infinity and NaN can be written back out unchanged.
fn (Number) str #
fn (n Number) str() string
str returns the number as its original literal text.
fn (Number) i64 #
fn (n Number) i64() i64
i64 returns the number as an i64. Hexadecimal and decimal integers convert exactly; fractional and non-finite values truncate toward zero.
fn (Number) int #
fn (n Number) int() int
int returns the number as an int.
fn (Number) u64 #
fn (n Number) u64() u64
u64 returns the number as a u64. Negative values clamp to zero.
fn (Number) f64 #
fn (n Number) f64() f64
f64 returns the number as an f64. Infinity and NaN keep their IEEE value.
struct ParseError #
struct ParseError {
Error
pub:
message string
line int
col int
}
ParseError describes a lexical or syntactic problem in the input.
fn (ParseError) msg #
fn (e &ParseError) msg() string
msg formats a ParseError for IError.msg().
struct Parser #
struct Parser {
mut:
scanner &Scanner
tok Token
started bool
}
Parser turns a stream of JSON5 tokens into an Any tree.
fn (Parser) parse #
fn (mut p Parser) parse() !Any
parse reads a single JSON5 value and checks that nothing but trivia follows.
struct PathStep #
struct PathStep {
pub:
key string
index int
}
PathStep is one element of a parsed lookup path: either an object key or an array index.
fn (PathStep) is_index #
fn (s PathStep) is_index() bool
is_index reports whether the step indexes an array.
fn (PathStep) str #
fn (s PathStep) str() string
str returns the step in path syntax.
struct Pos #
struct Pos {
pub:
line int
col int
}
Pos is a location inside the scanned text.
struct Raw #
struct Raw {
pub:
text string
}
Raw is JSON5 text that the encoder writes verbatim. A type's to_json5() method produces a Raw, which is how a custom encoder can control quoting, comments and spacing.
struct Scanner #
struct Scanner {
pub:
text []rune
mut:
pos int
line int = 1
col int = 1
}
Scanner tokenizes JSON5 text. Whitespace and comments are treated as trivia and skipped; every other token is reported to the caller.
fn (Scanner) next #
fn (mut s Scanner) next() !Token
next returns the next token, skipping whitespace and comments.
struct Token #
struct Token {
pub:
kind TokenKind
lit string // decoded value for strings, raw source text otherwise
pos Pos
}
Token is a single lexical unit produced by the Scanner.
struct TypeError #
struct TypeError {
Error
pub:
message string
path string // dotted path of the offending value, empty at the root
}
TypeError describes a value that does not fit the requested V type.
fn (TypeError) msg #
fn (e &TypeError) msg() string
msg formats a TypeError for IError.msg().
- README
- Constants
- fn decode
- fn decode_any
- fn decode_file
- fn encode
- fn encode_any
- fn encode_with_opts
- fn new_doc
- fn new_parser
- fn new_scanner
- fn parse
- fn parse_file
- fn parse_path
- fn parse_text
- fn quote_string
- fn reindent
- fn resolve_path
- type Any
- type []Any
- type map[string]Any
- enum TokenKind
- enum ValueKind
- struct Doc
- struct EncodeOpts
- struct EnumError
- struct NameError
- struct Null
- struct Number
- struct ParseError
- struct Parser
- struct PathStep
- struct Pos
- struct Raw
- struct Scanner
- struct Token
- struct TypeError