README.md

5.24 Kb · 168 lines

JSON Parser

The JSON parser is a package that provides functionality for parsing and processing JSON strings. This package accepts JSON strings as byte slices.

Currently, gno does not support the reflect package, so it cannot retrieve type information at runtime. Therefore, it is designed to infer and handle type information when parsing JSON strings using a state machine approach.

After passing through the state machine, JSON strings are represented as the Node type. The Node type represents nodes for JSON data, including various types such as ObjectNode, ArrayNode, StringNode, NumberNode, BoolNode, and NullNode.

This package provides methods for manipulating, searching, and extracting the Node type.

State Machine

To parse JSON strings, a finite state machine approach is used. The state machine transitions to the next state based on the current state and the input character while parsing the JSON string. Through this method, type information can be inferred and processed without reflect, and the amount of parser code can be significantly reduced.

The image below shows the state transitions of the state machine according to the states and input characters.

stateDiagram-v2
    [*] --> __: Start
    __ --> ST: String
    __ --> MI: Number
    __ --> ZE: Zero
    __ --> IN: Integer
    __ --> T1: Boolean (true)
    __ --> F1: Boolean (false)
    __ --> N1: Null
    __ --> ec: Empty Object End
    __ --> cc: Object End
    __ --> bc: Array End
    __ --> co: Object Begin
    __ --> bo: Array Begin
    __ --> cm: Comma
    __ --> cl: Colon
    __ --> OK: Success/End
    ST --> OK: String Complete
    MI --> OK: Number Complete
    ZE --> OK: Zero Complete
    IN --> OK: Integer Complete
    T1 --> OK: True Complete
    F1 --> OK: False Complete
    N1 --> OK: Null Complete
    ec --> OK: Empty Object Complete
    cc --> OK: Object Complete
    bc --> OK: Array Complete
    co --> OB: Inside Object
    bo --> AR: Inside Array
    cm --> KE: Expecting New Key
    cm --> VA: Expecting New Value
    cl --> VA: Expecting Value
    OB --> ST: String in Object (Key)
    OB --> ec: Empty Object
    OB --> cc: End Object
    AR --> ST: String in Array
    AR --> bc: End Array
    KE --> ST: String as Key
    VA --> ST: String as Value
    VA --> MI: Number as Value
    VA --> T1: True as Value
    VA --> F1: False as Value
    VA --> N1: Null as Value
    OK --> [*]: End

Examples

This package provides parsing functionality along with encoding and decoding functionality. The following examples demonstrate how to use this package.

Decoding

Decoding (or Unmarshaling) is the functionality that converts an input byte slice JSON string into a Node type.

The converted Node type allows you to modify the JSON data or search and extract data that meets specific conditions.

 1package main
 2
 3import (
 4    "gno.land/p/demo/json"
 5    "gno.land/p/demo/ufmt"
 6)
 7
 8func main() {
 9    node, err := json.Unmarshal([]byte(`{"foo": "var"}`))
10    if err != nil {
11        ufmt.Errorf("error: %v", err)
12    }
13
14    ufmt.Sprintf("node: %v", node)
15}

Encoding

Encoding (or Marshaling) is the functionality that converts JSON data represented as a Node type into a byte slice JSON string.

⚠️ Caution: Converting a large Node type into a JSON string may impact performance. or might be cause unexpected behavior.

 1package main
 2
 3import (
 4    "gno.land/p/demo/json"
 5    "gno.land/p/demo/ufmt"
 6)
 7
 8func main() {
 9    node := ObjectNode("", map[string]*Node{
10        "foo": StringNode("foo", "bar"),
11        "baz": NumberNode("baz", 100500),
12        "qux": NullNode("qux"),
13    })
14
15    b, err := json.Marshal(node)
16    if err != nil {
17        ufmt.Errorf("error: %v", err)
18    }
19
20    ufmt.Sprintf("json: %s", string(b))
21}

Searching

Once the JSON data converted into a Node type, you can search and extract data that satisfy specific conditions. For example, you can find data with a specific type or data with a specific key.

To use this functionality, you can use methods in the GetXXX prefixed methods. The MustXXX methods also provide the same functionality as the former methods, but they will panic if data doesn't satisfies the condition.

Here is an example of finding data with a specific key. For more examples, please refer to the node.gno file.

 1package main
 2
 3import (
 4    "gno.land/p/demo/json"
 5    "gno.land/p/demo/ufmt"
 6)
 7
 8func main() {
 9    root, err := Unmarshal([]byte(`{"foo": true, "bar": null}`))
10    if err != nil {
11        ufmt.Errorf("error: %v", err)
12    }
13
14    value, err := root.GetKey("foo")
15    if err != nil {
16        ufmt.Errorf("error occurred while getting key, %s", err)
17    }
18
19    if value.MustBool() != true {
20        ufmt.Errorf("value is not true")
21    }
22
23    value, err = root.GetKey("bar")
24    if err != nil {
25        t.Errorf("error occurred while getting key, %s", err)
26    }
27
28    _, err = root.GetKey("baz")
29    if err == nil {
30        t.Errorf("key baz is not exist. must be failed")
31    }
32}

Contributing

Please submit any issues or pull requests for this package through the GitHub repository at gnolang/gno.