Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
7.8 KiB
312 lines
7.8 KiB
// Copyright 2015 go-swagger maintainers |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
|
|
package swag |
|
|
|
import ( |
|
"bytes" |
|
"encoding/json" |
|
"log" |
|
"reflect" |
|
"strings" |
|
"sync" |
|
|
|
"github.com/mailru/easyjson/jlexer" |
|
"github.com/mailru/easyjson/jwriter" |
|
) |
|
|
|
// nullJSON represents a JSON object with null type |
|
var nullJSON = []byte("null") |
|
|
|
// DefaultJSONNameProvider the default cache for types |
|
var DefaultJSONNameProvider = NewNameProvider() |
|
|
|
const comma = byte(',') |
|
|
|
var closers map[byte]byte |
|
|
|
func init() { |
|
closers = map[byte]byte{ |
|
'{': '}', |
|
'[': ']', |
|
} |
|
} |
|
|
|
type ejMarshaler interface { |
|
MarshalEasyJSON(w *jwriter.Writer) |
|
} |
|
|
|
type ejUnmarshaler interface { |
|
UnmarshalEasyJSON(w *jlexer.Lexer) |
|
} |
|
|
|
// WriteJSON writes json data, prefers finding an appropriate interface to short-circuit the marshaler |
|
// so it takes the fastest option available. |
|
func WriteJSON(data interface{}) ([]byte, error) { |
|
if d, ok := data.(ejMarshaler); ok { |
|
jw := new(jwriter.Writer) |
|
d.MarshalEasyJSON(jw) |
|
return jw.BuildBytes() |
|
} |
|
if d, ok := data.(json.Marshaler); ok { |
|
return d.MarshalJSON() |
|
} |
|
return json.Marshal(data) |
|
} |
|
|
|
// ReadJSON reads json data, prefers finding an appropriate interface to short-circuit the unmarshaler |
|
// so it takes the fastest option available |
|
func ReadJSON(data []byte, value interface{}) error { |
|
trimmedData := bytes.Trim(data, "\x00") |
|
if d, ok := value.(ejUnmarshaler); ok { |
|
jl := &jlexer.Lexer{Data: trimmedData} |
|
d.UnmarshalEasyJSON(jl) |
|
return jl.Error() |
|
} |
|
if d, ok := value.(json.Unmarshaler); ok { |
|
return d.UnmarshalJSON(trimmedData) |
|
} |
|
return json.Unmarshal(trimmedData, value) |
|
} |
|
|
|
// DynamicJSONToStruct converts an untyped json structure into a struct |
|
func DynamicJSONToStruct(data interface{}, target interface{}) error { |
|
// TODO: convert straight to a json typed map (mergo + iterate?) |
|
b, err := WriteJSON(data) |
|
if err != nil { |
|
return err |
|
} |
|
return ReadJSON(b, target) |
|
} |
|
|
|
// ConcatJSON concatenates multiple json objects efficiently |
|
func ConcatJSON(blobs ...[]byte) []byte { |
|
if len(blobs) == 0 { |
|
return nil |
|
} |
|
|
|
last := len(blobs) - 1 |
|
for blobs[last] == nil || bytes.Equal(blobs[last], nullJSON) { |
|
// strips trailing null objects |
|
last-- |
|
if last < 0 { |
|
// there was nothing but "null"s or nil... |
|
return nil |
|
} |
|
} |
|
if last == 0 { |
|
return blobs[0] |
|
} |
|
|
|
var opening, closing byte |
|
var idx, a int |
|
buf := bytes.NewBuffer(nil) |
|
|
|
for i, b := range blobs[:last+1] { |
|
if b == nil || bytes.Equal(b, nullJSON) { |
|
// a null object is in the list: skip it |
|
continue |
|
} |
|
if len(b) > 0 && opening == 0 { // is this an array or an object? |
|
opening, closing = b[0], closers[b[0]] |
|
} |
|
|
|
if opening != '{' && opening != '[' { |
|
continue // don't know how to concatenate non container objects |
|
} |
|
|
|
if len(b) < 3 { // yep empty but also the last one, so closing this thing |
|
if i == last && a > 0 { |
|
if err := buf.WriteByte(closing); err != nil { |
|
log.Println(err) |
|
} |
|
} |
|
continue |
|
} |
|
|
|
idx = 0 |
|
if a > 0 { // we need to join with a comma for everything beyond the first non-empty item |
|
if err := buf.WriteByte(comma); err != nil { |
|
log.Println(err) |
|
} |
|
idx = 1 // this is not the first or the last so we want to drop the leading bracket |
|
} |
|
|
|
if i != last { // not the last one, strip brackets |
|
if _, err := buf.Write(b[idx : len(b)-1]); err != nil { |
|
log.Println(err) |
|
} |
|
} else { // last one, strip only the leading bracket |
|
if _, err := buf.Write(b[idx:]); err != nil { |
|
log.Println(err) |
|
} |
|
} |
|
a++ |
|
} |
|
// somehow it ended up being empty, so provide a default value |
|
if buf.Len() == 0 { |
|
if err := buf.WriteByte(opening); err != nil { |
|
log.Println(err) |
|
} |
|
if err := buf.WriteByte(closing); err != nil { |
|
log.Println(err) |
|
} |
|
} |
|
return buf.Bytes() |
|
} |
|
|
|
// ToDynamicJSON turns an object into a properly JSON typed structure |
|
func ToDynamicJSON(data interface{}) interface{} { |
|
// TODO: convert straight to a json typed map (mergo + iterate?) |
|
b, err := json.Marshal(data) |
|
if err != nil { |
|
log.Println(err) |
|
} |
|
var res interface{} |
|
if err := json.Unmarshal(b, &res); err != nil { |
|
log.Println(err) |
|
} |
|
return res |
|
} |
|
|
|
// FromDynamicJSON turns an object into a properly JSON typed structure |
|
func FromDynamicJSON(data, target interface{}) error { |
|
b, err := json.Marshal(data) |
|
if err != nil { |
|
log.Println(err) |
|
} |
|
return json.Unmarshal(b, target) |
|
} |
|
|
|
// NameProvider represents an object capable of translating from go property names |
|
// to json property names |
|
// This type is thread-safe. |
|
type NameProvider struct { |
|
lock *sync.Mutex |
|
index map[reflect.Type]nameIndex |
|
} |
|
|
|
type nameIndex struct { |
|
jsonNames map[string]string |
|
goNames map[string]string |
|
} |
|
|
|
// NewNameProvider creates a new name provider |
|
func NewNameProvider() *NameProvider { |
|
return &NameProvider{ |
|
lock: &sync.Mutex{}, |
|
index: make(map[reflect.Type]nameIndex), |
|
} |
|
} |
|
|
|
func buildnameIndex(tpe reflect.Type, idx, reverseIdx map[string]string) { |
|
for i := 0; i < tpe.NumField(); i++ { |
|
targetDes := tpe.Field(i) |
|
|
|
if targetDes.PkgPath != "" { // unexported |
|
continue |
|
} |
|
|
|
if targetDes.Anonymous { // walk embedded structures tree down first |
|
buildnameIndex(targetDes.Type, idx, reverseIdx) |
|
continue |
|
} |
|
|
|
if tag := targetDes.Tag.Get("json"); tag != "" { |
|
|
|
parts := strings.Split(tag, ",") |
|
if len(parts) == 0 { |
|
continue |
|
} |
|
|
|
nm := parts[0] |
|
if nm == "-" { |
|
continue |
|
} |
|
if nm == "" { // empty string means we want to use the Go name |
|
nm = targetDes.Name |
|
} |
|
|
|
idx[nm] = targetDes.Name |
|
reverseIdx[targetDes.Name] = nm |
|
} |
|
} |
|
} |
|
|
|
func newNameIndex(tpe reflect.Type) nameIndex { |
|
var idx = make(map[string]string, tpe.NumField()) |
|
var reverseIdx = make(map[string]string, tpe.NumField()) |
|
|
|
buildnameIndex(tpe, idx, reverseIdx) |
|
return nameIndex{jsonNames: idx, goNames: reverseIdx} |
|
} |
|
|
|
// GetJSONNames gets all the json property names for a type |
|
func (n *NameProvider) GetJSONNames(subject interface{}) []string { |
|
n.lock.Lock() |
|
defer n.lock.Unlock() |
|
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type() |
|
names, ok := n.index[tpe] |
|
if !ok { |
|
names = n.makeNameIndex(tpe) |
|
} |
|
|
|
res := make([]string, 0, len(names.jsonNames)) |
|
for k := range names.jsonNames { |
|
res = append(res, k) |
|
} |
|
return res |
|
} |
|
|
|
// GetJSONName gets the json name for a go property name |
|
func (n *NameProvider) GetJSONName(subject interface{}, name string) (string, bool) { |
|
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type() |
|
return n.GetJSONNameForType(tpe, name) |
|
} |
|
|
|
// GetJSONNameForType gets the json name for a go property name on a given type |
|
func (n *NameProvider) GetJSONNameForType(tpe reflect.Type, name string) (string, bool) { |
|
n.lock.Lock() |
|
defer n.lock.Unlock() |
|
names, ok := n.index[tpe] |
|
if !ok { |
|
names = n.makeNameIndex(tpe) |
|
} |
|
nme, ok := names.goNames[name] |
|
return nme, ok |
|
} |
|
|
|
func (n *NameProvider) makeNameIndex(tpe reflect.Type) nameIndex { |
|
names := newNameIndex(tpe) |
|
n.index[tpe] = names |
|
return names |
|
} |
|
|
|
// GetGoName gets the go name for a json property name |
|
func (n *NameProvider) GetGoName(subject interface{}, name string) (string, bool) { |
|
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type() |
|
return n.GetGoNameForType(tpe, name) |
|
} |
|
|
|
// GetGoNameForType gets the go name for a given type for a json property name |
|
func (n *NameProvider) GetGoNameForType(tpe reflect.Type, name string) (string, bool) { |
|
n.lock.Lock() |
|
defer n.lock.Unlock() |
|
names, ok := n.index[tpe] |
|
if !ok { |
|
names = n.makeNameIndex(tpe) |
|
} |
|
nme, ok := names.jsonNames[name] |
|
return nme, ok |
|
}
|
|
|