// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// What a repeated member name means.
///
/// Every mainstream implementation keeps the last one — JavaScript, Python, Go
/// and serde all do — so that is the default. The other two are here because a
/// document with two `"id"` members is usually a mistake, and a reader that
/// wants to hear about it should not have to write its own parser.
pub(all) enum Duplicates {
/// The last one wins. What everybody does.
Last
/// The first one wins.
First
/// A repeated name is an error.
Reject
} derive(Eq, Debug)
///|
pub extend Duplicates with Eq::{equal, not_equal}
///|
pub extend Duplicates with Debug::{to_repr}
///|
/// How to read a document: what syntax to accept, and what to do at the edges.
///
/// The dialects in this family differ only in their syntax — the tree they
/// produce is the same — so the scanner is written once and each dialect is a
/// set of switches. A dialect outside this repository can be described the same
/// way without forking anything.
///
/// The last three fields are not syntax but policy. They are here rather than
/// as arguments because they are set once for a deployment and because this
/// family has sixteen entry points: a policy added here costs one line, and a
/// policy added to every signature costs sixteen.
pub(all) struct Flavor {
/// `//` to end of line, and `/* … */`.
comments : Bool
/// A comma before the closing brace or bracket.
trailing_commas : Bool
/// A member name written as an identifier rather than a string.
unquoted_keys : Bool
/// `'like this'` as well as `"like this"`.
single_quotes : Bool
/// `+1`, `.5`, `5.`, `0xFF`, `Infinity`, `-Infinity`, `NaN`.
extra_numbers : Bool
/// `\x41`, `\0`, `\v`, a backslash before a newline, and a backslash before
/// anything else standing for that character.
extra_escapes : Bool
/// The whitespace ECMAScript recognises, not only the four RFC 8259 allows.
extra_space : Bool
/// How deep the nesting may go before the document is refused.
///
/// Five hundred is past anything written for a reader and far short of what
/// exhausts a stack on any backend. The mainstream disagrees on the number —
/// Jackson refuses past 1000, serde_json past 128 — but not on having one,
/// because a document of a hundred thousand open brackets is a denial of
/// service rather than a document.
depth : Int
/// Whether a lone half of a surrogate pair is a character.
///
/// Implementations disagree: JavaScript keeps it, Go replaces it, serde_json
/// refuses it, and JSONTestSuite files these cases as implementation-defined.
/// The default refuses, because a lone half cannot be written back out and
/// every value that could replace it is a guess about what the writer meant.
surrogates : Bool
/// What a repeated member name means.
duplicates : Duplicates
} derive(Eq, Debug)
///|
pub extend Flavor with Eq::{equal, not_equal}
///|
pub extend Flavor with Debug::{to_repr}
///|
/// RFC 8259 and nothing more.
pub let strict : Flavor = {
comments: false,
trailing_commas: false,
unquoted_keys: false,
single_quotes: false,
extra_numbers: false,
extra_escapes: false,
extra_space: false,
depth: 500,
surrogates: false,
duplicates: Last,
}
///|
/// JSON with comments and trailing commas — what editors and their
/// configuration files have settled on, `tsconfig.json` being the best-known.
pub let commented : Flavor = {
..strict,
comments: true,
trailing_commas: true,
}
///|
/// JSON5: everything ECMAScript 5 would have accepted.
pub let relaxed : Flavor = {
comments: true,
trailing_commas: true,
unquoted_keys: true,
single_quotes: true,
extra_numbers: true,
extra_escapes: true,
extra_space: true,
depth: 500,
surrogates: false,
duplicates: Last,
}
///|
/// Build a dialect by naming the parts that differ from strict JSON.
///
/// The same thing can be written `{ ..@moonjson.strict, depth: 100 }`; this
/// form exists because a named argument reads better when several parts differ.
pub fn Flavor::new(
comments? : Bool = strict.comments,
trailing_commas? : Bool = strict.trailing_commas,
unquoted_keys? : Bool = strict.unquoted_keys,
single_quotes? : Bool = strict.single_quotes,
extra_numbers? : Bool = strict.extra_numbers,
extra_escapes? : Bool = strict.extra_escapes,
extra_space? : Bool = strict.extra_space,
depth? : Int = strict.depth,
surrogates? : Bool = strict.surrogates,
duplicates? : Duplicates = strict.duplicates,
) -> Flavor {
{
comments,
trailing_commas,
unquoted_keys,
single_quotes,
extra_numbers,
extra_escapes,
extra_space,
depth,
surrogates,
duplicates,
}
}