Fs02/grimoire

View on GitHub
params/json.go

Summary

Maintainability
A
1 hr
Test Coverage
A
100%
package params

import (
    "reflect"
    "sync"
    "time"

    "github.com/tidwall/gjson"
)

// JSON is param type for json document.
type JSON struct {
    gjson.Result
    results sync.Map
}

var _ Params = (*JSON)(nil)

// Exists returns true if key exists.
func (json *JSON) Exists(name string) bool {
    return json.fetch(name).Exists()
}

// Get returns value as interface.
// returns nil if value doens't exists.
func (json *JSON) Get(name string) interface{} {
    return json.Result.Get(name).Value()
}

// GetWithType returns value given from given name and type.
// second return value will only be false if the type of parameter is not convertible to requested type.
// If value is not convertible to type, it'll return nil, false
// If value is not exists, it will return nil, true
func (json *JSON) GetWithType(name string, typ reflect.Type) (interface{}, bool) {
    value := json.fetch(name)
    if value.IsArray() && typ.Kind() == reflect.Slice {
        array := value.Array()
        result := reflect.MakeSlice(typ, len(array), len(array))
        elmType := typ.Elem()

        for i, elm := range array {
            elmValue, valid := json.convert(elm, elmType)
            if !valid {
                return nil, false
            }

            result.Index(i).Set(reflect.ValueOf(elmValue))
        }
        return result.Interface(), true
    }

    return json.convert(value, typ)
}

// GetParams returns nested param
func (json *JSON) GetParams(name string) (Params, bool) {
    if value := json.fetch(name); value.IsObject() {
        return &JSON{Result: value}, true
    }

    return nil, false
}

// GetParamsSlice returns slice of nested param
func (json *JSON) GetParamsSlice(name string) ([]Params, bool) {
    if value := json.fetch(name); value.IsArray() {
        pars := value.Array()
        mpar := make([]Params, len(pars))

        for i, par := range pars {
            if !par.IsObject() {
                return nil, false
            }

            mpar[i] = &JSON{Result: par}
        }
        return mpar, true
    }

    return nil, false
}

func (json *JSON) convert(value gjson.Result, typ reflect.Type) (interface{}, bool) {
    if value.Type == gjson.Null {
        return nil, true
    }

    // handle type alias
    if typ.PkgPath() != "" && typ.Kind() != reflect.Struct && typ.Kind() != reflect.Slice && typ.Kind() != reflect.Array {
        rv := reflect.ValueOf(value.Value())
        if !rv.Type().ConvertibleTo(typ) {
            return nil, false
        }

        return rv.Convert(typ).Interface(), true
    }

    switch value.Type {
    case gjson.False, gjson.True:
        if typ.Kind() == reflect.Bool {
            return value.Bool(), true
        }
    case gjson.Number:
        switch typ.Kind() {
        case reflect.Int:
            return int(value.Int()), true
        case reflect.Int8:
            return int8(value.Int()), true
        case reflect.Int16:
            return int16(value.Int()), true
        case reflect.Int32:
            return int32(value.Int()), true
        case reflect.Int64:
            return value.Int(), true
        case reflect.Uint:
            return uint(value.Uint()), true
        case reflect.Uint8:
            return uint8(value.Uint()), true
        case reflect.Uint16:
            return uint16(value.Uint()), true
        case reflect.Uint32:
            return uint32(value.Uint()), true
        case reflect.Uint64:
            return value.Uint(), true
        case reflect.Uintptr:
            return uintptr(value.Uint()), true
        case reflect.Float32:
            return float32(value.Float()), true
        case reflect.Float64:
            return value.Float(), true
        }
    case gjson.String:
        if typ.Kind() == reflect.String {
            return value.String(), true
        } else if typ == timeType {
            if res, err := time.Parse(time.RFC3339, value.String()); err == nil {
                return res, true
            }
        }
    }

    return nil, false
}

func (json *JSON) fetch(name string) gjson.Result {
    if result, ok := json.results.Load(name); ok {
        return result.(gjson.Result)
    }

    result := json.Result.Get(name)
    json.results.Store(name, result)
    return result
}

// ParseJSON as params
func ParseJSON(json string) Params {
    return &JSON{Result: gjson.Parse(json)}
}