Skip to content

Super loose JSON + TOML parsers and unmarshaller for Go. JSONC and JSON5 can also be read.

License

Notifications You must be signed in to change notification settings

shellyln/go-loose-json-parser

Folders and files

NameName
Last commit message
Last commit date

Latest commit

May 4, 2023
00a4dc4 · May 4, 2023

History

88 Commits
Mar 13, 2023
Mar 12, 2023
Feb 10, 2023
May 4, 2023
May 4, 2023
Apr 25, 2023
Feb 18, 2023
Feb 4, 2023
May 4, 2023
Feb 4, 2023
Apr 25, 2023
Apr 25, 2023
Feb 18, 2023
Feb 11, 2023
Feb 11, 2023
Feb 12, 2023

Repository files navigation

Loose JSON + TOML parsers for Go

Super loose JSON + TOML parsers and unmarshaller for Go.
JSONC and JSON5 can also be read.

Test release Go version

logo


📦 Example app

🚀 Getting started

JSON

package main

import (
    "fmt"
    "github.com/shellyln/go-loose-json-parser/jsonlp"
)

func main() {
    // src: Loose JSON
    //
    // plafLb:
    // * Platform-dependent line break.
    //   * `Linebreak_Lf` | `Linebreak_CrLf` | `Linebreak_Cr`
    // * Line break codes in multi-line string are replaced by this specified line break.
    //   (Excluding line breaks by escape sequences)
    //
    // interop:
    // * If `Interop_JSON` is set,
    //   replace NaN, Infinity, complex number
    //   by `{nan:true}`, `{inf:+/-1}`, `{re:re,im:im}` .
    // * If `Interop_TOML` is set,
    //   replace complex number by `{re:re,im:im}` .
    // * If `Interop_JSON_AsNull` is set,
    //   replace NaN, Infinity, complex number by null.
    // * If `Interop_TOML_AsNull` is set,
    //   replace complex number by null.
    //
    // parsed:
    //   nil | []any | map[string]any |
    //   float64 | int64 | uint64 | complex128 |
    //   string | bool | time.Time
    parsed, err := jsonlp.ParseJSON(`{
        // comment
        config: {
            addr: '127.0.0.1',
        }
    }`, jsonlp.Linebreak_Lf, jsonlp.Interop_None)

    if err != nil {
        fmt.Printf("Parse: error = %v\n", err)
        return
    }

    fmt.Printf("Parsed = %v\n", parsed)
}

TOML

package main

import (
    "fmt"
    "github.com/shellyln/go-loose-json-parser/jsonlp"
)

func main() {
    // src: Loose TOML
    //
    // plafLb:
    // * Platform-dependent line break.
    //   * `Linebreak_Lf` | `Linebreak_CrLf` | `Linebreak_Cr`
    // * Line break codes in multi-line string are replaced by this specified line break.
    //   (Excluding line breaks by escape sequences)
    //
    // interop:
    // * If `Interop_JSON` is set,
    //   replace NaN, Infinity, complex number
    //   by `{nan:true}`, `{inf:+/-1}`, `{re:re,im:im}` .
    // * If `Interop_TOML` is set,
    //   replace complex number by `{re:re,im:im}` .
    // * If `Interop_JSON_AsNull` is set,
    //   replace NaN, Infinity, complex number by null.
    // * If `Interop_TOML_AsNull` is set,
    //   replace complex number by null.
    //
    // parsed:
    //   nil | []any | map[string]any |
    //   float64 | int64 | uint64 | complex128 |
    //   string | bool | time.Time
    parsed, err := jsonlp.ParseTOML(`
    # comment
    [config]
    addr = '127.0.0.1'
    `, jsonlp.Linebreak_Lf, jsonlp.Interop_None)

    if err != nil {
        fmt.Printf("Parse: error = %v\n", err)
        return
    }

    fmt.Printf("Parsed = %v\n", parsed)
}

Unmarshal

Mapping untyped data to a typed variable.

package main

import (
    "fmt"
    "github.com/shellyln/go-loose-json-parser/jsonlp"
    "github.com/shellyln/go-loose-json-parser/marshal"
)

type config struct {
    Addr string `json:"addr"`
}

type response struct {
    Config config `json:"config"`
}

func main() {
    parsed, err := jsonlp.ParseJSON(`{
        // comment
        config: {
            addr: '127.0.0.1',
        }
    }`, jsonlp.Linebreak_Lf, jsonlp.Interop_None)

    if err != nil {
        fmt.Printf("Parse: error = %v\n", err)
        return
    }

    var typed response

    // src:  Source data. Untyped in typical use.
    // dst:  Pointer to result data. Typed in typical use.
    // opts: Pointer to struct of the `Unmarshal` options. If nil, use default.
    //       Default options are {
    //           TagName: "json",               // Tag name of the struct fields
    //           NoCopyUnexportedFields: false, // If true, no shallow copying of unexported fields
    //           NoCustomMarshaller: false,     // If true, IMarshal and IUnmarshal are not used
    //       }
    if err := marshal.Unmarshal(parsed, &typed, nil); err != nil {
        fmt.Printf("Unmarshal: error = %v\n", err)
        return
    }

    fmt.Printf("Typed = %v\n", typed)
}

Note
Unmarshal also works well for typed to untyped conversions and as deep cloning.

🥅 Goal

  • ✅ Can read strict TOML.
  • ✅ Can read loose JSON, JSONC, JSON5, and TOML for configuration files.
  • ✅ Can be mapped to structs, untyped maps/slices, or primitives.
  • ✅ Can read many list and map literals in JavaScript, Python, PHP, Ruby, etc.

🚧 ToDo

APIs

  • Marshalling from any to typed

