gopherjs - json/encode.go
1 package json
2
3 import "github.com/gopherjs/gopherjs/js"
4
5 // Marshal encodes v into a byte slice, using the same rules as stdlib/json
6 func Marshal(v interface{}) ([]byte, error) {
7 return MarshalIndent(v, "", "")
8 }
9
10 // MarshalIndent is like Marshal, but applies the given indentation and prefix
11 // to the beginning of the line
12 func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
13 s, err := MarshalIndentString(v, prefix, indent)
14 return []byte(s), err
15 }
16
17 // MarshalString encodes v into a byte slice, using the same rules as
18 // stdlib/json
19 func MarshalString(v interface{}) (string, error) {
20 return MarshalIndentString(v, "", "")
21 }
22
23 // MarshalIndentString is like MarshalIndent, but outputs a string
24 func MarshalIndentString(v interface{}, prefix, indent string) (json string, err error) {
25 defer func() {
26 if e := recover(); e != nil {
27 if er, ok := e.(*js.Error); ok {
28 err = er
29 }
30 }
31 }()
32 str := js.Global.Get("JSON").Call("stringify", js.InternalObject(toObject).Invoke(js.InternalObject(v)), nil, indent)
33 if len(prefix) > 0 {
34 str = str.Call("replace", "\n", "\n"+prefix)
35 }
36 return str.String(), nil
37 }
38
39 const (
40 boolKind = 1
41 intKind = 2
42 int8Kind = 3
43 int16Kind = 4
44 int32Kind = 5
45 int64Kind = 6
46 uintKind = 7
47 uint8Kind = 8
48 uint16Kind = 9
49 uint32Kind = 10
50 uint64Kind = 11
51 uintptrKind = 12
52 float32Kind = 13
53 float64Kind = 14
54 complex64Kind = 15
55 complex128Kind = 16
56 arrayKind = 17
57 interfaceKind = 20
58 mapKind = 21
59 ptrKind = 22
60 sliceKind = 23
61 stringKind = 24
62 structKind = 25
63 )
64
65 func unwrap(v, t *js.Object) (*js.Object, *js.Object) {
66 Loop:
67 for {
68 switch t.Get("kind").Int() {
69 case ptrKind:
70 if v.Get("$val") != t.Get("nil") {
71 v = v.Call("$get")
72 t = t.Get("elem")
73 } else {
74 break Loop
75 }
76 case interfaceKind:
77 t = v.Get("constructor")
78 v = v.Get("$val")
79 default:
80 break Loop
81 }
82 }
83 return v, t
84 }
85
86 func wrap(v, t *js.Object) *js.Object {
87 nt := js.Global.Get("Object").New()
88 nt.Set("elem", t)
89 nt.Set("kind", ptrKind)
90 nv := js.Global.Get("Object").New()
91 nv.Set("$val", v)
92 nv.Set("constructor", nt)
93 nv.Set("$get", js.MakeFunc(func(this *js.Object, _ []*js.Object) interface{} {
94 return this.Get("$val")
95 }))
96 return nv
97 }
98
99 func toObject(v *js.Object) *js.Object {
100 t := v.Get("constructor")
101 v, t = unwrap(v, t)
102 switch t.Get("kind").Int() {
103 case boolKind, intKind, int8Kind, int16Kind, int32Kind, uintKind, uint8Kind, uint16Kind, uint32Kind, uintptrKind, float32Kind, float64Kind, int64Kind, uint64Kind, complex64Kind, complex128Kind, ptrKind, stringKind:
104 return js.Global.Call("$externalize", v, t)
105 case arrayKind, sliceKind:
106 l := v.Get("$length").Int()
107 a := js.Global.Get("Array").New(l)
108 for i := 0; i < l; i++ {
109 a.SetIndex(i, js.InternalObject(toObject).Invoke(v.Get("$array").Index(i)))
110 }
111 return a
112 case mapKind:
113 m := js.Global.Get("Object").New()
114 keys := js.Global.Get("Object").Call("keys", v)
115 len := keys.Length()
116 for i := 0; i < len; i++ {
117 val := v.Get(keys.Index(i).String())
118 m.Set(val.Get("k").String(), js.InternalObject(toObject).Invoke(val.Get("v")))
119 }
120 return m
121 case structKind:
122 if t.Get("name").String() == "JSObject" { // needs checking
123 return v.Get("Object")
124 }
125 s := js.Global.Get("Object").New()
126 nextLevel := [][2]*js.Object{{v.Get("$val"), t}}
127 fieldsTodo := make(map[string]*js.Object)
128 for len(nextLevel) > 0 {
129 level := nextLevel
130 //nextLevel = nextLevel[:0]
131 nextLevel = make([][2]*js.Object, 0)
132 for len(level) > 0 {
133 v := level[0][0]
134 t := level[0][1]
135 level = level[1:]
136 fields := t.Get("fields")
137 fieldsLen := fields.Length()
138 for i := 0; i < fieldsLen; i++ {
139 f := fields.Index(i)
140 if f.Get("pkg").Length() > 0 {
141 continue
142 }
143 fName := f.Get("prop").String()
144 name := f.Get("name").String()
145 anon := false
146 if name == "" {
147 name = fName // filter $x?
148 anon = true
149 }
150 n, o := parseTag(getJSONTag(f.Get("tag")))
151 if n == "-" || (o.Contains("omitempty") && isEmpty(v.Get(fName), f.Get("typ"))) {
152 continue
153 }
154 jName := name
155 if n != "" {
156 jName = n
157 }
158 if anon {
159 nv, nt := unwrap(v.Get(fName), f.Get("typ"))
160 if nv != nt.Get("nil") {
161 nextLevel = append(nextLevel, [2]*js.Object{nv, nt})
162 }
163 } else if _, ok := fieldsTodo[jName]; ok {
164 fieldsTodo[jName] = nil
165 } else {
166 val := v.Get(fName)
167 vt := f.Get("typ")
168 val, vt = unwrap(val, vt)
169 if o.Contains("string") && stringable(vt) {
170 s := js.Global.Get("Object").New()
171 s.Set("kind", stringKind)
172 if vt.Get("kind").Int() == stringKind {
173 val = wrap(js.Global.Get("JSON").Call("stringify", val), s)
174 } else {
175 val = wrap(val.Call("toString"), s)
176 }
177 } else if val.Get("constructor").Get("kind") == js.Undefined {
178 val = wrap(val, vt)
179 }
180 fieldsTodo[jName] = val
181 }
182 }
183 }
184 for name, f := range fieldsTodo {
185 if f != nil {
186 if isMarshaler(f) {
187 tup := f.Call("MarshalJSON")
188 if tup.Index(1) != js.Global.Get("$ifaceNil") {
189 panic(tup.Index(1))
190 }
191 s.Set(name, js.Global.Get("JSON").Call("parse", js.Global.Call("$bytesToString", tup.Index(0)).String()))
192 } else {
193 s.Set(name, js.InternalObject(toObject).Invoke(f))
194 }
195 fieldsTodo[name] = nil
196 }
197 }
198 }
199 return s
200 }
201 return js.Undefined
202 }
203
204 func isEmpty(v, t *js.Object) bool {
205 switch t.Get("kind").Int() {
206 case boolKind:
207 return !v.Bool()
208 case int8Kind, int16Kind, int32Kind, intKind, uint8Kind, uint16Kind, uint32Kind, uintKind:
209 return v.Int() == 0
210 case int64Kind, uint64Kind:
211 return v.Int64() == 0
212 case float32Kind, float64Kind:
213 return v.Float() == 0
214 case arrayKind, sliceKind, stringKind:
215 return v.Length() == 0
216 case ptrKind:
217 return v == t.Get("nil")
218 case interfaceKind:
219 if v.Get("$val") == js.Undefined {
220 return true
221 }
222 return isEmpty(v.Get("$val"), v.Get("constructor"))
223 case mapKind:
224 return js.Global.Get("Object").Call("keys", v).Length() == 0
225 }
226 return false
227 }
228
229 func stringable(t *js.Object) bool {
230 switch t.Get("kind").Int() {
231 case boolKind, int8Kind, int16Kind, int32Kind, intKind, uint8Kind, uint16Kind, uint32Kind, uintKind, int64Kind, uint64Kind, float32Kind, float64Kind, stringKind:
232 return true
233 }
234 return false
235 }
236
237 func isMarshaler(v *js.Object) bool {
238 _, t := unwrap(v, v.Get("constructor"))
239 methods := t.Get("methods")
240 if methods == js.Undefined {
241 return false
242 }
243 methodNum := methods.Length()
244 for i := 0; i < methodNum; i++ {
245 method := methods.Index(i)
246 if method.Get("name").String() == "MarshalJSON" {
247 mt := method.Get("typ")
248 return mt.Get("params").Length() == 0 && mt.Get("results").Length() == 2 && mt.Get("results").Index(0).Get("string").String() == "[]uint8" && mt.Get("results").Index(1).Get("string").String() == "error"
249 }
250 }
251 return false
252 }