package altsrc
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strings"
"time"
"github.com/urfave/cli/v2"
)
// NewJSONSourceFromFlagFunc returns a func that takes a cli.Context
// and returns an InputSourceContext suitable for retrieving config
// variables from a file containing JSON data with the file name defined
// by the given flag.
func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) {
return func(context *cli.Context) (InputSourceContext, error) {
return NewJSONSourceFromFile(context.String(flag))
}
// NewJSONSourceFromFile returns an InputSourceContext suitable for
// retrieving config variables from a file (or url) containing JSON
// data.
func NewJSONSourceFromFile(f string) (InputSourceContext, error) {
data, err := loadDataFrom(f)
if err != nil {
return nil, err
return NewJSONSource(data)
// NewJSONSourceFromReader returns an InputSourceContext suitable for
// retrieving config variables from an io.Reader that returns JSON data.
func NewJSONSourceFromReader(r io.Reader) (InputSourceContext, error) {
data, err := ioutil.ReadAll(r)
// NewJSONSource returns an InputSourceContext suitable for retrieving
// config variables from raw JSON data.
func NewJSONSource(data []byte) (InputSourceContext, error) {
var deserialized map[string]interface{}
if err := json.Unmarshal(data, &deserialized); err != nil {
return &jsonSource{deserialized: deserialized}, nil
func (x *jsonSource) Source() string {
return x.file
func (x *jsonSource) Int(name string) (int, error) {
i, err := x.getValue(name)
return 0, err
switch v := i.(type) {
default:
return 0, fmt.Errorf("unexpected type %T for %q", i, name)
case int:
return v, nil
case float32:
return int(v), nil
case float64:
func (x *jsonSource) Duration(name string) (time.Duration, error) {
v, ok := i.(time.Duration)
if !ok {
func (x *jsonSource) Float64(name string) (float64, error) {
v, ok := i.(float64)
func (x *jsonSource) String(name string) (string, error) {
return "", err
v, ok := i.(string)
return "", fmt.Errorf("unexpected type %T for %q", i, name)
func (x *jsonSource) StringSlice(name string) ([]string, error) {
return nil, fmt.Errorf("unexpected type %T for %q", i, name)
case []string:
case []interface{}:
c := []string{}
for _, s := range v {
if str, ok := s.(string); ok {
c = append(c, str)
} else {
return c, fmt.Errorf("unexpected item type %T in %T for %q", s, c, name)
return c, nil
func (x *jsonSource) IntSlice(name string) ([]int, error) {
case []int:
c := []int{}
if i2, ok := s.(int); ok {
c = append(c, i2)
func (x *jsonSource) Generic(name string) (cli.Generic, error) {
v, ok := i.(cli.Generic)
func (x *jsonSource) Bool(name string) (bool, error) {
return false, err
v, ok := i.(bool)
return false, fmt.Errorf("unexpected type %T for %q", i, name)
func (x *jsonSource) getValue(key string) (interface{}, error) {
return jsonGetValue(key, x.deserialized)
func jsonGetValue(key string, m map[string]interface{}) (interface{}, error) {
var ret interface{}
var ok bool
working := m
keys := strings.Split(key, ".")
for ix, k := range keys {
if ret, ok = working[k]; !ok {
return ret, fmt.Errorf("missing key %q", key)
if working, ok = ret.(map[string]interface{}); !ok {
if ix < len(keys)-1 {
return ret, fmt.Errorf("unexpected intermediate value at %q segment of %q: %T", k, key, ret)
return ret, nil
type jsonSource struct {
file string
deserialized map[string]interface{}