diff --git a/simplejson.go b/simplejson.go index 95e73fd..faee8d4 100644 --- a/simplejson.go +++ b/simplejson.go @@ -3,7 +3,9 @@ package simplejson import ( "encoding/json" "errors" + "fmt" "log" + "strconv" ) // returns the current implementation version @@ -27,17 +29,38 @@ func NewJson(body []byte) (*Json, error) { } // New returns a pointer to a new, empty `Json` object +// type map func New() *Json { return &Json{ data: make(map[string]interface{}), } } +// NewArray returns a pointer to a new, empty `Json` object +// type slice +func NewArray(c int) *Json { + return &Json{ + data: make([]interface{}, 0, c), + } +} + +// Wrap returns a pointer to a new, `Json` object +func Wrap(val interface{}) *Json { + return &Json{data: val} +} + // Interface returns the underlying data func (j *Json) Interface() interface{} { return j.data } +func (j *Json) Empty() bool { + if j.data == nil { + return true + } + return false +} + // Encode returns its marshaled data as `[]byte` func (j *Json) Encode() ([]byte, error) { return j.MarshalJSON() @@ -53,14 +76,24 @@ func (j *Json) MarshalJSON() ([]byte, error) { return json.Marshal(&j.data) } +// AddArray adds val to the `Json` slice +// Useful for adding value in a `Json` slice easily. +func (j *Json) AddArray(val interface{}) error { + arr, err := j.Array() + if err == nil { + j.data = append(arr, val) + } + return err +} + // Set modifies `Json` map by `key` and `value` // Useful for changing single key/value in a `Json` object easily. -func (j *Json) Set(key string, val interface{}) { +func (j *Json) Set(key string, val interface{}) error { m, err := j.Map() - if err != nil { - return + if err == nil { + m[key] = val } - m[key] = val + return err } // SetPath modifies `Json`, recursively checking/creating map keys for the supplied path, @@ -154,6 +187,25 @@ func (j *Json) GetIndex(index int) *Json { return &Json{nil} } +// ArrayLen returns a length current slice +func (j *Json) ArrayLen() int { + if a, err := j.Array(); err == nil { + return len(a) + } + return 0 +} + +func (j *Json) Len() int { + switch j.data.(type) { + case []interface{}: + return len(j.data.([]interface{})) + case map[string]interface{}: + return len(j.data.(map[string]interface{})) + default: + return 0 + } +} + // CheckGet returns a pointer to a new `Json` object and // a `bool` identifying success or failure // @@ -232,6 +284,26 @@ func (j *Json) StringArray() ([]string, error) { return retArr, nil } +func (j *Json) IntArray() ([]int, error) { + arr, err := j.Array() + if err != nil { + return nil, err + } + retArr := make([]int, 0, len(arr)) + for _, a := range arr { + if a == nil { + retArr = append(retArr, 0) + continue + } + s, err := Wrap(a).Int() + if err != nil { + return nil, errors.New("type assertion to []int failed") + } + retArr = append(retArr, s) + } + return retArr, nil +} + // MustArray guarantees the return of a `[]interface{}` (with optional default) // // useful when you want to interate over array values in a succinct manner: @@ -330,6 +402,25 @@ func (j *Json) MustStringArray(args ...[]string) []string { return def } +func (j *Json) MustIntArray(args ...[]int) []int { + var def []int + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustIntArray() received too many arguments %d", len(args)) + } + + a, err := j.IntArray() + if err == nil { + return a + } + + return def +} + // MustInt guarantees the return of an `int` (with optional default) // // useful when you explicitly want an `int` in a single value return context: @@ -444,3 +535,51 @@ func (j *Json) MustUint64(args ...uint64) uint64 { return def } + +// ToString type cast to `string` +func (j *Json) ToString() string { + switch j.data.(type) { + case string: + return j.data.(string) + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + return fmt.Sprintf("%d", j.data) + case float32, float64: + return fmt.Sprintf("%f", j.data) + case bool: + return fmt.Sprintf("%t", j.data) + } + return "" +} + +// ToInt type cast to `int` +func (j *Json) ToInt() int { + var v int + switch j.data.(type) { + case bool: + if j.data.(bool) { + v = 1 + } + case string: + v, _ = strconv.Atoi(j.data.(string)) + default: + v, _ = j.Int() + } + return v +} + +// ToBool type cast to `bool` +func (j *Json) ToBool() bool { + switch j.data.(type) { + case bool: + return j.data.(bool) + case string: + if j.data.(string) == "true" { + return true + } + default: + if j.ToInt() != 0 { + return true + } + } + return false +} diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..d726582 --- /dev/null +++ b/tools.go @@ -0,0 +1,125 @@ +package simplejson + +import ( + "bytes" + "fmt" + "strings" +) + +var logbuff bytes.Buffer + +func writeLog(frm string, arg ...interface{}) { + logbuff.WriteString(fmt.Sprintf(frm, arg...)) + logbuff.WriteString("\n") +} + +func (j *Json) Merge(data *Json) error { + dmp, err := data.Map() + if err != nil { + return err + } + + for k, v := range dmp { + switch v.(type) { + case map[string]interface{}: + if _, f := j.CheckGet(k); !f { + j.Set(k, make(map[string]interface{})) + } + j.Get(k).Merge(Wrap(v)) + default: + j.Set(k, v) + } + } + return nil +} + +func (j *Json) compilePath(path string, stack []string) (*Json, error) { + for _, v := range stack { + if v == path { + return nil, fmt.Errorf("Circular reference: %s in %s", path, stack) + } + } + stack = append(stack, path) + if branch := j.GetPath(strings.Split(path, "/")...); !branch.Empty() { + return branch.compile(j, stack), nil + } + return nil, fmt.Errorf("Path not found: %s", path) +} + +func (j *Json) compile(root *Json, stack []string) *Json { + switch j.data.(type) { + case map[string]interface{}: + out := New() + acc := New() + mp, _ := j.Map() + for k, v := range mp { + if k != "$include" { + if str, ok := v.(string); ok && len(str) > 0 && str[0] == 36 { + path := str[1:len(str)] + br, err := root.compilePath(path, stack) + if err != nil { + writeLog(err.Error()) + continue + } + out.Set(k, br.Interface()) + } else { + br := Wrap(v).compile(root, stack) + out.Set(k, br.Interface()) + } + } + } + if fld, f := mp["$include"]; f { + if str, ok := fld.(string); ok { + fld = make([]interface{}, 1) + (fld.([]interface{}))[0] = str + } + if arr, ok := fld.([]interface{}); ok { + for _, v := range arr { + if path, ok := v.(string); ok { + br, err := root.compilePath(path, stack) + if err != nil { + writeLog(err.Error()) + continue + } + acc.Merge(br) + } + } + } + } + if acc.Len() > 0 { + acc.Merge(out) + out = acc + } + return out + case []interface{}: + out := NewArray(j.Len()) + mp, _ := j.Array() + for _, v := range mp { + br := Wrap(v).compile(root, stack) + out.AddArray(br) + } + return out + case string: + if str, _ := j.String(); len(str) > 0 && str[0] == 36 { + path := str[1:len(str)] + br, err := root.compilePath(path, stack) + if err != nil { + writeLog(err.Error()) + return j + } + return br + } + } + return j +} + +func (j *Json) Compile() (*Json, error) { + logbuff.Reset() + stack := make([]string, 0, 10) + var err error + out := j.compile(j, stack) + if logbuff.Len() > 0 { + err = fmt.Errorf(logbuff.String()) + } + return out, err +}