package runtime
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/oapi-codegen/runtime/types"
)
func marshalDeepObject (in interface {}, path []string ) ([]string , error ) {
var result []string
switch t := in .(type ) {
case []interface {}:
for i , iface := range t {
newPath := append (path , strconv .Itoa (i ))
fields , err := marshalDeepObject (iface , newPath )
if err != nil {
return nil , fmt .Errorf ("error traversing array: %w" , err )
}
result = append (result , fields ...)
}
case map [string ]interface {}:
keys := make ([]string , len (t ))
i := 0
for k := range t {
keys [i ] = k
i ++
}
sort .Strings (keys )
for _ , k := range keys {
newPath := append (path , k )
fields , err := marshalDeepObject (t [k ], newPath )
if err != nil {
return nil , fmt .Errorf ("error traversing map: %w" , err )
}
result = append (result , fields ...)
}
default :
prefix := "[" + strings .Join (path , "][" ) + "]"
result = []string {
prefix + fmt .Sprintf ("=%v" , t ),
}
}
return result , nil
}
func MarshalDeepObject (i interface {}, paramName string ) (string , error ) {
buf , err := json .Marshal (i )
if err != nil {
return "" , fmt .Errorf ("failed to marshal input to JSON: %w" , err )
}
var i2 interface {}
err = json .Unmarshal (buf , &i2 )
if err != nil {
return "" , fmt .Errorf ("failed to unmarshal JSON: %w" , err )
}
fields , err := marshalDeepObject (i2 , nil )
if err != nil {
return "" , fmt .Errorf ("error traversing JSON structure: %w" , err )
}
for i := range fields {
fields [i ] = paramName + fields [i ]
}
return strings .Join (fields , "&" ), nil
}
type fieldOrValue struct {
fields map [string ]fieldOrValue
value string
}
func (f *fieldOrValue ) appendPathValue (path []string , value string ) {
fieldName := path [0 ]
if len (path ) == 1 {
f .fields [fieldName ] = fieldOrValue {value : value }
return
}
pv , found := f .fields [fieldName ]
if !found {
pv = fieldOrValue {
fields : make (map [string ]fieldOrValue ),
}
f .fields [fieldName ] = pv
}
pv .appendPathValue (path [1 :], value )
}
func makeFieldOrValue (paths [][]string , values []string ) fieldOrValue {
f := fieldOrValue {
fields : make (map [string ]fieldOrValue ),
}
for i := range paths {
path := paths [i ]
value := values [i ]
f .appendPathValue (path , value )
}
return f
}
func UnmarshalDeepObject (dst interface {}, paramName string , params url .Values ) error {
var fieldNames []string
var fieldValues []string
searchStr := paramName + "["
for pName , pValues := range params {
if strings .HasPrefix (pName , searchStr ) {
pName = pName [len (paramName ):]
fieldNames = append (fieldNames , pName )
if len (pValues ) != 1 {
return fmt .Errorf ("%s has multiple values" , pName )
}
fieldValues = append (fieldValues , pValues [0 ])
}
}
paths := make ([][]string , len (fieldNames ))
for i , path := range fieldNames {
path = strings .TrimLeft (path , "[" )
path = strings .TrimRight (path , "]" )
paths [i ] = strings .Split (path , "][" )
}
fieldPaths := makeFieldOrValue (paths , fieldValues )
err := assignPathValues (dst , fieldPaths )
if err != nil {
return fmt .Errorf ("error assigning value to destination: %w" , err )
}
return nil
}
func getFieldName (f reflect .StructField ) string {
n := f .Name
tag , found := f .Tag .Lookup ("json" )
if found {
parts := strings .Split (tag , "," )
if parts [0 ] != "" {
n = parts [0 ]
}
}
return n
}
func fieldIndicesByJSONTag (i interface {}) (map [string ]int , error ) {
t := reflect .TypeOf (i )
if t .Kind () != reflect .Struct {
return nil , errors .New ("expected a struct as input" )
}
n := t .NumField ()
fieldMap := make (map [string ]int )
for i := 0 ; i < n ; i ++ {
field := t .Field (i )
fieldName := getFieldName (field )
fieldMap [fieldName ] = i
}
return fieldMap , nil
}
func assignPathValues (dst interface {}, pathValues fieldOrValue ) error {
v := reflect .ValueOf (dst )
iv := reflect .Indirect (v )
it := iv .Type ()
switch it .Kind () {
case reflect .Map :
dstMap := reflect .MakeMap (iv .Type ())
for key , value := range pathValues .fields {
dstKey := reflect .ValueOf (key )
dstVal := reflect .New (iv .Type ().Elem ())
err := assignPathValues (dstVal .Interface (), value )
if err != nil {
return fmt .Errorf ("error binding map: %w" , err )
}
dstMap .SetMapIndex (dstKey , dstVal .Elem ())
}
iv .Set (dstMap )
return nil
case reflect .Slice :
sliceLength := len (pathValues .fields )
dstSlice := reflect .MakeSlice (it , sliceLength , sliceLength )
err := assignSlice (dstSlice , pathValues )
if err != nil {
return fmt .Errorf ("error assigning slice: %w" , err )
}
iv .Set (dstSlice )
return nil
case reflect .Struct :
if dst , isBinder := v .Interface ().(Binder ); isBinder {
return dst .Bind (pathValues .value )
}
if it .ConvertibleTo (reflect .TypeOf (types .Date {})) {
var date types .Date
var err error
date .Time , err = time .Parse (types .DateFormat , pathValues .value )
if err != nil {
return fmt .Errorf ("invalid date format: %w" , err )
}
dst := iv
if it != reflect .TypeOf (types .Date {}) {
ivPtr := iv .Addr ()
aPtr := ivPtr .Convert (reflect .TypeOf (&types .Date {}))
dst = reflect .Indirect (aPtr )
}
dst .Set (reflect .ValueOf (date ))
}
if it .ConvertibleTo (reflect .TypeOf (time .Time {})) {
var tm time .Time
var err error
tm , err = time .Parse (time .RFC3339Nano , pathValues .value )
if err != nil {
tm , err = time .Parse (types .DateFormat , pathValues .value )
if err != nil {
return fmt .Errorf ("error parsing '%s' as RFC3339 or 2006-01-02 time: %s" , pathValues .value , err )
}
return fmt .Errorf ("invalid date format: %w" , err )
}
dst := iv
if it != reflect .TypeOf (time .Time {}) {
ivPtr := iv .Addr ()
aPtr := ivPtr .Convert (reflect .TypeOf (&time .Time {}))
dst = reflect .Indirect (aPtr )
}
dst .Set (reflect .ValueOf (tm ))
}
fieldMap , err := fieldIndicesByJSONTag (iv .Interface ())
if err != nil {
return fmt .Errorf ("failed enumerating fields: %w" , err )
}
for _ , fieldName := range sortedFieldOrValueKeys (pathValues .fields ) {
fieldValue := pathValues .fields [fieldName ]
fieldIndex , found := fieldMap [fieldName ]
if !found {
return fmt .Errorf ("field [%s] is not present in destination object" , fieldName )
}
field := iv .Field (fieldIndex )
err = assignPathValues (field .Addr ().Interface (), fieldValue )
if err != nil {
return fmt .Errorf ("error assigning field [%s]: %w" , fieldName , err )
}
}
return nil
case reflect .Ptr :
dstVal := reflect .New (it .Elem ())
dstPtr := dstVal .Interface ()
err := assignPathValues (dstPtr , pathValues )
iv .Set (dstVal )
return err
case reflect .Bool :
val , err := strconv .ParseBool (pathValues .value )
if err != nil {
return fmt .Errorf ("expected a valid bool, got %s" , pathValues .value )
}
iv .SetBool (val )
return nil
case reflect .Float32 :
val , err := strconv .ParseFloat (pathValues .value , 32 )
if err != nil {
return fmt .Errorf ("expected a valid float, got %s" , pathValues .value )
}
iv .SetFloat (val )
return nil
case reflect .Float64 :
val , err := strconv .ParseFloat (pathValues .value , 64 )
if err != nil {
return fmt .Errorf ("expected a valid float, got %s" , pathValues .value )
}
iv .SetFloat (val )
return nil
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
val , err := strconv .ParseInt (pathValues .value , 10 , 64 )
if err != nil {
return fmt .Errorf ("expected a valid int, got %s" , pathValues .value )
}
iv .SetInt (val )
return nil
case reflect .String :
iv .SetString (pathValues .value )
return nil
default :
return errors .New ("unhandled type: " + it .String ())
}
}
func assignSlice (dst reflect .Value , pathValues fieldOrValue ) error {
nValues := len (pathValues .fields )
values := make ([]string , nValues )
for i := 0 ; i < nValues ; i ++ {
indexStr := strconv .Itoa (i )
fv , found := pathValues .fields [indexStr ]
if !found {
return errors .New ("array deepObjects must have consecutive indices" )
}
values [i ] = fv .value
}
for i := 0 ; i < nValues ; i ++ {
dstElem := dst .Index (i ).Addr ()
err := assignPathValues (dstElem .Interface (), fieldOrValue {value : values [i ]})
if err != nil {
return fmt .Errorf ("error binding array: %w" , err )
}
}
return nil
}
func sortedFieldOrValueKeys (m map [string ]fieldOrValue ) []string {
keys := make ([]string , 0 , len (m ))
for k := range m {
keys = append (keys , k )
}
sort .Strings (keys )
return keys
}
The pages are generated with Golds v0.7.6 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .