func (s State) GetString(path string) (string, error) {
	v, err := s.Get(path)
	if err != nil {
		return "", err
	}
	if s, ok := v.(string); ok {
		return s, nil
	}
	return "", fmt.Errorf("cannot convert %q to string", v)
}
GetString retrieves a string parameter.
package plan
import (
	"encoding/json"
	"fmt"
	"io"
	"reflect"
	"strconv"
	"strings"
	"github.com/chanwit/plandiff"
)
type State map[string]interface{}
var EmptyState = State(nil)
func NewState() State {
	return make(map[string]interface{})
}
func (s State) ToJSON() string {
	json, _ := json.Marshal(s)
	return string(json)
}
func NewStateFromJSON(r io.Reader) (State, error) {
	p := NewState()
	decoder := json.NewDecoder(r)
	err := decoder.Decode(&p)
	return p, err
}
func (s State) toPlan() Plan {
	p := newPlan()
	for id := range s {
		p.graph.AddNode(id)
	}
	for id, dval := range s {
		data := dval.(map[string]interface{})
		depsint := (data["meta"].(map[string]interface{}))["dependsOn"]
		var deps []string
		if depstrs, ok := depsint.([]string); ok {
			deps = depstrs
		} else {
			for _, str := range depsint.([]interface{}) {
				deps = append(deps, str.(string))
			}
		}
		for _, dep := range deps {
			p.graph.AddEdge(dep, id)
		}
		var rtype resourceType
		var vals map[string]interface{}
		for k, v := range data {
			if k != "meta" {
				rtype = resourceTypeByName(k)
				vals = v.(map[string]interface{})
				break
			}
		}
		if rtype.baseType == reflect.ValueOf(Plan{}).Type() {
			pres := State(vals).toPlan()
			p.resources[id] = &pres
			continue
		}
		resref := reflect.New(rtype.baseType).Elem()
		fnameMap := extractActualNamesFromTags(rtype.baseType)
		for fname, fval := range vals {
			if fval != nil {
				resref.FieldByName(lookupActualName(fnameMap, fname)).Set(reflect.ValueOf(fval))
			}
		}
		var res Resource
		if rtype.isPtr {
			res = resref.Addr().Interface().(Resource)
		} else {
			res = resref.Interface().(Resource)
		}
		p.resources[id] = res
	}
	return *p
}
func extractActualNamesFromTags(restype reflect.Type) map[string]string {
	result := make(map[string]string)
	for i := 0; i < restype.NumField(); i++ {
		f := restype.Field(i)
		structsTag := f.Tag.Get("structs")
		if structsTag != "" {
			result[strings.Split(structsTag, ",")[0]] = f.Name
		}
	}
	return result
}
func lookupActualName(fnameMap map[string]string, fname string) string {
	if actualName, ok := fnameMap[fname]; ok {
		return actualName
	}
	return fname
}
func (s State) IsEmpty() bool {
	return len(s) == 0
}
func (s State) Equal(other State) bool {
	return reflect.DeepEqual(s, other)
}
func (s State) Get(path string) (interface{}, error) {
	parts := strings.Split(path, ".")
	m := s
	for i, part := range parts {
		v, found := m[part]
		if !found {
			return nil, fmt.Errorf("invalid path (key not found): %s", strings.Join(parts[:i+1], "."))
		}
		if i == len(parts)-1 {
			if retMap, ok := v.(map[string]interface{}); ok {
				return State(retMap), nil
			}
			return v, nil
		}
		if newMap, ok := v.(map[string]interface{}); ok {
			m = State(newMap)
			continue
		}
		return nil, fmt.Errorf("invalid path (key isn't a map): %s", strings.Join(parts[:i+1], "."))
	}
	return nil, fmt.Errorf("invalid path (eek!): %s", path)
}
func (s State) GetBool(path string) (bool, error) {
	v, err := s.Get(path)
	if err != nil {
		return false, err
	}
	if b, ok := v.(bool); ok {
		return b, nil
	}
	if s, ok := v.(string); ok {
		if b, err := strconv.ParseBool(s); err == nil {
			return b, nil
		}
	}
	return false, fmt.Errorf("cannot convert %q to bool", v)
}
func (s State) Bool(path string) bool {
	b, _ := s.GetBool(path)
	return b
}
func (s State) GetNumber(path string) (float64, error) {
	v, err := s.Get(path)
	if err != nil {
		return 0, err
	}
	if f, ok := v.(float64); ok {
		return f, nil
	}
	if s, ok := v.(string); ok {
		if f, err := strconv.ParseFloat(s, 64); err == nil {
			return f, nil
		}
	}
	return 0, fmt.Errorf("cannot convert %q to float64", v)
}
func (s State) Number(path string) float64 {
	n, _ := s.GetNumber(path)
	return n
}