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