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>).