133 lines
2.9 KiB
Go
133 lines
2.9 KiB
Go
|
package jflect
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"go/format"
|
||
|
"io"
|
||
|
"log"
|
||
|
"os"
|
||
|
"sort"
|
||
|
)
|
||
|
|
||
|
// TODO: write proper Usage and README
|
||
|
var (
|
||
|
ErrNotValidSyntax = errors.New("Json reflection is not valid Go syntax")
|
||
|
)
|
||
|
|
||
|
//Read accepts a structName, json io.Reader and outputs a golang struct to an io.Writer
|
||
|
func Read(structName string, r io.Reader, w io.Writer) error {
|
||
|
var v interface{}
|
||
|
err := json.NewDecoder(r).Decode(&v)
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
return err
|
||
|
}
|
||
|
buf := new(bytes.Buffer)
|
||
|
// Open struct
|
||
|
b, err := xreflect(v)
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
return err
|
||
|
}
|
||
|
field := NewField(structName, "struct", b...)
|
||
|
fmt.Fprintf(buf, "type %s %s", field.name, field.gtype)
|
||
|
|
||
|
// Pass through gofmt for uniform formatting, and weak syntax check.
|
||
|
b, err = format.Source(buf.Bytes())
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
fmt.Println("Final Go Code")
|
||
|
fmt.Println()
|
||
|
os.Stderr.Write(buf.Bytes())
|
||
|
fmt.Println()
|
||
|
return ErrNotValidSyntax
|
||
|
}
|
||
|
w.Write(b)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func xreflect(v interface{}) ([]byte, error) {
|
||
|
var (
|
||
|
buf = new(bytes.Buffer)
|
||
|
)
|
||
|
fields := []Field{}
|
||
|
switch root := v.(type) {
|
||
|
case map[string]interface{}:
|
||
|
for key, val := range root {
|
||
|
switch j := val.(type) {
|
||
|
case nil:
|
||
|
// FIXME: sometimes json service will return nil even though the type is string.
|
||
|
// go can not convert string to nil and vs versa. Can we assume its a string?
|
||
|
continue
|
||
|
case float64:
|
||
|
fields = append(fields, NewField(key, "int"))
|
||
|
case map[string]interface{}:
|
||
|
// If type is map[string]interface{} then we have nested object, Recurse
|
||
|
o, err := xreflect(j)
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
return nil, err
|
||
|
}
|
||
|
fields = append(fields, NewField(key, "struct", o...))
|
||
|
case []interface{}:
|
||
|
gtype, err := sliceType(j)
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
return nil, err
|
||
|
}
|
||
|
fields = append(fields, NewField(key, gtype))
|
||
|
default:
|
||
|
fields = append(fields, NewField(key, fmt.Sprintf("%T", val)))
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
return nil, fmt.Errorf("%T: unexpected type", root)
|
||
|
}
|
||
|
// Sort and write field buffer last to keep order and formatting.
|
||
|
sort.Sort(FieldSort(fields))
|
||
|
for _, f := range fields {
|
||
|
fmt.Fprintf(buf, "%s %s %s\n", f.name, f.gtype, f.tag)
|
||
|
}
|
||
|
return buf.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
// if all entries in j are the same type, return slice of that type
|
||
|
func sliceType(j []interface{}) (string, error) {
|
||
|
dft := "[]interface{}"
|
||
|
if len(j) == 0 {
|
||
|
return dft, nil
|
||
|
}
|
||
|
var t, t2 string
|
||
|
for _, v := range j {
|
||
|
switch v.(type) {
|
||
|
case string:
|
||
|
t2 = "[]string"
|
||
|
case float64:
|
||
|
t2 = "[]int"
|
||
|
case map[string]interface{}:
|
||
|
t2 = "[]struct"
|
||
|
default:
|
||
|
// something else, just return default
|
||
|
return dft, nil
|
||
|
}
|
||
|
if t != "" && t != t2 {
|
||
|
return dft, nil
|
||
|
}
|
||
|
t = t2
|
||
|
}
|
||
|
|
||
|
if t == "[]struct" {
|
||
|
o, err := xreflect(j[0])
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
return "", err
|
||
|
}
|
||
|
f := NewField("", "struct", o...)
|
||
|
t = "[]" + f.gtype
|
||
|
}
|
||
|
return t, nil
|
||
|
}
|