TOML

  • Datetime format with date and time delimited by space (RFC 3339 section 5.6)
    • e.g. 2006-01-02 15:04:05Z
  • Datetime format without timezone
    • e.g. 2006-01-02T15:04:05
  • Platform-dependent newline in multiline string
  • Error detection when values are overwritten

🪄 Examples

JSON

# Hash comment
{
    // Line comment
    /* Block comment */

    // Object keys can be enclosed in either double-quote, single-quote, back-quote.
    // It is also allowed not to enclose them.

    "foo": [
        123,          // -> float64(123)
        -123.45,      // -> float64(-123.45)
        -1.2345e+6,   // -> float64(-1234500)
        -123_456_789, // -> float64(-123456789)
        0x12345678,   // -> float64(305419896)
        0x1234_5678,  // -> float64(305419896)
        0o7654_3210,  // -> float64(16434824)
        0b0101_0101,  // -> float64(85)
    ],

    'bar': null,      // -> nil
    baz: undefined,   // -> nil
    "qux": -Infinity, // -> -Inf // Infinity, +Infinity are also available
    "quux": NaN,      // -> NaN

    // Non-ASCII identifiers can also be used.
    あいうえお: {
        key: "value1",
        bare_key: "value2",
        bare-key: "value3", // Keys containing hyphens are allowed.
        1234: "value4",     // Keys starting with a number are allowed.
        -3.14: "value5",    // "-3": { "14": "value5" }
    },

    "corge": [
        // Escape sequences are available
        'Hello, World!\r\n',

        // Unicode escape sequences (\uXXXX and \u{XXXXXX}) are available
        "\u0048\u0065\u006c\u006c\u006f\u002c\u0020\u0057\u006f\u0072\u006c\u0064\u0021",

        // Byte escape sequence is also available
        "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21",

        // Multiline string literal
        `Tempor adipiscing amet velit ipsum diam ut ea takimata lorem gubergren sed laoreet.
        Congue possim facilisis sea justo dolore amet eos dolores est magna.`
    ],

    "grault": [
        // Date, Time, DateTime literals are available

        2020-12-31,              // -> time.Time 2020-12-31:00:00.000Z
        18:20:30.001,            // -> time.Time 1870-01-01T18:20:30.001Z
        2020-12-31T18:20:30.001Z // -> time.Time 2020-12-31:20:30.001Z
    ],

    // "key = value" syntax is allowed
    garply = true,

    // "key => value" syntax is allowed
    waldo => false,

    // Trailing commas are allowed.
    fred: 10,
}

See also: Introducing JSON, JSON5 Spec

TOML

# Hash comment
// Line comment (non-standard)
/* Block comment (non-standard) */

"quoted-key" = "value0"
key          = "value1"
bare_key     = "value2"
bare-key     = "value3"  # Keys containing hyphens are allowed.
1234         = "value4"  # Keys starting with a number are allowed.
-3.14        = "value5"  # "-3": { "14": "value5" }
あいうえお    = "value6"  # // Non-ASCII identifiers are allowed. (non-standard)

[table-A]
item-a = 1
item-b = 2
item-c = "standard\n string"
item-d = '\literal string'
item-e = """\
         Multiline \
         standard string"""
item-f = '''
Multiline
literal string''' "\t" '''!!'''

[table-A.sub-table-P]
item-m = 11
item-n = 12
sub-table-X.sub-table-Y = 111

[[array-of-table-U]]
foo = 1
[array-of-table-U.sub-table-V]
bar = 11

[[array-of-table-U]]
foo = 2
[array-of-table-U.sub-table-V]
bar = 22

See also: TOML Spec

📚 Syntax

Array

[]
[1]
[1,]
[1, 2]
[1, 2,]

Object

{}
{ "foo": 1 }
{ "foo": 1, }
{ 'foo': 1, }
{ `foo`: 1, }
{ foo: 1, }
{ "foo": 1, "bar": 2, }
{ "foo" => 1 }
{ "foo" = 1 }
{ foo.bar.baz = 1 }
// -> { "foo": { "bar": { "baz": 1 } } }
{ "foo".bar."baz" = 1 }
// -> { "foo": { "bar": { "baz": 1 } } }

Number

123
-123
-123s64
123u64
-123.45
-1.2345e+6
-123_456_789
-123_456.789_012
0x12345678
0x1234_5678
0o7654_3210
0b0101_0101
0x1p-2
0x1.Fp+0
0X_1_FFF_P-16
NaN
nan
Infinity
+Infinity
-Infinity
inf
+inf
-inf

Complex

1.23 - 34.5i
1.23e+1 - 34.5e-1i
0x1.8p+1 - 0x1.8p-1i
NaN-NaNi
NaN - NaN_i
Infinity-Infinityi
Infinity - Infinity_i

String

"foobar"
'foobar'
`foo
bar`
"""foo
bar"""
'''foo
bar'''
"Hello\n\r\v\t\b\fWorld!"
"\u0048\u0065\u006c\u006c\u006f\u002c\u0020\u0057\u006f\u0072\u006c\u0064\u0021"
"\u{000048}\u{000065}\u{00006c}\u{00006c}\u{00006f}\u{00002c}\u{000020}\u{000057}\u{00006f}\u{000072}\u{00006c}\u{000064}\u{000021}"
"\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21"
"foo" "bar"
// -> "foobar"
'foo' 'bar'
// -> 'foobar'

Boolean

true
false

Null, Undefined

null
// -> nil
undefined
// -> nil
None
// -> nil

Comment

# Hash line comment
// Line comment
/*
   Block comment
*/

⚖️ License

MIT
Copyright (c) 2023 Shellyl_N and Authors.


Powered by takenoco Parser Combinator Library