-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjson.go
143 lines (117 loc) · 3.09 KB
/
json.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package yajson
import (
"errors"
"github.com/indigo-web/utils/buffer"
"github.com/indigo-web/utils/uf"
"github.com/indigo-web/yajson/flect"
"github.com/romshark/jscan/v2"
"reflect"
"strconv"
"strings"
"unsafe"
)
const (
BufferSpaceMin = 1024
BufferSpaceMax = 127 * 1024
)
var (
ErrNoSpace = errors.New("no space for values")
ErrTypeMismatch = errors.New("JSON carries an entry with an incompatible type")
ErrUnknownType = errors.New("the type is not supported by yajson")
)
type JSON[T any] struct {
model flect.Model[T]
buffer *buffer.Buffer
}
// New returns a JSON parser instance for one specific model, defined via the generic
func New[T any]() *JSON[T] {
return &JSON[T]{
model: flect.NewModel[T](new(pathDeserializer)),
buffer: buffer.New(BufferSpaceMin, BufferSpaceMax),
}
}
// Parse parses the passed JSON and stores the values into a new instance of the model
func (j *JSON[T]) Parse(input string) (result T, err error) {
jsonErr := jscan.Scan(input, func(i *jscan.Iterator[string]) (exit bool) {
key := i.Pointer()
if len(key) == 0 {
return false
}
field, found := j.model.Field(key)
if !found {
return false
}
switch field.Type {
case flect.String:
if i.ValueType() != jscan.ValueTypeString {
err = ErrTypeMismatch
return true
}
if !j.buffer.Append(uf.S2B(i.Value())) {
err = ErrNoSpace
return true
}
value := uf.B2S(j.buffer.Finish())
result = field.WriteString(result, value[1:len(value)-1])
case flect.Bool:
switch i.ValueType() {
case jscan.ValueTypeTrue, jscan.ValueTypeFalse:
default:
err = ErrTypeMismatch
return true
}
result = field.WriteBool(result, i.ValueType() == jscan.ValueTypeTrue)
case flect.Int, flect.I8, flect.I16, flect.I32, flect.I64:
if i.ValueType() != jscan.ValueTypeNumber {
err = ErrTypeMismatch
return true
}
num, er := strconv.ParseInt(i.Value(), 10, int(field.Size()))
if er != nil {
err = ErrTypeMismatch
return true
}
result = field.WriteUPtr(result, unsafe.Pointer(&num))
case flect.Uint, flect.U8, flect.U16, flect.U32, flect.U64:
if i.ValueType() != jscan.ValueTypeNumber {
err = ErrTypeMismatch
return true
}
num, er := strconv.ParseUint(i.Value(), 10, int(field.Size()))
if er != nil {
err = ErrTypeMismatch
return true
}
result = field.WriteUPtr(result, unsafe.Pointer(&num))
default:
err = ErrUnknownType
return true
}
return false
})
if jsonErr.IsErr() {
err = jsonErr
}
// this doesn't destroy data we've written, but will override them on the next call
j.buffer.Clear()
return result, err
}
type pathDeserializer struct {
stack []string
}
func (p *pathDeserializer) Descend(field reflect.StructField) {
p.stack = append(p.stack, field.Name)
}
func (p *pathDeserializer) Ascend() {
p.stack = p.stack[:len(p.stack)-1]
}
func (p *pathDeserializer) Visit(field reflect.StructField) (name string) {
name = "/"
if len(p.stack) > 0 {
name += strings.Join(p.stack, "/") + "/"
}
if tag, found := field.Tag.Lookup("json"); found {
return name + tag
}
return name + field.Name
}