/** * 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) }