README.md

5.24 Kb · 167 lines
  1# JSON Parser
  2
  3The JSON parser is a package that provides functionality for parsing and processing JSON strings. This package accepts JSON strings as byte slices.
  4
  5Currently, gno does not [support the `reflect` package](https://docs.gno.land/resources/effective-gno#reflection-is-never-clear), 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.
  6
  7After 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`.
  8
  9This package provides methods for manipulating, searching, and extracting the Node type.
 10
 11## State Machine
 12
 13To parse JSON strings, a [finite state machine](https://en.wikipedia.org/wiki/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.
 14
 15The image below shows the state transitions of the state machine according to the states and input characters.
 16
 17```mermaid
 18stateDiagram-v2
 19    [*] --> __: Start
 20    __ --> ST: String
 21    __ --> MI: Number
 22    __ --> ZE: Zero
 23    __ --> IN: Integer
 24    __ --> T1: Boolean (true)
 25    __ --> F1: Boolean (false)
 26    __ --> N1: Null
 27    __ --> ec: Empty Object End
 28    __ --> cc: Object End
 29    __ --> bc: Array End
 30    __ --> co: Object Begin
 31    __ --> bo: Array Begin
 32    __ --> cm: Comma
 33    __ --> cl: Colon
 34    __ --> OK: Success/End
 35    ST --> OK: String Complete
 36    MI --> OK: Number Complete
 37    ZE --> OK: Zero Complete
 38    IN --> OK: Integer Complete
 39    T1 --> OK: True Complete
 40    F1 --> OK: False Complete
 41    N1 --> OK: Null Complete
 42    ec --> OK: Empty Object Complete
 43    cc --> OK: Object Complete
 44    bc --> OK: Array Complete
 45    co --> OB: Inside Object
 46    bo --> AR: Inside Array
 47    cm --> KE: Expecting New Key
 48    cm --> VA: Expecting New Value
 49    cl --> VA: Expecting Value
 50    OB --> ST: String in Object (Key)
 51    OB --> ec: Empty Object
 52    OB --> cc: End Object
 53    AR --> ST: String in Array
 54    AR --> bc: End Array
 55    KE --> ST: String as Key
 56    VA --> ST: String as Value
 57    VA --> MI: Number as Value
 58    VA --> T1: True as Value
 59    VA --> F1: False as Value
 60    VA --> N1: Null as Value
 61    OK --> [*]: End
 62```
 63
 64## Examples
 65
 66This package provides parsing functionality along with encoding and decoding functionality. The following examples demonstrate how to use this package.
 67
 68### Decoding
 69
 70Decoding (or Unmarshaling) is the functionality that converts an input byte slice JSON string into a `Node` type.
 71
 72The converted `Node` type allows you to modify the JSON data or search and extract data that meets specific conditions.
 73
 74```go
 75package main
 76
 77import (
 78    "gno.land/p/demo/json"
 79    "gno.land/p/demo/ufmt"
 80)
 81
 82func main() {
 83    node, err := json.Unmarshal([]byte(`{"foo": "var"}`))
 84    if err != nil {
 85        ufmt.Errorf("error: %v", err)
 86    }
 87
 88    ufmt.Sprintf("node: %v", node)
 89}
 90```
 91
 92### Encoding
 93
 94Encoding (or Marshaling) is the functionality that converts JSON data represented as a Node type into a byte slice JSON string.
 95
 96> ⚠️ Caution: Converting a large `Node` type into a JSON string may _impact performance_. or might be cause _unexpected behavior_.
 97
 98```go
 99package main
100
101import (
102    "gno.land/p/demo/json"
103    "gno.land/p/demo/ufmt"
104)
105
106func main() {
107    node := ObjectNode("", map[string]*Node{
108        "foo": StringNode("foo", "bar"),
109        "baz": NumberNode("baz", 100500),
110        "qux": NullNode("qux"),
111    })
112
113    b, err := json.Marshal(node)
114    if err != nil {
115        ufmt.Errorf("error: %v", err)
116    }
117
118    ufmt.Sprintf("json: %s", string(b))
119}
120```
121
122### Searching
123
124Once 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.
125
126To 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.
127
128Here is an example of finding data with a specific key. For more examples, please refer to the [node.gno](node.gno) file.
129
130```go
131package main
132
133import (
134    "gno.land/p/demo/json"
135    "gno.land/p/demo/ufmt"
136)
137
138func main() {
139    root, err := Unmarshal([]byte(`{"foo": true, "bar": null}`))
140    if err != nil {
141        ufmt.Errorf("error: %v", err)
142    }
143
144    value, err := root.GetKey("foo")
145    if err != nil {
146        ufmt.Errorf("error occurred while getting key, %s", err)
147    }
148
149    if value.MustBool() != true {
150        ufmt.Errorf("value is not true")
151    }
152
153    value, err = root.GetKey("bar")
154    if err != nil {
155        t.Errorf("error occurred while getting key, %s", err)
156    }
157
158    _, err = root.GetKey("baz")
159    if err == nil {
160        t.Errorf("key baz is not exist. must be failed")
161    }
162}
163```
164
165## Contributing
166
167Please submit any issues or pull requests for this package through the GitHub repository at [gnolang/gno](<https://github.com/gnolang/gno>).