blob: 33da5e4e7d6b98cca55eb2dc490bef7f9fbc8725 [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package swag
16
17import (
18 "bytes"
19 "encoding/json"
20 "log"
21 "reflect"
22 "strings"
23 "sync"
24
25 "github.com/mailru/easyjson/jlexer"
26 "github.com/mailru/easyjson/jwriter"
27)
28
29// nullJSON represents a JSON object with null type
30var nullJSON = []byte("null")
31
32// DefaultJSONNameProvider the default cache for types
33var DefaultJSONNameProvider = NewNameProvider()
34
35const comma = byte(',')
36
37var closers map[byte]byte
38
39func init() {
40 closers = map[byte]byte{
41 '{': '}',
42 '[': ']',
43 }
44}
45
46type ejMarshaler interface {
47 MarshalEasyJSON(w *jwriter.Writer)
48}
49
50type ejUnmarshaler interface {
51 UnmarshalEasyJSON(w *jlexer.Lexer)
52}
53
54// WriteJSON writes json data, prefers finding an appropriate interface to short-circuit the marshaller
55// so it takes the fastest option available.
56func WriteJSON(data interface{}) ([]byte, error) {
57 if d, ok := data.(ejMarshaler); ok {
58 jw := new(jwriter.Writer)
59 d.MarshalEasyJSON(jw)
60 return jw.BuildBytes()
61 }
62 if d, ok := data.(json.Marshaler); ok {
63 return d.MarshalJSON()
64 }
65 return json.Marshal(data)
66}
67
68// ReadJSON reads json data, prefers finding an appropriate interface to short-circuit the unmarshaller
69// so it takes the fastes option available
70func ReadJSON(data []byte, value interface{}) error {
71 if d, ok := value.(ejUnmarshaler); ok {
72 jl := &jlexer.Lexer{Data: data}
73 d.UnmarshalEasyJSON(jl)
74 return jl.Error()
75 }
76 if d, ok := value.(json.Unmarshaler); ok {
77 return d.UnmarshalJSON(data)
78 }
79 return json.Unmarshal(data, value)
80}
81
82// DynamicJSONToStruct converts an untyped json structure into a struct
83func DynamicJSONToStruct(data interface{}, target interface{}) error {
84 // TODO: convert straight to a json typed map (mergo + iterate?)
85 b, err := WriteJSON(data)
86 if err != nil {
87 return err
88 }
89 return ReadJSON(b, target)
90}
91
92// ConcatJSON concatenates multiple json objects efficiently
93func ConcatJSON(blobs ...[]byte) []byte {
94 if len(blobs) == 0 {
95 return nil
96 }
97
98 last := len(blobs) - 1
99 for blobs[last] == nil || bytes.Equal(blobs[last], nullJSON) {
100 // strips trailing null objects
101 last = last - 1
102 if last < 0 {
103 // there was nothing but "null"s or nil...
104 return nil
105 }
106 }
107 if last == 0 {
108 return blobs[0]
109 }
110
111 var opening, closing byte
112 var idx, a int
113 buf := bytes.NewBuffer(nil)
114
115 for i, b := range blobs[:last+1] {
116 if b == nil || bytes.Equal(b, nullJSON) {
117 // a null object is in the list: skip it
118 continue
119 }
120 if len(b) > 0 && opening == 0 { // is this an array or an object?
121 opening, closing = b[0], closers[b[0]]
122 }
123
124 if opening != '{' && opening != '[' {
125 continue // don't know how to concatenate non container objects
126 }
127
128 if len(b) < 3 { // yep empty but also the last one, so closing this thing
129 if i == last && a > 0 {
130 if err := buf.WriteByte(closing); err != nil {
131 log.Println(err)
132 }
133 }
134 continue
135 }
136
137 idx = 0
138 if a > 0 { // we need to join with a comma for everything beyond the first non-empty item
139 if err := buf.WriteByte(comma); err != nil {
140 log.Println(err)
141 }
142 idx = 1 // this is not the first or the last so we want to drop the leading bracket
143 }
144
145 if i != last { // not the last one, strip brackets
146 if _, err := buf.Write(b[idx : len(b)-1]); err != nil {
147 log.Println(err)
148 }
149 } else { // last one, strip only the leading bracket
150 if _, err := buf.Write(b[idx:]); err != nil {
151 log.Println(err)
152 }
153 }
154 a++
155 }
156 // somehow it ended up being empty, so provide a default value
157 if buf.Len() == 0 {
158 if err := buf.WriteByte(opening); err != nil {
159 log.Println(err)
160 }
161 if err := buf.WriteByte(closing); err != nil {
162 log.Println(err)
163 }
164 }
165 return buf.Bytes()
166}
167
168// ToDynamicJSON turns an object into a properly JSON typed structure
169func ToDynamicJSON(data interface{}) interface{} {
170 // TODO: convert straight to a json typed map (mergo + iterate?)
171 b, err := json.Marshal(data)
172 if err != nil {
173 log.Println(err)
174 }
175 var res interface{}
176 if err := json.Unmarshal(b, &res); err != nil {
177 log.Println(err)
178 }
179 return res
180}
181
182// FromDynamicJSON turns an object into a properly JSON typed structure
183func FromDynamicJSON(data, target interface{}) error {
184 b, err := json.Marshal(data)
185 if err != nil {
186 log.Println(err)
187 }
188 return json.Unmarshal(b, target)
189}
190
191// NameProvider represents an object capabale of translating from go property names
192// to json property names
193// This type is thread-safe.
194type NameProvider struct {
195 lock *sync.Mutex
196 index map[reflect.Type]nameIndex
197}
198
199type nameIndex struct {
200 jsonNames map[string]string
201 goNames map[string]string
202}
203
204// NewNameProvider creates a new name provider
205func NewNameProvider() *NameProvider {
206 return &NameProvider{
207 lock: &sync.Mutex{},
208 index: make(map[reflect.Type]nameIndex),
209 }
210}
211
212func buildnameIndex(tpe reflect.Type, idx, reverseIdx map[string]string) {
213 for i := 0; i < tpe.NumField(); i++ {
214 targetDes := tpe.Field(i)
215
216 if targetDes.PkgPath != "" { // unexported
217 continue
218 }
219
220 if targetDes.Anonymous { // walk embedded structures tree down first
221 buildnameIndex(targetDes.Type, idx, reverseIdx)
222 continue
223 }
224
225 if tag := targetDes.Tag.Get("json"); tag != "" {
226
227 parts := strings.Split(tag, ",")
228 if len(parts) == 0 {
229 continue
230 }
231
232 nm := parts[0]
233 if nm == "-" {
234 continue
235 }
236 if nm == "" { // empty string means we want to use the Go name
237 nm = targetDes.Name
238 }
239
240 idx[nm] = targetDes.Name
241 reverseIdx[targetDes.Name] = nm
242 }
243 }
244}
245
246func newNameIndex(tpe reflect.Type) nameIndex {
247 var idx = make(map[string]string, tpe.NumField())
248 var reverseIdx = make(map[string]string, tpe.NumField())
249
250 buildnameIndex(tpe, idx, reverseIdx)
251 return nameIndex{jsonNames: idx, goNames: reverseIdx}
252}
253
254// GetJSONNames gets all the json property names for a type
255func (n *NameProvider) GetJSONNames(subject interface{}) []string {
256 n.lock.Lock()
257 defer n.lock.Unlock()
258 tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
259 names, ok := n.index[tpe]
260 if !ok {
261 names = n.makeNameIndex(tpe)
262 }
263
264 res := make([]string, 0, len(names.jsonNames))
265 for k := range names.jsonNames {
266 res = append(res, k)
267 }
268 return res
269}
270
271// GetJSONName gets the json name for a go property name
272func (n *NameProvider) GetJSONName(subject interface{}, name string) (string, bool) {
273 tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
274 return n.GetJSONNameForType(tpe, name)
275}
276
277// GetJSONNameForType gets the json name for a go property name on a given type
278func (n *NameProvider) GetJSONNameForType(tpe reflect.Type, name string) (string, bool) {
279 n.lock.Lock()
280 defer n.lock.Unlock()
281 names, ok := n.index[tpe]
282 if !ok {
283 names = n.makeNameIndex(tpe)
284 }
285 nme, ok := names.goNames[name]
286 return nme, ok
287}
288
289func (n *NameProvider) makeNameIndex(tpe reflect.Type) nameIndex {
290 names := newNameIndex(tpe)
291 n.index[tpe] = names
292 return names
293}
294
295// GetGoName gets the go name for a json property name
296func (n *NameProvider) GetGoName(subject interface{}, name string) (string, bool) {
297 tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
298 return n.GetGoNameForType(tpe, name)
299}
300
301// GetGoNameForType gets the go name for a given type for a json property name
302func (n *NameProvider) GetGoNameForType(tpe reflect.Type, name string) (string, bool) {
303 n.lock.Lock()
304 defer n.lock.Unlock()
305 names, ok := n.index[tpe]
306 if !ok {
307 names = n.makeNameIndex(tpe)
308 }
309 nme, ok := names.jsonNames[name]
310 return nme, ok
311}