vendor ffjson deps required during generation
ffjson requires some packages during intermediate go generation steps that are not present in the `./vendor` directory since the final files do not require it. To trick go modules into believing that we need those dependencies, add a `ffjson_deps.go` file referencing them. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
parent
d85da313ef
commit
fada7dd6e2
|
|
@ -0,0 +1,10 @@
|
|||
package storage
|
||||
|
||||
// NOTE: this is a hack to trick go modules into vendoring the below
|
||||
// dependencies. Those are required during ffjson generation
|
||||
// but do NOT end up in the final file.
|
||||
|
||||
import (
|
||||
_ "github.com/pquerna/ffjson/inception" // nolint:typecheck
|
||||
_ "github.com/pquerna/ffjson/shared" // nolint:typecheck
|
||||
)
|
||||
|
|
@ -0,0 +1,323 @@
|
|||
/**
|
||||
* Copyright 2014 Paul Querna
|
||||
*
|
||||
* 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 ffjsoninception
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pquerna/ffjson/shared"
|
||||
)
|
||||
|
||||
var validValues []string = []string{
|
||||
"FFTok_left_brace",
|
||||
"FFTok_left_bracket",
|
||||
"FFTok_integer",
|
||||
"FFTok_double",
|
||||
"FFTok_string",
|
||||
"FFTok_bool",
|
||||
"FFTok_null",
|
||||
}
|
||||
|
||||
func CreateUnmarshalJSON(ic *Inception, si *StructInfo) error {
|
||||
out := ""
|
||||
ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true
|
||||
if len(si.Fields) > 0 {
|
||||
ic.OutputImports[`"bytes"`] = true
|
||||
}
|
||||
ic.OutputImports[`"fmt"`] = true
|
||||
|
||||
out += tplStr(decodeTpl["header"], header{
|
||||
IC: ic,
|
||||
SI: si,
|
||||
})
|
||||
|
||||
out += tplStr(decodeTpl["ujFunc"], ujFunc{
|
||||
SI: si,
|
||||
IC: ic,
|
||||
ValidValues: validValues,
|
||||
ResetFields: ic.ResetFields,
|
||||
})
|
||||
|
||||
ic.OutputFuncs = append(ic.OutputFuncs, out)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleField(ic *Inception, name string, typ reflect.Type, ptr bool, quoted bool) string {
|
||||
return handleFieldAddr(ic, name, false, typ, ptr, quoted)
|
||||
}
|
||||
|
||||
func handleFieldAddr(ic *Inception, name string, takeAddr bool, typ reflect.Type, ptr bool, quoted bool) string {
|
||||
out := fmt.Sprintf("/* handler: %s type=%v kind=%v quoted=%t*/\n", name, typ, typ.Kind(), quoted)
|
||||
|
||||
umlx := typ.Implements(unmarshalFasterType) || typeInInception(ic, typ, shared.MustDecoder)
|
||||
umlx = umlx || reflect.PtrTo(typ).Implements(unmarshalFasterType)
|
||||
|
||||
umlstd := typ.Implements(unmarshalerType) || reflect.PtrTo(typ).Implements(unmarshalerType)
|
||||
|
||||
out += tplStr(decodeTpl["handleUnmarshaler"], handleUnmarshaler{
|
||||
IC: ic,
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
Ptr: reflect.Ptr,
|
||||
TakeAddr: takeAddr || ptr,
|
||||
UnmarshalJSONFFLexer: umlx,
|
||||
Unmarshaler: umlstd,
|
||||
})
|
||||
|
||||
if umlx || umlstd {
|
||||
return out
|
||||
}
|
||||
|
||||
// TODO(pquerna): generic handling of token type mismatching struct type
|
||||
switch typ.Kind() {
|
||||
case reflect.Int,
|
||||
reflect.Int8,
|
||||
reflect.Int16,
|
||||
reflect.Int32,
|
||||
reflect.Int64:
|
||||
|
||||
allowed := buildTokens(quoted, "FFTok_string", "FFTok_integer", "FFTok_null")
|
||||
out += getAllowTokens(typ.Name(), allowed...)
|
||||
|
||||
out += getNumberHandler(ic, name, takeAddr || ptr, typ, "ParseInt")
|
||||
|
||||
case reflect.Uint,
|
||||
reflect.Uint8,
|
||||
reflect.Uint16,
|
||||
reflect.Uint32,
|
||||
reflect.Uint64:
|
||||
|
||||
allowed := buildTokens(quoted, "FFTok_string", "FFTok_integer", "FFTok_null")
|
||||
out += getAllowTokens(typ.Name(), allowed...)
|
||||
|
||||
out += getNumberHandler(ic, name, takeAddr || ptr, typ, "ParseUint")
|
||||
|
||||
case reflect.Float32,
|
||||
reflect.Float64:
|
||||
|
||||
allowed := buildTokens(quoted, "FFTok_string", "FFTok_double", "FFTok_integer", "FFTok_null")
|
||||
out += getAllowTokens(typ.Name(), allowed...)
|
||||
|
||||
out += getNumberHandler(ic, name, takeAddr || ptr, typ, "ParseFloat")
|
||||
|
||||
case reflect.Bool:
|
||||
ic.OutputImports[`"bytes"`] = true
|
||||
ic.OutputImports[`"errors"`] = true
|
||||
|
||||
allowed := buildTokens(quoted, "FFTok_string", "FFTok_bool", "FFTok_null")
|
||||
out += getAllowTokens(typ.Name(), allowed...)
|
||||
|
||||
out += tplStr(decodeTpl["handleBool"], handleBool{
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
TakeAddr: takeAddr || ptr,
|
||||
})
|
||||
|
||||
case reflect.Ptr:
|
||||
out += tplStr(decodeTpl["handlePtr"], handlePtr{
|
||||
IC: ic,
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
Quoted: quoted,
|
||||
})
|
||||
|
||||
case reflect.Array,
|
||||
reflect.Slice:
|
||||
out += getArrayHandler(ic, name, typ, ptr)
|
||||
|
||||
case reflect.String:
|
||||
// Is it a json.Number?
|
||||
if typ.PkgPath() == "encoding/json" && typ.Name() == "Number" {
|
||||
// Fall back to json package to rely on the valid number check.
|
||||
// See: https://github.com/golang/go/blob/f05c3aa24d815cd3869153750c9875e35fc48a6e/src/encoding/json/decode.go#L897
|
||||
ic.OutputImports[`"encoding/json"`] = true
|
||||
out += tplStr(decodeTpl["handleFallback"], handleFallback{
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
Kind: typ.Kind(),
|
||||
})
|
||||
} else {
|
||||
out += tplStr(decodeTpl["handleString"], handleString{
|
||||
IC: ic,
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
TakeAddr: takeAddr || ptr,
|
||||
Quoted: quoted,
|
||||
})
|
||||
}
|
||||
case reflect.Interface:
|
||||
ic.OutputImports[`"encoding/json"`] = true
|
||||
out += tplStr(decodeTpl["handleFallback"], handleFallback{
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
Kind: typ.Kind(),
|
||||
})
|
||||
case reflect.Map:
|
||||
out += tplStr(decodeTpl["handleObject"], handleObject{
|
||||
IC: ic,
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
Ptr: reflect.Ptr,
|
||||
TakeAddr: takeAddr || ptr,
|
||||
})
|
||||
default:
|
||||
ic.OutputImports[`"encoding/json"`] = true
|
||||
out += tplStr(decodeTpl["handleFallback"], handleFallback{
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
Kind: typ.Kind(),
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func getArrayHandler(ic *Inception, name string, typ reflect.Type, ptr bool) string {
|
||||
if typ.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 {
|
||||
ic.OutputImports[`"encoding/base64"`] = true
|
||||
useReflectToSet := false
|
||||
if typ.Elem().Name() != "byte" {
|
||||
ic.OutputImports[`"reflect"`] = true
|
||||
useReflectToSet = true
|
||||
}
|
||||
|
||||
return tplStr(decodeTpl["handleByteSlice"], handleArray{
|
||||
IC: ic,
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
Ptr: reflect.Ptr,
|
||||
UseReflectToSet: useReflectToSet,
|
||||
})
|
||||
}
|
||||
|
||||
if typ.Elem().Kind() == reflect.Struct && typ.Elem().Name() != "" {
|
||||
goto sliceOrArray
|
||||
}
|
||||
|
||||
if (typ.Elem().Kind() == reflect.Struct || typ.Elem().Kind() == reflect.Map) ||
|
||||
typ.Elem().Kind() == reflect.Array || typ.Elem().Kind() == reflect.Slice &&
|
||||
typ.Elem().Name() == "" {
|
||||
ic.OutputImports[`"encoding/json"`] = true
|
||||
|
||||
return tplStr(decodeTpl["handleFallback"], handleFallback{
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
Kind: typ.Kind(),
|
||||
})
|
||||
}
|
||||
|
||||
sliceOrArray:
|
||||
|
||||
if typ.Kind() == reflect.Array {
|
||||
return tplStr(decodeTpl["handleArray"], handleArray{
|
||||
IC: ic,
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
IsPtr: ptr,
|
||||
Ptr: reflect.Ptr,
|
||||
})
|
||||
}
|
||||
|
||||
return tplStr(decodeTpl["handleSlice"], handleArray{
|
||||
IC: ic,
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
IsPtr: ptr,
|
||||
Ptr: reflect.Ptr,
|
||||
})
|
||||
}
|
||||
|
||||
func getAllowTokens(name string, tokens ...string) string {
|
||||
return tplStr(decodeTpl["allowTokens"], allowTokens{
|
||||
Name: name,
|
||||
Tokens: tokens,
|
||||
})
|
||||
}
|
||||
|
||||
func getNumberHandler(ic *Inception, name string, takeAddr bool, typ reflect.Type, parsefunc string) string {
|
||||
return tplStr(decodeTpl["handlerNumeric"], handlerNumeric{
|
||||
IC: ic,
|
||||
Name: name,
|
||||
ParseFunc: parsefunc,
|
||||
TakeAddr: takeAddr,
|
||||
Typ: typ,
|
||||
})
|
||||
}
|
||||
|
||||
func getNumberSize(typ reflect.Type) string {
|
||||
return fmt.Sprintf("%d", typ.Bits())
|
||||
}
|
||||
|
||||
func getType(ic *Inception, name string, typ reflect.Type) string {
|
||||
s := typ.Name()
|
||||
|
||||
if typ.PkgPath() != "" && typ.PkgPath() != ic.PackagePath {
|
||||
path := removeVendor(typ.PkgPath())
|
||||
ic.OutputImports[`"`+path+`"`] = true
|
||||
s = typ.String()
|
||||
}
|
||||
|
||||
if s == "" {
|
||||
return typ.String()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// removeVendor removes everything before and including a '/vendor/'
|
||||
// substring in the package path.
|
||||
// This is needed becuase that full path can't be used in the
|
||||
// import statement.
|
||||
func removeVendor(path string) string {
|
||||
i := strings.Index(path, "/vendor/")
|
||||
if i == -1 {
|
||||
return path
|
||||
}
|
||||
return path[i+8:]
|
||||
}
|
||||
|
||||
func buildTokens(containsOptional bool, optional string, required ...string) []string {
|
||||
if containsOptional {
|
||||
return append(required, optional)
|
||||
}
|
||||
|
||||
return required
|
||||
}
|
||||
|
||||
func unquoteField(quoted bool) string {
|
||||
// The outer quote of a string is already stripped out by
|
||||
// the lexer. We need to check if the inner string is also
|
||||
// quoted. If so, we will decode it as json string. If decoding
|
||||
// fails, we will use the original string
|
||||
if quoted {
|
||||
return `
|
||||
unquoted, ok := fflib.UnquoteBytes(outBuf)
|
||||
if ok {
|
||||
outBuf = unquoted
|
||||
}
|
||||
`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getTmpVarFor(name string) string {
|
||||
return "tmp" + strings.Replace(strings.Title(name), ".", "", -1)
|
||||
}
|
||||
|
|
@ -0,0 +1,773 @@
|
|||
/**
|
||||
* Copyright 2014 Paul Querna
|
||||
*
|
||||
* 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 ffjsoninception
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var decodeTpl map[string]*template.Template
|
||||
|
||||
func init() {
|
||||
decodeTpl = make(map[string]*template.Template)
|
||||
|
||||
funcs := map[string]string{
|
||||
"handlerNumeric": handlerNumericTxt,
|
||||
"allowTokens": allowTokensTxt,
|
||||
"handleFallback": handleFallbackTxt,
|
||||
"handleString": handleStringTxt,
|
||||
"handleObject": handleObjectTxt,
|
||||
"handleArray": handleArrayTxt,
|
||||
"handleSlice": handleSliceTxt,
|
||||
"handleByteSlice": handleByteSliceTxt,
|
||||
"handleBool": handleBoolTxt,
|
||||
"handlePtr": handlePtrTxt,
|
||||
"header": headerTxt,
|
||||
"ujFunc": ujFuncTxt,
|
||||
"handleUnmarshaler": handleUnmarshalerTxt,
|
||||
}
|
||||
|
||||
tplFuncs := template.FuncMap{
|
||||
"getAllowTokens": getAllowTokens,
|
||||
"getNumberSize": getNumberSize,
|
||||
"getType": getType,
|
||||
"handleField": handleField,
|
||||
"handleFieldAddr": handleFieldAddr,
|
||||
"unquoteField": unquoteField,
|
||||
"getTmpVarFor": getTmpVarFor,
|
||||
}
|
||||
|
||||
for k, v := range funcs {
|
||||
decodeTpl[k] = template.Must(template.New(k).Funcs(tplFuncs).Parse(v))
|
||||
}
|
||||
}
|
||||
|
||||
type handlerNumeric struct {
|
||||
IC *Inception
|
||||
Name string
|
||||
ParseFunc string
|
||||
Typ reflect.Type
|
||||
TakeAddr bool
|
||||
}
|
||||
|
||||
var handlerNumericTxt = `
|
||||
{
|
||||
{{$ic := .IC}}
|
||||
|
||||
if tok == fflib.FFTok_null {
|
||||
{{if eq .TakeAddr true}}
|
||||
{{.Name}} = nil
|
||||
{{end}}
|
||||
} else {
|
||||
{{if eq .ParseFunc "ParseFloat" }}
|
||||
tval, err := fflib.{{ .ParseFunc}}(fs.Output.Bytes(), {{getNumberSize .Typ}})
|
||||
{{else}}
|
||||
tval, err := fflib.{{ .ParseFunc}}(fs.Output.Bytes(), 10, {{getNumberSize .Typ}})
|
||||
{{end}}
|
||||
|
||||
if err != nil {
|
||||
return fs.WrapErr(err)
|
||||
}
|
||||
{{if eq .TakeAddr true}}
|
||||
ttypval := {{getType $ic .Name .Typ}}(tval)
|
||||
{{.Name}} = &ttypval
|
||||
{{else}}
|
||||
{{.Name}} = {{getType $ic .Name .Typ}}(tval)
|
||||
{{end}}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type allowTokens struct {
|
||||
Name string
|
||||
Tokens []string
|
||||
}
|
||||
|
||||
var allowTokensTxt = `
|
||||
{
|
||||
if {{range $index, $element := .Tokens}}{{if ne $index 0 }}&&{{end}} tok != fflib.{{$element}}{{end}} {
|
||||
return fs.WrapErr(fmt.Errorf("cannot unmarshal %s into Go value for {{.Name}}", tok))
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type handleFallback struct {
|
||||
Name string
|
||||
Typ reflect.Type
|
||||
Kind reflect.Kind
|
||||
}
|
||||
|
||||
var handleFallbackTxt = `
|
||||
{
|
||||
/* Falling back. type={{printf "%v" .Typ}} kind={{printf "%v" .Kind}} */
|
||||
tbuf, err := fs.CaptureField(tok)
|
||||
if err != nil {
|
||||
return fs.WrapErr(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(tbuf, &{{.Name}})
|
||||
if err != nil {
|
||||
return fs.WrapErr(err)
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type handleString struct {
|
||||
IC *Inception
|
||||
Name string
|
||||
Typ reflect.Type
|
||||
TakeAddr bool
|
||||
Quoted bool
|
||||
}
|
||||
|
||||
var handleStringTxt = `
|
||||
{
|
||||
{{$ic := .IC}}
|
||||
|
||||
{{getAllowTokens .Typ.Name "FFTok_string" "FFTok_null"}}
|
||||
if tok == fflib.FFTok_null {
|
||||
{{if eq .TakeAddr true}}
|
||||
{{.Name}} = nil
|
||||
{{end}}
|
||||
} else {
|
||||
{{if eq .TakeAddr true}}
|
||||
var tval {{getType $ic .Name .Typ}}
|
||||
outBuf := fs.Output.Bytes()
|
||||
{{unquoteField .Quoted}}
|
||||
tval = {{getType $ic .Name .Typ}}(string(outBuf))
|
||||
{{.Name}} = &tval
|
||||
{{else}}
|
||||
outBuf := fs.Output.Bytes()
|
||||
{{unquoteField .Quoted}}
|
||||
{{.Name}} = {{getType $ic .Name .Typ}}(string(outBuf))
|
||||
{{end}}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type handleObject struct {
|
||||
IC *Inception
|
||||
Name string
|
||||
Typ reflect.Type
|
||||
Ptr reflect.Kind
|
||||
TakeAddr bool
|
||||
}
|
||||
|
||||
var handleObjectTxt = `
|
||||
{
|
||||
{{$ic := .IC}}
|
||||
{{getAllowTokens .Typ.Name "FFTok_left_bracket" "FFTok_null"}}
|
||||
if tok == fflib.FFTok_null {
|
||||
{{.Name}} = nil
|
||||
} else {
|
||||
|
||||
{{if eq .TakeAddr true}}
|
||||
{{if eq .Typ.Elem.Kind .Ptr }}
|
||||
{{if eq .Typ.Key.Kind .Ptr }}
|
||||
var tval = make(map[*{{getType $ic .Name .Typ.Key.Elem}}]*{{getType $ic .Name .Typ.Elem.Elem}}, 0)
|
||||
{{else}}
|
||||
var tval = make(map[{{getType $ic .Name .Typ.Key}}]*{{getType $ic .Name .Typ.Elem.Elem}}, 0)
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if eq .Typ.Key.Kind .Ptr }}
|
||||
var tval = make(map[*{{getType $ic .Name .Typ.Key.Elem}}]{{getType $ic .Name .Typ.Elem}}, 0)
|
||||
{{else}}
|
||||
var tval = make(map[{{getType $ic .Name .Typ.Key}}]{{getType $ic .Name .Typ.Elem}}, 0)
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if eq .Typ.Elem.Kind .Ptr }}
|
||||
{{if eq .Typ.Key.Kind .Ptr }}
|
||||
{{.Name}} = make(map[*{{getType $ic .Name .Typ.Key.Elem}}]*{{getType $ic .Name .Typ.Elem.Elem}}, 0)
|
||||
{{else}}
|
||||
{{.Name}} = make(map[{{getType $ic .Name .Typ.Key}}]*{{getType $ic .Name .Typ.Elem.Elem}}, 0)
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if eq .Typ.Key.Kind .Ptr }}
|
||||
{{.Name}} = make(map[*{{getType $ic .Name .Typ.Key.Elem}}]{{getType $ic .Name .Typ.Elem}}, 0)
|
||||
{{else}}
|
||||
{{.Name}} = make(map[{{getType $ic .Name .Typ.Key}}]{{getType $ic .Name .Typ.Elem}}, 0)
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
wantVal := true
|
||||
|
||||
for {
|
||||
{{$keyPtr := false}}
|
||||
{{if eq .Typ.Key.Kind .Ptr }}
|
||||
{{$keyPtr := true}}
|
||||
var k *{{getType $ic .Name .Typ.Key.Elem}}
|
||||
{{else}}
|
||||
var k {{getType $ic .Name .Typ.Key}}
|
||||
{{end}}
|
||||
|
||||
{{$valPtr := false}}
|
||||
{{$tmpVar := getTmpVarFor .Name}}
|
||||
{{if eq .Typ.Elem.Kind .Ptr }}
|
||||
{{$valPtr := true}}
|
||||
var {{$tmpVar}} *{{getType $ic .Name .Typ.Elem.Elem}}
|
||||
{{else}}
|
||||
var {{$tmpVar}} {{getType $ic .Name .Typ.Elem}}
|
||||
{{end}}
|
||||
|
||||
tok = fs.Scan()
|
||||
if tok == fflib.FFTok_error {
|
||||
goto tokerror
|
||||
}
|
||||
if tok == fflib.FFTok_right_bracket {
|
||||
break
|
||||
}
|
||||
|
||||
if tok == fflib.FFTok_comma {
|
||||
if wantVal == true {
|
||||
// TODO(pquerna): this isn't an ideal error message, this handles
|
||||
// things like [,,,] as an array value.
|
||||
return fs.WrapErr(fmt.Errorf("wanted value token, but got token: %v", tok))
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
wantVal = true
|
||||
}
|
||||
|
||||
{{handleField .IC "k" .Typ.Key $keyPtr false}}
|
||||
|
||||
// Expect ':' after key
|
||||
tok = fs.Scan()
|
||||
if tok != fflib.FFTok_colon {
|
||||
return fs.WrapErr(fmt.Errorf("wanted colon token, but got token: %v", tok))
|
||||
}
|
||||
|
||||
tok = fs.Scan()
|
||||
{{handleField .IC $tmpVar .Typ.Elem $valPtr false}}
|
||||
|
||||
{{if eq .TakeAddr true}}
|
||||
tval[k] = {{$tmpVar}}
|
||||
{{else}}
|
||||
{{.Name}}[k] = {{$tmpVar}}
|
||||
{{end}}
|
||||
wantVal = false
|
||||
}
|
||||
|
||||
{{if eq .TakeAddr true}}
|
||||
{{.Name}} = &tval
|
||||
{{end}}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type handleArray struct {
|
||||
IC *Inception
|
||||
Name string
|
||||
Typ reflect.Type
|
||||
Ptr reflect.Kind
|
||||
UseReflectToSet bool
|
||||
IsPtr bool
|
||||
}
|
||||
|
||||
var handleArrayTxt = `
|
||||
{
|
||||
{{$ic := .IC}}
|
||||
{{getAllowTokens .Typ.Name "FFTok_left_brace" "FFTok_null"}}
|
||||
{{if eq .Typ.Elem.Kind .Ptr}}
|
||||
{{.Name}} = [{{.Typ.Len}}]*{{getType $ic .Name .Typ.Elem.Elem}}{}
|
||||
{{else}}
|
||||
{{.Name}} = [{{.Typ.Len}}]{{getType $ic .Name .Typ.Elem}}{}
|
||||
{{end}}
|
||||
if tok != fflib.FFTok_null {
|
||||
wantVal := true
|
||||
|
||||
idx := 0
|
||||
for {
|
||||
{{$ptr := false}}
|
||||
{{$tmpVar := getTmpVarFor .Name}}
|
||||
{{if eq .Typ.Elem.Kind .Ptr }}
|
||||
{{$ptr := true}}
|
||||
var {{$tmpVar}} *{{getType $ic .Name .Typ.Elem.Elem}}
|
||||
{{else}}
|
||||
var {{$tmpVar}} {{getType $ic .Name .Typ.Elem}}
|
||||
{{end}}
|
||||
|
||||
tok = fs.Scan()
|
||||
if tok == fflib.FFTok_error {
|
||||
goto tokerror
|
||||
}
|
||||
if tok == fflib.FFTok_right_brace {
|
||||
break
|
||||
}
|
||||
|
||||
if tok == fflib.FFTok_comma {
|
||||
if wantVal == true {
|
||||
// TODO(pquerna): this isn't an ideal error message, this handles
|
||||
// things like [,,,] as an array value.
|
||||
return fs.WrapErr(fmt.Errorf("wanted value token, but got token: %v", tok))
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
wantVal = true
|
||||
}
|
||||
|
||||
{{handleField .IC $tmpVar .Typ.Elem $ptr false}}
|
||||
|
||||
// Standard json.Unmarshal ignores elements out of array bounds,
|
||||
// that what we do as well.
|
||||
if idx < {{.Typ.Len}} {
|
||||
{{.Name}}[idx] = {{$tmpVar}}
|
||||
idx++
|
||||
}
|
||||
|
||||
wantVal = false
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var handleSliceTxt = `
|
||||
{
|
||||
{{$ic := .IC}}
|
||||
{{getAllowTokens .Typ.Name "FFTok_left_brace" "FFTok_null"}}
|
||||
if tok == fflib.FFTok_null {
|
||||
{{.Name}} = nil
|
||||
} else {
|
||||
{{if eq .Typ.Elem.Kind .Ptr }}
|
||||
{{if eq .IsPtr true}}
|
||||
{{.Name}} = &[]*{{getType $ic .Name .Typ.Elem.Elem}}{}
|
||||
{{else}}
|
||||
{{.Name}} = []*{{getType $ic .Name .Typ.Elem.Elem}}{}
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if eq .IsPtr true}}
|
||||
{{.Name}} = &[]{{getType $ic .Name .Typ.Elem}}{}
|
||||
{{else}}
|
||||
{{.Name}} = []{{getType $ic .Name .Typ.Elem}}{}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
wantVal := true
|
||||
|
||||
for {
|
||||
{{$ptr := false}}
|
||||
{{$tmpVar := getTmpVarFor .Name}}
|
||||
{{if eq .Typ.Elem.Kind .Ptr }}
|
||||
{{$ptr := true}}
|
||||
var {{$tmpVar}} *{{getType $ic .Name .Typ.Elem.Elem}}
|
||||
{{else}}
|
||||
var {{$tmpVar}} {{getType $ic .Name .Typ.Elem}}
|
||||
{{end}}
|
||||
|
||||
tok = fs.Scan()
|
||||
if tok == fflib.FFTok_error {
|
||||
goto tokerror
|
||||
}
|
||||
if tok == fflib.FFTok_right_brace {
|
||||
break
|
||||
}
|
||||
|
||||
if tok == fflib.FFTok_comma {
|
||||
if wantVal == true {
|
||||
// TODO(pquerna): this isn't an ideal error message, this handles
|
||||
// things like [,,,] as an array value.
|
||||
return fs.WrapErr(fmt.Errorf("wanted value token, but got token: %v", tok))
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
wantVal = true
|
||||
}
|
||||
|
||||
{{handleField .IC $tmpVar .Typ.Elem $ptr false}}
|
||||
{{if eq .IsPtr true}}
|
||||
*{{.Name}} = append(*{{.Name}}, {{$tmpVar}})
|
||||
{{else}}
|
||||
{{.Name}} = append({{.Name}}, {{$tmpVar}})
|
||||
{{end}}
|
||||
wantVal = false
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var handleByteSliceTxt = `
|
||||
{
|
||||
{{getAllowTokens .Typ.Name "FFTok_string" "FFTok_null"}}
|
||||
if tok == fflib.FFTok_null {
|
||||
{{.Name}} = nil
|
||||
} else {
|
||||
b := make([]byte, base64.StdEncoding.DecodedLen(fs.Output.Len()))
|
||||
n, err := base64.StdEncoding.Decode(b, fs.Output.Bytes())
|
||||
if err != nil {
|
||||
return fs.WrapErr(err)
|
||||
}
|
||||
{{if eq .UseReflectToSet true}}
|
||||
v := reflect.ValueOf(&{{.Name}}).Elem()
|
||||
v.SetBytes(b[0:n])
|
||||
{{else}}
|
||||
{{.Name}} = append([]byte(), b[0:n]...)
|
||||
{{end}}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type handleBool struct {
|
||||
Name string
|
||||
Typ reflect.Type
|
||||
TakeAddr bool
|
||||
}
|
||||
|
||||
var handleBoolTxt = `
|
||||
{
|
||||
if tok == fflib.FFTok_null {
|
||||
{{if eq .TakeAddr true}}
|
||||
{{.Name}} = nil
|
||||
{{end}}
|
||||
} else {
|
||||
tmpb := fs.Output.Bytes()
|
||||
|
||||
{{if eq .TakeAddr true}}
|
||||
var tval bool
|
||||
{{end}}
|
||||
|
||||
if bytes.Compare([]byte{'t', 'r', 'u', 'e'}, tmpb) == 0 {
|
||||
{{if eq .TakeAddr true}}
|
||||
tval = true
|
||||
{{else}}
|
||||
{{.Name}} = true
|
||||
{{end}}
|
||||
} else if bytes.Compare([]byte{'f', 'a', 'l', 's', 'e'}, tmpb) == 0 {
|
||||
{{if eq .TakeAddr true}}
|
||||
tval = false
|
||||
{{else}}
|
||||
{{.Name}} = false
|
||||
{{end}}
|
||||
} else {
|
||||
err = errors.New("unexpected bytes for true/false value")
|
||||
return fs.WrapErr(err)
|
||||
}
|
||||
|
||||
{{if eq .TakeAddr true}}
|
||||
{{.Name}} = &tval
|
||||
{{end}}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type handlePtr struct {
|
||||
IC *Inception
|
||||
Name string
|
||||
Typ reflect.Type
|
||||
Quoted bool
|
||||
}
|
||||
|
||||
var handlePtrTxt = `
|
||||
{
|
||||
{{$ic := .IC}}
|
||||
|
||||
if tok == fflib.FFTok_null {
|
||||
{{.Name}} = nil
|
||||
} else {
|
||||
if {{.Name}} == nil {
|
||||
{{.Name}} = new({{getType $ic .Typ.Elem.Name .Typ.Elem}})
|
||||
}
|
||||
|
||||
{{handleFieldAddr .IC .Name true .Typ.Elem false .Quoted}}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type header struct {
|
||||
IC *Inception
|
||||
SI *StructInfo
|
||||
}
|
||||
|
||||
var headerTxt = `
|
||||
const (
|
||||
ffjt{{.SI.Name}}base = iota
|
||||
ffjt{{.SI.Name}}nosuchkey
|
||||
{{with $si := .SI}}
|
||||
{{range $index, $field := $si.Fields}}
|
||||
{{if ne $field.JsonName "-"}}
|
||||
ffjt{{$si.Name}}{{$field.Name}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
)
|
||||
|
||||
{{with $si := .SI}}
|
||||
{{range $index, $field := $si.Fields}}
|
||||
{{if ne $field.JsonName "-"}}
|
||||
var ffjKey{{$si.Name}}{{$field.Name}} = []byte({{$field.JsonName}})
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
`
|
||||
|
||||
type ujFunc struct {
|
||||
IC *Inception
|
||||
SI *StructInfo
|
||||
ValidValues []string
|
||||
ResetFields bool
|
||||
}
|
||||
|
||||
var ujFuncTxt = `
|
||||
{{$si := .SI}}
|
||||
{{$ic := .IC}}
|
||||
|
||||
// UnmarshalJSON umarshall json - template of ffjson
|
||||
func (j *{{.SI.Name}}) UnmarshalJSON(input []byte) error {
|
||||
fs := fflib.NewFFLexer(input)
|
||||
return j.UnmarshalJSONFFLexer(fs, fflib.FFParse_map_start)
|
||||
}
|
||||
|
||||
// UnmarshalJSONFFLexer fast json unmarshall - template ffjson
|
||||
func (j *{{.SI.Name}}) UnmarshalJSONFFLexer(fs *fflib.FFLexer, state fflib.FFParseState) error {
|
||||
var err error
|
||||
currentKey := ffjt{{.SI.Name}}base
|
||||
_ = currentKey
|
||||
tok := fflib.FFTok_init
|
||||
wantedTok := fflib.FFTok_init
|
||||
|
||||
{{if eq .ResetFields true}}
|
||||
{{range $index, $field := $si.Fields}}
|
||||
var ffjSet{{$si.Name}}{{$field.Name}} = false
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
mainparse:
|
||||
for {
|
||||
tok = fs.Scan()
|
||||
// println(fmt.Sprintf("debug: tok: %v state: %v", tok, state))
|
||||
if tok == fflib.FFTok_error {
|
||||
goto tokerror
|
||||
}
|
||||
|
||||
switch state {
|
||||
|
||||
case fflib.FFParse_map_start:
|
||||
if tok != fflib.FFTok_left_bracket {
|
||||
wantedTok = fflib.FFTok_left_bracket
|
||||
goto wrongtokenerror
|
||||
}
|
||||
state = fflib.FFParse_want_key
|
||||
continue
|
||||
|
||||
case fflib.FFParse_after_value:
|
||||
if tok == fflib.FFTok_comma {
|
||||
state = fflib.FFParse_want_key
|
||||
} else if tok == fflib.FFTok_right_bracket {
|
||||
goto done
|
||||
} else {
|
||||
wantedTok = fflib.FFTok_comma
|
||||
goto wrongtokenerror
|
||||
}
|
||||
|
||||
case fflib.FFParse_want_key:
|
||||
// json {} ended. goto exit. woo.
|
||||
if tok == fflib.FFTok_right_bracket {
|
||||
goto done
|
||||
}
|
||||
if tok != fflib.FFTok_string {
|
||||
wantedTok = fflib.FFTok_string
|
||||
goto wrongtokenerror
|
||||
}
|
||||
|
||||
kn := fs.Output.Bytes()
|
||||
if len(kn) <= 0 {
|
||||
// "" case. hrm.
|
||||
currentKey = ffjt{{.SI.Name}}nosuchkey
|
||||
state = fflib.FFParse_want_colon
|
||||
goto mainparse
|
||||
} else {
|
||||
switch kn[0] {
|
||||
{{range $byte, $fields := $si.FieldsByFirstByte}}
|
||||
case '{{$byte}}':
|
||||
{{range $index, $field := $fields}}
|
||||
{{if ne $index 0 }}} else if {{else}}if {{end}} bytes.Equal(ffjKey{{$si.Name}}{{$field.Name}}, kn) {
|
||||
currentKey = ffjt{{$si.Name}}{{$field.Name}}
|
||||
state = fflib.FFParse_want_colon
|
||||
goto mainparse
|
||||
{{end}} }
|
||||
{{end}}
|
||||
}
|
||||
{{range $index, $field := $si.ReverseFields}}
|
||||
if {{$field.FoldFuncName}}(ffjKey{{$si.Name}}{{$field.Name}}, kn) {
|
||||
currentKey = ffjt{{$si.Name}}{{$field.Name}}
|
||||
state = fflib.FFParse_want_colon
|
||||
goto mainparse
|
||||
}
|
||||
{{end}}
|
||||
currentKey = ffjt{{.SI.Name}}nosuchkey
|
||||
state = fflib.FFParse_want_colon
|
||||
goto mainparse
|
||||
}
|
||||
|
||||
case fflib.FFParse_want_colon:
|
||||
if tok != fflib.FFTok_colon {
|
||||
wantedTok = fflib.FFTok_colon
|
||||
goto wrongtokenerror
|
||||
}
|
||||
state = fflib.FFParse_want_value
|
||||
continue
|
||||
case fflib.FFParse_want_value:
|
||||
|
||||
if {{range $index, $v := .ValidValues}}{{if ne $index 0 }}||{{end}}tok == fflib.{{$v}}{{end}} {
|
||||
switch currentKey {
|
||||
{{range $index, $field := $si.Fields}}
|
||||
case ffjt{{$si.Name}}{{$field.Name}}:
|
||||
goto handle_{{$field.Name}}
|
||||
{{end}}
|
||||
case ffjt{{$si.Name}}nosuchkey:
|
||||
err = fs.SkipField(tok)
|
||||
if err != nil {
|
||||
return fs.WrapErr(err)
|
||||
}
|
||||
state = fflib.FFParse_after_value
|
||||
goto mainparse
|
||||
}
|
||||
} else {
|
||||
goto wantedvalue
|
||||
}
|
||||
}
|
||||
}
|
||||
{{range $index, $field := $si.Fields}}
|
||||
handle_{{$field.Name}}:
|
||||
{{with $fieldName := $field.Name | printf "j.%s"}}
|
||||
{{handleField $ic $fieldName $field.Typ $field.Pointer $field.ForceString}}
|
||||
{{if eq $.ResetFields true}}
|
||||
ffjSet{{$si.Name}}{{$field.Name}} = true
|
||||
{{end}}
|
||||
state = fflib.FFParse_after_value
|
||||
goto mainparse
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
wantedvalue:
|
||||
return fs.WrapErr(fmt.Errorf("wanted value token, but got token: %v", tok))
|
||||
wrongtokenerror:
|
||||
return fs.WrapErr(fmt.Errorf("ffjson: wanted token: %v, but got token: %v output=%s", wantedTok, tok, fs.Output.String()))
|
||||
tokerror:
|
||||
if fs.BigError != nil {
|
||||
return fs.WrapErr(fs.BigError)
|
||||
}
|
||||
err = fs.Error.ToError()
|
||||
if err != nil {
|
||||
return fs.WrapErr(err)
|
||||
}
|
||||
panic("ffjson-generated: unreachable, please report bug.")
|
||||
done:
|
||||
{{if eq .ResetFields true}}
|
||||
{{range $index, $field := $si.Fields}}
|
||||
if !ffjSet{{$si.Name}}{{$field.Name}} {
|
||||
{{with $fieldName := $field.Name | printf "j.%s"}}
|
||||
{{if eq $field.Pointer true}}
|
||||
{{$fieldName}} = nil
|
||||
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Interface), 10) + `}}
|
||||
{{$fieldName}} = nil
|
||||
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Slice), 10) + `}}
|
||||
{{$fieldName}} = nil
|
||||
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Array), 10) + `}}
|
||||
{{$fieldName}} = [{{$field.Typ.Len}}]{{getType $ic $fieldName $field.Typ.Elem}}{}
|
||||
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Map), 10) + `}}
|
||||
{{$fieldName}} = nil
|
||||
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Bool), 10) + `}}
|
||||
{{$fieldName}} = false
|
||||
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.String), 10) + `}}
|
||||
{{$fieldName}} = ""
|
||||
{{else if eq $field.Typ.Kind ` + strconv.FormatUint(uint64(reflect.Struct), 10) + `}}
|
||||
{{$fieldName}} = {{getType $ic $fieldName $field.Typ}}{}
|
||||
{{else}}
|
||||
{{$fieldName}} = {{getType $ic $fieldName $field.Typ}}(0)
|
||||
{{end}}
|
||||
{{end}}
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
return nil
|
||||
}
|
||||
`
|
||||
|
||||
type handleUnmarshaler struct {
|
||||
IC *Inception
|
||||
Name string
|
||||
Typ reflect.Type
|
||||
Ptr reflect.Kind
|
||||
TakeAddr bool
|
||||
UnmarshalJSONFFLexer bool
|
||||
Unmarshaler bool
|
||||
}
|
||||
|
||||
var handleUnmarshalerTxt = `
|
||||
{{$ic := .IC}}
|
||||
|
||||
{{if eq .UnmarshalJSONFFLexer true}}
|
||||
{
|
||||
if tok == fflib.FFTok_null {
|
||||
{{if eq .Typ.Kind .Ptr }}
|
||||
{{.Name}} = nil
|
||||
{{end}}
|
||||
{{if eq .TakeAddr true }}
|
||||
{{.Name}} = nil
|
||||
{{end}}
|
||||
} else {
|
||||
{{if eq .Typ.Kind .Ptr }}
|
||||
if {{.Name}} == nil {
|
||||
{{.Name}} = new({{getType $ic .Typ.Elem.Name .Typ.Elem}})
|
||||
}
|
||||
{{end}}
|
||||
{{if eq .TakeAddr true }}
|
||||
if {{.Name}} == nil {
|
||||
{{.Name}} = new({{getType $ic .Typ.Name .Typ}})
|
||||
}
|
||||
{{end}}
|
||||
err = {{.Name}}.UnmarshalJSONFFLexer(fs, fflib.FFParse_want_key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
state = fflib.FFParse_after_value
|
||||
}
|
||||
{{else}}
|
||||
{{if eq .Unmarshaler true}}
|
||||
{
|
||||
if tok == fflib.FFTok_null {
|
||||
{{if eq .TakeAddr true }}
|
||||
{{.Name}} = nil
|
||||
{{end}}
|
||||
} else {
|
||||
|
||||
tbuf, err := fs.CaptureField(tok)
|
||||
if err != nil {
|
||||
return fs.WrapErr(err)
|
||||
}
|
||||
|
||||
{{if eq .TakeAddr true }}
|
||||
if {{.Name}} == nil {
|
||||
{{.Name}} = new({{getType $ic .Typ.Name .Typ}})
|
||||
}
|
||||
{{end}}
|
||||
err = {{.Name}}.UnmarshalJSON(tbuf)
|
||||
if err != nil {
|
||||
return fs.WrapErr(err)
|
||||
}
|
||||
}
|
||||
state = fflib.FFParse_after_value
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`
|
||||
|
|
@ -0,0 +1,544 @@
|
|||
/**
|
||||
* Copyright 2014 Paul Querna
|
||||
*
|
||||
* 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 ffjsoninception
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/pquerna/ffjson/shared"
|
||||
)
|
||||
|
||||
func typeInInception(ic *Inception, typ reflect.Type, f shared.Feature) bool {
|
||||
for _, v := range ic.objs {
|
||||
if v.Typ == typ {
|
||||
return v.Options.HasFeature(f)
|
||||
}
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
if v.Typ == typ.Elem() {
|
||||
return v.Options.HasFeature(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getOmitEmpty(ic *Inception, sf *StructField) string {
|
||||
ptname := "j." + sf.Name
|
||||
if sf.Pointer {
|
||||
ptname = "*" + ptname
|
||||
return "if true {\n"
|
||||
}
|
||||
switch sf.Typ.Kind() {
|
||||
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return "if len(" + ptname + ") != 0 {" + "\n"
|
||||
|
||||
case reflect.Int,
|
||||
reflect.Int8,
|
||||
reflect.Int16,
|
||||
reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint,
|
||||
reflect.Uint8,
|
||||
reflect.Uint16,
|
||||
reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64:
|
||||
return "if " + ptname + " != 0 {" + "\n"
|
||||
|
||||
case reflect.Bool:
|
||||
return "if " + ptname + " != false {" + "\n"
|
||||
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return "if " + ptname + " != nil {" + "\n"
|
||||
|
||||
default:
|
||||
// TODO(pquerna): fix types
|
||||
return "if true {" + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
func getMapValue(ic *Inception, name string, typ reflect.Type, ptr bool, forceString bool) string {
|
||||
var out = ""
|
||||
|
||||
if typ.Key().Kind() != reflect.String {
|
||||
out += fmt.Sprintf("/* Falling back. type=%v kind=%v */\n", typ, typ.Kind())
|
||||
out += ic.q.Flush()
|
||||
out += "err = buf.Encode(" + name + ")" + "\n"
|
||||
out += "if err != nil {" + "\n"
|
||||
out += " return err" + "\n"
|
||||
out += "}" + "\n"
|
||||
return out
|
||||
}
|
||||
|
||||
var elemKind reflect.Kind
|
||||
elemKind = typ.Elem().Kind()
|
||||
|
||||
switch elemKind {
|
||||
case reflect.String,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64,
|
||||
reflect.Bool:
|
||||
|
||||
ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true
|
||||
|
||||
out += "if " + name + " == nil {" + "\n"
|
||||
ic.q.Write("null")
|
||||
out += ic.q.GetQueued()
|
||||
ic.q.DeleteLast()
|
||||
out += "} else {" + "\n"
|
||||
out += ic.q.WriteFlush("{ ")
|
||||
out += " for key, value := range " + name + " {" + "\n"
|
||||
out += " fflib.WriteJsonString(buf, key)" + "\n"
|
||||
out += " buf.WriteString(`:`)" + "\n"
|
||||
out += getGetInnerValue(ic, "value", typ.Elem(), false, forceString)
|
||||
out += " buf.WriteByte(',')" + "\n"
|
||||
out += " }" + "\n"
|
||||
out += "buf.Rewind(1)" + "\n"
|
||||
out += ic.q.WriteFlush("}")
|
||||
out += "}" + "\n"
|
||||
|
||||
default:
|
||||
out += ic.q.Flush()
|
||||
out += fmt.Sprintf("/* Falling back. type=%v kind=%v */\n", typ, typ.Kind())
|
||||
out += "err = buf.Encode(" + name + ")" + "\n"
|
||||
out += "if err != nil {" + "\n"
|
||||
out += " return err" + "\n"
|
||||
out += "}" + "\n"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func getGetInnerValue(ic *Inception, name string, typ reflect.Type, ptr bool, forceString bool) string {
|
||||
var out = ""
|
||||
|
||||
// Flush if not bool or maps
|
||||
if typ.Kind() != reflect.Bool && typ.Kind() != reflect.Map && typ.Kind() != reflect.Struct {
|
||||
out += ic.q.Flush()
|
||||
}
|
||||
|
||||
if typ.Implements(marshalerFasterType) ||
|
||||
reflect.PtrTo(typ).Implements(marshalerFasterType) ||
|
||||
typeInInception(ic, typ, shared.MustEncoder) ||
|
||||
typ.Implements(marshalerType) ||
|
||||
reflect.PtrTo(typ).Implements(marshalerType) {
|
||||
|
||||
out += ic.q.Flush()
|
||||
out += tplStr(encodeTpl["handleMarshaler"], handleMarshaler{
|
||||
IC: ic,
|
||||
Name: name,
|
||||
Typ: typ,
|
||||
Ptr: reflect.Ptr,
|
||||
MarshalJSONBuf: typ.Implements(marshalerFasterType) || reflect.PtrTo(typ).Implements(marshalerFasterType) || typeInInception(ic, typ, shared.MustEncoder),
|
||||
Marshaler: typ.Implements(marshalerType) || reflect.PtrTo(typ).Implements(marshalerType),
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
ptname := name
|
||||
if ptr {
|
||||
ptname = "*" + name
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Int,
|
||||
reflect.Int8,
|
||||
reflect.Int16,
|
||||
reflect.Int32,
|
||||
reflect.Int64:
|
||||
ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true
|
||||
out += "fflib.FormatBits2(buf, uint64(" + ptname + "), 10, " + ptname + " < 0)" + "\n"
|
||||
case reflect.Uint,
|
||||
reflect.Uint8,
|
||||
reflect.Uint16,
|
||||
reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Uintptr:
|
||||
ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true
|
||||
out += "fflib.FormatBits2(buf, uint64(" + ptname + "), 10, false)" + "\n"
|
||||
case reflect.Float32:
|
||||
ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true
|
||||
out += "fflib.AppendFloat(buf, float64(" + ptname + "), 'g', -1, 32)" + "\n"
|
||||
case reflect.Float64:
|
||||
ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true
|
||||
out += "fflib.AppendFloat(buf, float64(" + ptname + "), 'g', -1, 64)" + "\n"
|
||||
case reflect.Array,
|
||||
reflect.Slice:
|
||||
|
||||
// Arrays cannot be nil
|
||||
if typ.Kind() != reflect.Array {
|
||||
out += "if " + name + "!= nil {" + "\n"
|
||||
}
|
||||
// Array and slice values encode as JSON arrays, except that
|
||||
// []byte encodes as a base64-encoded string, and a nil slice
|
||||
// encodes as the null JSON object.
|
||||
if typ.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Uint8 {
|
||||
ic.OutputImports[`"encoding/base64"`] = true
|
||||
|
||||
out += "buf.WriteString(`\"`)" + "\n"
|
||||
out += `{` + "\n"
|
||||
out += `enc := base64.NewEncoder(base64.StdEncoding, buf)` + "\n"
|
||||
if typ.Elem().Name() != "byte" {
|
||||
ic.OutputImports[`"reflect"`] = true
|
||||
out += `enc.Write(reflect.Indirect(reflect.ValueOf(` + ptname + `)).Bytes())` + "\n"
|
||||
|
||||
} else {
|
||||
out += `enc.Write(` + ptname + `)` + "\n"
|
||||
}
|
||||
out += `enc.Close()` + "\n"
|
||||
out += `}` + "\n"
|
||||
out += "buf.WriteString(`\"`)" + "\n"
|
||||
} else {
|
||||
out += "buf.WriteString(`[`)" + "\n"
|
||||
out += "for i, v := range " + ptname + "{" + "\n"
|
||||
out += "if i != 0 {" + "\n"
|
||||
out += "buf.WriteString(`,`)" + "\n"
|
||||
out += "}" + "\n"
|
||||
out += getGetInnerValue(ic, "v", typ.Elem(), false, false)
|
||||
out += "}" + "\n"
|
||||
out += "buf.WriteString(`]`)" + "\n"
|
||||
}
|
||||
if typ.Kind() != reflect.Array {
|
||||
out += "} else {" + "\n"
|
||||
out += "buf.WriteString(`null`)" + "\n"
|
||||
out += "}" + "\n"
|
||||
}
|
||||
case reflect.String:
|
||||
// Is it a json.Number?
|
||||
if typ.PkgPath() == "encoding/json" && typ.Name() == "Number" {
|
||||
// Fall back to json package to rely on the valid number check.
|
||||
// See: https://github.com/golang/go/blob/92cd6e3af9f423ab4d8ac78f24e7fd81c31a8ce6/src/encoding/json/encode.go#L550
|
||||
out += fmt.Sprintf("/* json.Number */\n")
|
||||
out += "err = buf.Encode(" + name + ")" + "\n"
|
||||
out += "if err != nil {" + "\n"
|
||||
out += " return err" + "\n"
|
||||
out += "}" + "\n"
|
||||
} else {
|
||||
ic.OutputImports[`fflib "github.com/pquerna/ffjson/fflib/v1"`] = true
|
||||
if forceString {
|
||||
// Forcestring on strings does double-escaping of the entire value.
|
||||
// We create a temporary buffer, encode to that an re-encode it.
|
||||
out += "{" + "\n"
|
||||
out += "tmpbuf := fflib.Buffer{}" + "\n"
|
||||
out += "tmpbuf.Grow(len(" + ptname + ") + 16)" + "\n"
|
||||
out += "fflib.WriteJsonString(&tmpbuf, string(" + ptname + "))" + "\n"
|
||||
out += "fflib.WriteJsonString(buf, string( tmpbuf.Bytes() " + `))` + "\n"
|
||||
out += "}" + "\n"
|
||||
} else {
|
||||
out += "fflib.WriteJsonString(buf, string(" + ptname + "))" + "\n"
|
||||
}
|
||||
}
|
||||
case reflect.Ptr:
|
||||
out += "if " + name + "!= nil {" + "\n"
|
||||
switch typ.Elem().Kind() {
|
||||
case reflect.Struct:
|
||||
out += getGetInnerValue(ic, name, typ.Elem(), false, false)
|
||||
default:
|
||||
out += getGetInnerValue(ic, "*"+name, typ.Elem(), false, false)
|
||||
}
|
||||
out += "} else {" + "\n"
|
||||
out += "buf.WriteString(`null`)" + "\n"
|
||||
out += "}" + "\n"
|
||||
case reflect.Bool:
|
||||
out += "if " + ptname + " {" + "\n"
|
||||
ic.q.Write("true")
|
||||
out += ic.q.GetQueued()
|
||||
out += "} else {" + "\n"
|
||||
// Delete 'true'
|
||||
ic.q.DeleteLast()
|
||||
out += ic.q.WriteFlush("false")
|
||||
out += "}" + "\n"
|
||||
case reflect.Interface:
|
||||
out += fmt.Sprintf("/* Interface types must use runtime reflection. type=%v kind=%v */\n", typ, typ.Kind())
|
||||
out += "err = buf.Encode(" + name + ")" + "\n"
|
||||
out += "if err != nil {" + "\n"
|
||||
out += " return err" + "\n"
|
||||
out += "}" + "\n"
|
||||
case reflect.Map:
|
||||
out += getMapValue(ic, ptname, typ, ptr, forceString)
|
||||
case reflect.Struct:
|
||||
if typ.Name() == "" {
|
||||
ic.q.Write("{")
|
||||
ic.q.Write(" ")
|
||||
out += fmt.Sprintf("/* Inline struct. type=%v kind=%v */\n", typ, typ.Kind())
|
||||
newV := reflect.Indirect(reflect.New(typ)).Interface()
|
||||
fields := extractFields(newV)
|
||||
|
||||
// Output all fields
|
||||
for _, field := range fields {
|
||||
// Adjust field name
|
||||
field.Name = name + "." + field.Name
|
||||
out += getField(ic, field, "")
|
||||
}
|
||||
|
||||
if lastConditional(fields) {
|
||||
out += ic.q.Flush()
|
||||
out += `buf.Rewind(1)` + "\n"
|
||||
} else {
|
||||
ic.q.DeleteLast()
|
||||
}
|
||||
out += ic.q.WriteFlush("}")
|
||||
} else {
|
||||
out += fmt.Sprintf("/* Struct fall back. type=%v kind=%v */\n", typ, typ.Kind())
|
||||
out += ic.q.Flush()
|
||||
if ptr {
|
||||
out += "err = buf.Encode(" + name + ")" + "\n"
|
||||
} else {
|
||||
// We send pointer to avoid copying entire struct
|
||||
out += "err = buf.Encode(&" + name + ")" + "\n"
|
||||
}
|
||||
out += "if err != nil {" + "\n"
|
||||
out += " return err" + "\n"
|
||||
out += "}" + "\n"
|
||||
}
|
||||
default:
|
||||
out += fmt.Sprintf("/* Falling back. type=%v kind=%v */\n", typ, typ.Kind())
|
||||
out += "err = buf.Encode(" + name + ")" + "\n"
|
||||
out += "if err != nil {" + "\n"
|
||||
out += " return err" + "\n"
|
||||
out += "}" + "\n"
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func getValue(ic *Inception, sf *StructField, prefix string) string {
|
||||
closequote := false
|
||||
if sf.ForceString {
|
||||
switch sf.Typ.Kind() {
|
||||
case reflect.Int,
|
||||
reflect.Int8,
|
||||
reflect.Int16,
|
||||
reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint,
|
||||
reflect.Uint8,
|
||||
reflect.Uint16,
|
||||
reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64,
|
||||
reflect.Bool:
|
||||
ic.q.Write(`"`)
|
||||
closequote = true
|
||||
}
|
||||
}
|
||||
out := getGetInnerValue(ic, prefix+sf.Name, sf.Typ, sf.Pointer, sf.ForceString)
|
||||
if closequote {
|
||||
if sf.Pointer {
|
||||
out += ic.q.WriteFlush(`"`)
|
||||
} else {
|
||||
ic.q.Write(`"`)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func p2(v uint32) uint32 {
|
||||
v--
|
||||
v |= v >> 1
|
||||
v |= v >> 2
|
||||
v |= v >> 4
|
||||
v |= v >> 8
|
||||
v |= v >> 16
|
||||
v++
|
||||
return v
|
||||
}
|
||||
|
||||
func getTypeSize(t reflect.Type) uint32 {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
// TODO: consider runtime analysis.
|
||||
return 32
|
||||
case reflect.Array, reflect.Map, reflect.Slice:
|
||||
// TODO: consider runtime analysis.
|
||||
return 4 * getTypeSize(t.Elem())
|
||||
case reflect.Int,
|
||||
reflect.Int8,
|
||||
reflect.Int16,
|
||||
reflect.Int32,
|
||||
reflect.Uint,
|
||||
reflect.Uint8,
|
||||
reflect.Uint16,
|
||||
reflect.Uint32:
|
||||
return 8
|
||||
case reflect.Int64,
|
||||
reflect.Uint64,
|
||||
reflect.Uintptr:
|
||||
return 16
|
||||
case reflect.Float32,
|
||||
reflect.Float64:
|
||||
return 16
|
||||
case reflect.Bool:
|
||||
return 4
|
||||
case reflect.Ptr:
|
||||
return getTypeSize(t.Elem())
|
||||
default:
|
||||
return 16
|
||||
}
|
||||
}
|
||||
|
||||
func getTotalSize(si *StructInfo) uint32 {
|
||||
rv := uint32(si.Typ.Size())
|
||||
for _, f := range si.Fields {
|
||||
rv += getTypeSize(f.Typ)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func getBufGrowSize(si *StructInfo) uint32 {
|
||||
|
||||
// TOOD(pquerna): automatically calc a better grow size based on history
|
||||
// of a struct.
|
||||
return p2(getTotalSize(si))
|
||||
}
|
||||
|
||||
func isIntish(t reflect.Type) bool {
|
||||
if t.Kind() >= reflect.Int && t.Kind() <= reflect.Uintptr {
|
||||
return true
|
||||
}
|
||||
if t.Kind() == reflect.Array || t.Kind() == reflect.Slice || t.Kind() == reflect.Ptr {
|
||||
if t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
|
||||
// base64 special case.
|
||||
return false
|
||||
} else {
|
||||
return isIntish(t.Elem())
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getField(ic *Inception, f *StructField, prefix string) string {
|
||||
out := ""
|
||||
if f.OmitEmpty {
|
||||
out += ic.q.Flush()
|
||||
if f.Pointer {
|
||||
out += "if " + prefix + f.Name + " != nil {" + "\n"
|
||||
}
|
||||
out += getOmitEmpty(ic, f)
|
||||
}
|
||||
|
||||
if f.Pointer && !f.OmitEmpty {
|
||||
// Pointer values encode as the value pointed to. A nil pointer encodes as the null JSON object.
|
||||
out += "if " + prefix + f.Name + " != nil {" + "\n"
|
||||
}
|
||||
|
||||
// JsonName is already escaped and quoted.
|
||||
// getInnervalue should flush
|
||||
ic.q.Write(f.JsonName + ":")
|
||||
// We save a copy in case we need it
|
||||
t := ic.q
|
||||
|
||||
out += getValue(ic, f, prefix)
|
||||
ic.q.Write(",")
|
||||
|
||||
if f.Pointer && !f.OmitEmpty {
|
||||
out += "} else {" + "\n"
|
||||
out += t.WriteFlush("null")
|
||||
out += "}" + "\n"
|
||||
}
|
||||
|
||||
if f.OmitEmpty {
|
||||
out += ic.q.Flush()
|
||||
if f.Pointer {
|
||||
out += "}" + "\n"
|
||||
}
|
||||
out += "}" + "\n"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// We check if the last field is conditional.
|
||||
func lastConditional(fields []*StructField) bool {
|
||||
if len(fields) > 0 {
|
||||
f := fields[len(fields)-1]
|
||||
return f.OmitEmpty
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func CreateMarshalJSON(ic *Inception, si *StructInfo) error {
|
||||
conditionalWrites := lastConditional(si.Fields)
|
||||
out := ""
|
||||
|
||||
out += "// MarshalJSON marshal bytes to json - template\n"
|
||||
out += `func (j *` + si.Name + `) MarshalJSON() ([]byte, error) {` + "\n"
|
||||
out += `var buf fflib.Buffer` + "\n"
|
||||
|
||||
out += `if j == nil {` + "\n"
|
||||
out += ` buf.WriteString("null")` + "\n"
|
||||
out += " return buf.Bytes(), nil" + "\n"
|
||||
out += `}` + "\n"
|
||||
|
||||
out += `err := j.MarshalJSONBuf(&buf)` + "\n"
|
||||
out += `if err != nil {` + "\n"
|
||||
out += " return nil, err" + "\n"
|
||||
out += `}` + "\n"
|
||||
out += `return buf.Bytes(), nil` + "\n"
|
||||
out += `}` + "\n"
|
||||
|
||||
out += "// MarshalJSONBuf marshal buff to json - template\n"
|
||||
out += `func (j *` + si.Name + `) MarshalJSONBuf(buf fflib.EncodingBuffer) (error) {` + "\n"
|
||||
out += ` if j == nil {` + "\n"
|
||||
out += ` buf.WriteString("null")` + "\n"
|
||||
out += " return nil" + "\n"
|
||||
out += ` }` + "\n"
|
||||
|
||||
out += `var err error` + "\n"
|
||||
out += `var obj []byte` + "\n"
|
||||
out += `_ = obj` + "\n"
|
||||
out += `_ = err` + "\n"
|
||||
|
||||
ic.q.Write("{")
|
||||
|
||||
// The extra space is inserted here.
|
||||
// If nothing is written to the field this will be deleted
|
||||
// instead of the last comma.
|
||||
if conditionalWrites || len(si.Fields) == 0 {
|
||||
ic.q.Write(" ")
|
||||
}
|
||||
|
||||
for _, f := range si.Fields {
|
||||
out += getField(ic, f, "j.")
|
||||
}
|
||||
|
||||
// Handling the last comma is tricky.
|
||||
// If the last field has omitempty, conditionalWrites is set.
|
||||
// If something has been written, we delete the last comma,
|
||||
// by backing up the buffer, otherwise it will delete a space.
|
||||
if conditionalWrites {
|
||||
out += ic.q.Flush()
|
||||
out += `buf.Rewind(1)` + "\n"
|
||||
} else {
|
||||
ic.q.DeleteLast()
|
||||
}
|
||||
|
||||
out += ic.q.WriteFlush("}")
|
||||
out += `return nil` + "\n"
|
||||
out += `}` + "\n"
|
||||
ic.OutputFuncs = append(ic.OutputFuncs, out)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Copyright 2014 Paul Querna
|
||||
*
|
||||
* 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 ffjsoninception
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var encodeTpl map[string]*template.Template
|
||||
|
||||
func init() {
|
||||
encodeTpl = make(map[string]*template.Template)
|
||||
|
||||
funcs := map[string]string{
|
||||
"handleMarshaler": handleMarshalerTxt,
|
||||
}
|
||||
tplFuncs := template.FuncMap{}
|
||||
|
||||
for k, v := range funcs {
|
||||
encodeTpl[k] = template.Must(template.New(k).Funcs(tplFuncs).Parse(v))
|
||||
}
|
||||
}
|
||||
|
||||
type handleMarshaler struct {
|
||||
IC *Inception
|
||||
Name string
|
||||
Typ reflect.Type
|
||||
Ptr reflect.Kind
|
||||
MarshalJSONBuf bool
|
||||
Marshaler bool
|
||||
}
|
||||
|
||||
var handleMarshalerTxt = `
|
||||
{
|
||||
{{if eq .Typ.Kind .Ptr}}
|
||||
if {{.Name}} == nil {
|
||||
buf.WriteString("null")
|
||||
} else {
|
||||
{{end}}
|
||||
|
||||
{{if eq .MarshalJSONBuf true}}
|
||||
err = {{.Name}}.MarshalJSONBuf(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
{{else if eq .Marshaler true}}
|
||||
obj, err = {{.Name}}.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf.Write(obj)
|
||||
{{end}}
|
||||
{{if eq .Typ.Kind .Ptr}}
|
||||
}
|
||||
{{end}}
|
||||
}
|
||||
`
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* Copyright 2014 Paul Querna
|
||||
*
|
||||
* 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 ffjsoninception
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/pquerna/ffjson/shared"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Inception struct {
|
||||
objs []*StructInfo
|
||||
InputPath string
|
||||
OutputPath string
|
||||
PackageName string
|
||||
PackagePath string
|
||||
OutputImports map[string]bool
|
||||
OutputFuncs []string
|
||||
q ConditionalWrite
|
||||
ResetFields bool
|
||||
}
|
||||
|
||||
func NewInception(inputPath string, packageName string, outputPath string, resetFields bool) *Inception {
|
||||
return &Inception{
|
||||
objs: make([]*StructInfo, 0),
|
||||
InputPath: inputPath,
|
||||
OutputPath: outputPath,
|
||||
PackageName: packageName,
|
||||
OutputFuncs: make([]string, 0),
|
||||
OutputImports: make(map[string]bool),
|
||||
ResetFields: resetFields,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Inception) AddMany(objs []shared.InceptionType) {
|
||||
for _, obj := range objs {
|
||||
i.Add(obj)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Inception) Add(obj shared.InceptionType) {
|
||||
i.objs = append(i.objs, NewStructInfo(obj))
|
||||
i.PackagePath = i.objs[0].Typ.PkgPath()
|
||||
}
|
||||
|
||||
func (i *Inception) wantUnmarshal(si *StructInfo) bool {
|
||||
if si.Options.SkipDecoder {
|
||||
return false
|
||||
}
|
||||
typ := si.Typ
|
||||
umlx := typ.Implements(unmarshalFasterType) || reflect.PtrTo(typ).Implements(unmarshalFasterType)
|
||||
umlstd := typ.Implements(unmarshalerType) || reflect.PtrTo(typ).Implements(unmarshalerType)
|
||||
if umlstd && !umlx {
|
||||
// structure has UnmarshalJSON, but not our faster version -- skip it.
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *Inception) wantMarshal(si *StructInfo) bool {
|
||||
if si.Options.SkipEncoder {
|
||||
return false
|
||||
}
|
||||
typ := si.Typ
|
||||
mlx := typ.Implements(marshalerFasterType) || reflect.PtrTo(typ).Implements(marshalerFasterType)
|
||||
mlstd := typ.Implements(marshalerType) || reflect.PtrTo(typ).Implements(marshalerType)
|
||||
if mlstd && !mlx {
|
||||
// structure has MarshalJSON, but not our faster version -- skip it.
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type sortedStructs []*StructInfo
|
||||
|
||||
func (p sortedStructs) Len() int { return len(p) }
|
||||
func (p sortedStructs) Less(i, j int) bool { return p[i].Name < p[j].Name }
|
||||
func (p sortedStructs) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p sortedStructs) Sort() { sort.Sort(p) }
|
||||
|
||||
func (i *Inception) generateCode() error {
|
||||
// We sort the structs by name, so output if predictable.
|
||||
sorted := sortedStructs(i.objs)
|
||||
sorted.Sort()
|
||||
|
||||
for _, si := range sorted {
|
||||
if i.wantMarshal(si) {
|
||||
err := CreateMarshalJSON(i, si)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if i.wantUnmarshal(si) {
|
||||
err := CreateUnmarshalJSON(i, si)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Inception) handleError(err error) {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s:\n\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (i *Inception) Execute() {
|
||||
if len(os.Args) != 1 {
|
||||
i.handleError(errors.New(fmt.Sprintf("Internal ffjson error: inception executable takes no args: %v", os.Args)))
|
||||
return
|
||||
}
|
||||
|
||||
err := i.generateCode()
|
||||
if err != nil {
|
||||
i.handleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := RenderTemplate(i)
|
||||
if err != nil {
|
||||
i.handleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
stat, err := os.Stat(i.InputPath)
|
||||
|
||||
if err != nil {
|
||||
i.handleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(i.OutputPath, data, stat.Mode())
|
||||
|
||||
if err != nil {
|
||||
i.handleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
/**
|
||||
* Copyright 2014 Paul Querna
|
||||
*
|
||||
* 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 ffjsoninception
|
||||
|
||||
import (
|
||||
fflib "github.com/pquerna/ffjson/fflib/v1"
|
||||
"github.com/pquerna/ffjson/shared"
|
||||
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type StructField struct {
|
||||
Name string
|
||||
JsonName string
|
||||
FoldFuncName string
|
||||
Typ reflect.Type
|
||||
OmitEmpty bool
|
||||
ForceString bool
|
||||
HasMarshalJSON bool
|
||||
HasUnmarshalJSON bool
|
||||
Pointer bool
|
||||
Tagged bool
|
||||
}
|
||||
|
||||
type FieldByJsonName []*StructField
|
||||
|
||||
func (a FieldByJsonName) Len() int { return len(a) }
|
||||
func (a FieldByJsonName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a FieldByJsonName) Less(i, j int) bool { return a[i].JsonName < a[j].JsonName }
|
||||
|
||||
type StructInfo struct {
|
||||
Name string
|
||||
Obj interface{}
|
||||
Typ reflect.Type
|
||||
Fields []*StructField
|
||||
Options shared.StructOptions
|
||||
}
|
||||
|
||||
func NewStructInfo(obj shared.InceptionType) *StructInfo {
|
||||
t := reflect.TypeOf(obj.Obj)
|
||||
return &StructInfo{
|
||||
Obj: obj.Obj,
|
||||
Name: t.Name(),
|
||||
Typ: t,
|
||||
Fields: extractFields(obj.Obj),
|
||||
Options: obj.Options,
|
||||
}
|
||||
}
|
||||
|
||||
func (si *StructInfo) FieldsByFirstByte() map[string][]*StructField {
|
||||
rv := make(map[string][]*StructField)
|
||||
for _, f := range si.Fields {
|
||||
b := string(f.JsonName[1])
|
||||
rv[b] = append(rv[b], f)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (si *StructInfo) ReverseFields() []*StructField {
|
||||
var i int
|
||||
rv := make([]*StructField, 0)
|
||||
for i = len(si.Fields) - 1; i >= 0; i-- {
|
||||
rv = append(rv, si.Fields[i])
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
const (
|
||||
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
|
||||
)
|
||||
|
||||
func foldFunc(key []byte) string {
|
||||
nonLetter := false
|
||||
special := false // special letter
|
||||
for _, b := range key {
|
||||
if b >= utf8.RuneSelf {
|
||||
return "bytes.EqualFold"
|
||||
}
|
||||
upper := b & caseMask
|
||||
if upper < 'A' || upper > 'Z' {
|
||||
nonLetter = true
|
||||
} else if upper == 'K' || upper == 'S' {
|
||||
// See above for why these letters are special.
|
||||
special = true
|
||||
}
|
||||
}
|
||||
if special {
|
||||
return "fflib.EqualFoldRight"
|
||||
}
|
||||
if nonLetter {
|
||||
return "fflib.AsciiEqualFold"
|
||||
}
|
||||
return "fflib.SimpleLetterEqualFold"
|
||||
}
|
||||
|
||||
type MarshalerFaster interface {
|
||||
MarshalJSONBuf(buf fflib.EncodingBuffer) error
|
||||
}
|
||||
|
||||
type UnmarshalFaster interface {
|
||||
UnmarshalJSONFFLexer(l *fflib.FFLexer, state fflib.FFParseState) error
|
||||
}
|
||||
|
||||
var marshalerType = reflect.TypeOf(new(json.Marshaler)).Elem()
|
||||
var marshalerFasterType = reflect.TypeOf(new(MarshalerFaster)).Elem()
|
||||
var unmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem()
|
||||
var unmarshalFasterType = reflect.TypeOf(new(UnmarshalFaster)).Elem()
|
||||
|
||||
// extractFields returns a list of fields that JSON should recognize for the given type.
|
||||
// The algorithm is breadth-first search over the set of structs to include - the top struct
|
||||
// and then any reachable anonymous structs.
|
||||
func extractFields(obj interface{}) []*StructField {
|
||||
t := reflect.TypeOf(obj)
|
||||
// Anonymous fields to explore at the current level and the next.
|
||||
current := []StructField{}
|
||||
next := []StructField{{Typ: t}}
|
||||
|
||||
// Count of queued names for current level and the next.
|
||||
count := map[reflect.Type]int{}
|
||||
nextCount := map[reflect.Type]int{}
|
||||
|
||||
// Types already visited at an earlier level.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
||||
// Fields found.
|
||||
var fields []*StructField
|
||||
|
||||
for len(next) > 0 {
|
||||
current, next = next, current[:0]
|
||||
count, nextCount = nextCount, map[reflect.Type]int{}
|
||||
|
||||
for _, f := range current {
|
||||
if visited[f.Typ] {
|
||||
continue
|
||||
}
|
||||
visited[f.Typ] = true
|
||||
|
||||
// Scan f.typ for fields to include.
|
||||
for i := 0; i < f.Typ.NumField(); i++ {
|
||||
sf := f.Typ.Field(i)
|
||||
if sf.PkgPath != "" { // unexported
|
||||
continue
|
||||
}
|
||||
tag := sf.Tag.Get("json")
|
||||
if tag == "-" {
|
||||
continue
|
||||
}
|
||||
name, opts := parseTag(tag)
|
||||
if !isValidTag(name) {
|
||||
name = ""
|
||||
}
|
||||
|
||||
ft := sf.Type
|
||||
ptr := false
|
||||
if ft.Kind() == reflect.Ptr {
|
||||
ptr = true
|
||||
}
|
||||
|
||||
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
||||
// Follow pointer.
|
||||
ft = ft.Elem()
|
||||
}
|
||||
|
||||
// Record found field and index sequence.
|
||||
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
||||
tagged := name != ""
|
||||
if name == "" {
|
||||
name = sf.Name
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
fflib.WriteJsonString(&buf, name)
|
||||
|
||||
field := &StructField{
|
||||
Name: sf.Name,
|
||||
JsonName: string(buf.Bytes()),
|
||||
FoldFuncName: foldFunc([]byte(name)),
|
||||
Typ: ft,
|
||||
HasMarshalJSON: ft.Implements(marshalerType),
|
||||
HasUnmarshalJSON: ft.Implements(unmarshalerType),
|
||||
OmitEmpty: opts.Contains("omitempty"),
|
||||
ForceString: opts.Contains("string"),
|
||||
Pointer: ptr,
|
||||
Tagged: tagged,
|
||||
}
|
||||
|
||||
fields = append(fields, field)
|
||||
|
||||
if count[f.Typ] > 1 {
|
||||
// If there were multiple instances, add a second,
|
||||
// so that the annihilation code will see a duplicate.
|
||||
// It only cares about the distinction between 1 or 2,
|
||||
// so don't bother generating any more copies.
|
||||
fields = append(fields, fields[len(fields)-1])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Record new anonymous struct to explore in next round.
|
||||
nextCount[ft]++
|
||||
if nextCount[ft] == 1 {
|
||||
next = append(next, StructField{
|
||||
Name: ft.Name(),
|
||||
Typ: ft,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all fields that are hidden by the Go rules for embedded fields,
|
||||
// except that fields with JSON tags are promoted.
|
||||
|
||||
// The fields are sorted in primary order of name, secondary order
|
||||
// of field index length. Loop over names; for each name, delete
|
||||
// hidden fields by choosing the one dominant field that survives.
|
||||
out := fields[:0]
|
||||
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||
// One iteration per name.
|
||||
// Find the sequence of fields with the name of this first field.
|
||||
fi := fields[i]
|
||||
name := fi.JsonName
|
||||
for advance = 1; i+advance < len(fields); advance++ {
|
||||
fj := fields[i+advance]
|
||||
if fj.JsonName != name {
|
||||
break
|
||||
}
|
||||
}
|
||||
if advance == 1 { // Only one field with this name
|
||||
out = append(out, fi)
|
||||
continue
|
||||
}
|
||||
dominant, ok := dominantField(fields[i : i+advance])
|
||||
if ok {
|
||||
out = append(out, dominant)
|
||||
}
|
||||
}
|
||||
|
||||
fields = out
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// dominantField looks through the fields, all of which are known to
|
||||
// have the same name, to find the single field that dominates the
|
||||
// others using Go's embedding rules, modified by the presence of
|
||||
// JSON tags. If there are multiple top-level fields, the boolean
|
||||
// will be false: This condition is an error in Go and we skip all
|
||||
// the fields.
|
||||
func dominantField(fields []*StructField) (*StructField, bool) {
|
||||
tagged := -1 // Index of first tagged field.
|
||||
for i, f := range fields {
|
||||
if f.Tagged {
|
||||
if tagged >= 0 {
|
||||
// Multiple tagged fields at the same level: conflict.
|
||||
// Return no field.
|
||||
return nil, false
|
||||
}
|
||||
tagged = i
|
||||
}
|
||||
}
|
||||
if tagged >= 0 {
|
||||
return fields[tagged], true
|
||||
}
|
||||
// All remaining fields have the same length. If there's more than one,
|
||||
// we have a conflict (two fields named "X" at the same level) and we
|
||||
// return no field.
|
||||
if len(fields) > 1 {
|
||||
return nil, false
|
||||
}
|
||||
return fields[0], true
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* Copyright 2014 Paul Querna
|
||||
*
|
||||
* 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 ffjsoninception
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// from: http://golang.org/src/pkg/encoding/json/tags.go
|
||||
|
||||
// tagOptions is the string following a comma in a struct field's "json"
|
||||
// tag, or the empty string. It does not include the leading comma.
|
||||
type tagOptions string
|
||||
|
||||
// parseTag splits a struct field's json tag into its name and
|
||||
// comma-separated options.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
if idx := strings.Index(tag, ","); idx != -1 {
|
||||
return tag[:idx], tagOptions(tag[idx+1:])
|
||||
}
|
||||
return tag, tagOptions("")
|
||||
}
|
||||
|
||||
// Contains reports whether a comma-separated list of options
|
||||
// contains a particular substr flag. substr must be surrounded by a
|
||||
// string boundary or commas.
|
||||
func (o tagOptions) Contains(optionName string) bool {
|
||||
if len(o) == 0 {
|
||||
return false
|
||||
}
|
||||
s := string(o)
|
||||
for s != "" {
|
||||
var next string
|
||||
i := strings.Index(s, ",")
|
||||
if i >= 0 {
|
||||
s, next = s[:i], s[i+1:]
|
||||
}
|
||||
if s == optionName {
|
||||
return true
|
||||
}
|
||||
s = next
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isValidTag(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, c := range s {
|
||||
switch {
|
||||
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
|
||||
// Backslash and quote chars are reserved, but
|
||||
// otherwise any punctuation chars are allowed
|
||||
// in a tag name.
|
||||
default:
|
||||
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* Copyright 2014 Paul Querna
|
||||
*
|
||||
* 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 ffjsoninception
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/format"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const ffjsonTemplate = `
|
||||
// Code generated by ffjson <https://github.com/pquerna/ffjson>. DO NOT EDIT.
|
||||
// source: {{.InputPath}}
|
||||
|
||||
package {{.PackageName}}
|
||||
|
||||
import (
|
||||
{{range $k, $v := .OutputImports}}{{$k}}
|
||||
{{end}}
|
||||
)
|
||||
|
||||
{{range .OutputFuncs}}
|
||||
{{.}}
|
||||
{{end}}
|
||||
|
||||
`
|
||||
|
||||
func RenderTemplate(ic *Inception) ([]byte, error) {
|
||||
t := template.Must(template.New("ffjson.go").Parse(ffjsonTemplate))
|
||||
buf := new(bytes.Buffer)
|
||||
err := t.Execute(buf, ic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return format.Source(buf.Bytes())
|
||||
}
|
||||
|
||||
func tplStr(t *template.Template, data interface{}) string {
|
||||
buf := bytes.Buffer{}
|
||||
err := t.Execute(&buf, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package ffjsoninception
|
||||
|
||||
import "strings"
|
||||
|
||||
// ConditionalWrite is a stack containing a number of pending writes
|
||||
type ConditionalWrite struct {
|
||||
Queued []string
|
||||
}
|
||||
|
||||
// Write will add a string to be written
|
||||
func (w *ConditionalWrite) Write(s string) {
|
||||
w.Queued = append(w.Queued, s)
|
||||
}
|
||||
|
||||
// DeleteLast will delete the last added write
|
||||
func (w *ConditionalWrite) DeleteLast() {
|
||||
if len(w.Queued) == 0 {
|
||||
return
|
||||
}
|
||||
w.Queued = w.Queued[:len(w.Queued)-1]
|
||||
}
|
||||
|
||||
// Last will return the last added write
|
||||
func (w *ConditionalWrite) Last() string {
|
||||
if len(w.Queued) == 0 {
|
||||
return ""
|
||||
}
|
||||
return w.Queued[len(w.Queued)-1]
|
||||
}
|
||||
|
||||
// Flush will return all queued writes, and return
|
||||
// "" (empty string) in nothing has been queued
|
||||
// "buf.WriteByte('" + byte + "')" + '\n' if one bute has been queued.
|
||||
// "buf.WriteString(`" + string + "`)" + "\n" if more than one byte has been queued.
|
||||
func (w *ConditionalWrite) Flush() string {
|
||||
combined := strings.Join(w.Queued, "")
|
||||
if len(combined) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
w.Queued = nil
|
||||
if len(combined) == 1 {
|
||||
return "buf.WriteByte('" + combined + "')" + "\n"
|
||||
}
|
||||
return "buf.WriteString(`" + combined + "`)" + "\n"
|
||||
}
|
||||
|
||||
func (w *ConditionalWrite) FlushTo(out string) string {
|
||||
out += w.Flush()
|
||||
return out
|
||||
}
|
||||
|
||||
// WriteFlush will add a string and return the Flush result for the queue
|
||||
func (w *ConditionalWrite) WriteFlush(s string) string {
|
||||
w.Write(s)
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// GetQueued will return the current queued content without flushing.
|
||||
func (w *ConditionalWrite) GetQueued() string {
|
||||
t := w.Queued
|
||||
s := w.Flush()
|
||||
w.Queued = t
|
||||
return s
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Copyright 2014 Paul Querna, Klaus Post
|
||||
*
|
||||
* 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 shared
|
||||
|
||||
type StructOptions struct {
|
||||
SkipDecoder bool
|
||||
SkipEncoder bool
|
||||
}
|
||||
|
||||
type InceptionType struct {
|
||||
Obj interface{}
|
||||
Options StructOptions
|
||||
}
|
||||
type Feature int
|
||||
|
||||
const (
|
||||
Nothing Feature = 0
|
||||
MustDecoder = 1 << 1
|
||||
MustEncoder = 1 << 2
|
||||
MustEncDec = MustDecoder | MustEncoder
|
||||
)
|
||||
|
||||
func (i InceptionType) HasFeature(f Feature) bool {
|
||||
return i.HasFeature(f)
|
||||
}
|
||||
|
||||
func (s StructOptions) HasFeature(f Feature) bool {
|
||||
hasNeeded := true
|
||||
if f&MustDecoder != 0 && s.SkipDecoder {
|
||||
hasNeeded = false
|
||||
}
|
||||
if f&MustEncoder != 0 && s.SkipEncoder {
|
||||
hasNeeded = false
|
||||
}
|
||||
return hasNeeded
|
||||
}
|
||||
|
|
@ -66,6 +66,8 @@ github.com/pkg/errors
|
|||
github.com/pmezard/go-difflib/difflib
|
||||
# github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7
|
||||
github.com/pquerna/ffjson/fflib/v1
|
||||
github.com/pquerna/ffjson/inception
|
||||
github.com/pquerna/ffjson/shared
|
||||
github.com/pquerna/ffjson/fflib/v1/internal
|
||||
# github.com/sirupsen/logrus v1.4.2
|
||||
github.com/sirupsen/logrus
|
||||
|
|
|
|||
Loading…
Reference in New Issue