func CompareMapToStruct(dst interface{}, src map[string]interface{}, opts *CompareOpts) (*CompareResults, error) {
	if opts == nil {
		opts = &CompareOpts{
			ConvertibleFunc: DefaultCanConvert,
			TypeNameFunc:    DetailedTypeName,
		}
	} else {
		opts = &CompareOpts{
			ConvertibleFunc: opts.ConvertibleFunc,
			TypeNameFunc:    opts.TypeNameFunc,
		}
		if opts.ConvertibleFunc == nil {
			opts.ConvertibleFunc = DefaultCanConvert
		}
		if opts.TypeNameFunc == nil {
			opts.TypeNameFunc = DetailedTypeName
		}
	}
	v := reflect.ValueOf(dst)
	if !v.IsValid() || v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
		return nil, ErrInvalidDst
	} else if src == nil {
		return nil, ErrNilSrc
	}
	results := &CompareResults{
		MismatchedFields: []FieldMismatch{},
		MissingFields:    []FieldMissing{},
	}
	compare(v.Elem().Type(), src, opts, results)
	return results, nil
}
/*
CompareMapToStruct takes a pointer to a struct (dst) and a map (src). For each field
in dst, it checks if: the field is in src, and if the types are compatible. The name
of the field is the same as the JSON name.

Fields that have a type mismatch are added to MismatchedFields in the returned
CompareResults. Any fields in dst that are not in src are added to MissingFields.

A type mismatch occurs if a value cannot be converted to a different type without
modifying it, parsing it, etc.

Examples of a type mismatch (src -> dst):
string -> int
int    -> string
bool   -> int
float  -> int
null   -> string

Examples of allowed type conversions (src -> dst):
int  -> float
<T>  -> *<T>
null -> *<T>

Embedded structs work as you might expect. The fields of the struct are treated as
if they were hardcoded into dst. In other words, embedding does not change how
src should be structured.
package schema
import (
	"fmt"
	"math"
	"reflect"
	"strings"
)
type CompareResults struct {
	MismatchedFields []FieldMismatch
	MissingFields []FieldMissing
}
func (cr *CompareResults) Errors() error {
	if len(cr.MismatchedFields) == 0 {
		return nil
	}
	m := make(map[string]interface{})
	for _, f := range cr.MismatchedFields {
		cursor := m
		for _, path := range f.Path {
			if _, ok := cursor[path]; !ok {
				cursor[path] = make(map[string]interface{})
			}
			cursor = cursor[path].(map[string]interface{})
		}
		cursor[f.Field] = f.Message()
	}
	return MismatchError(m)
}
type FieldMissing struct {
	Field string
	Path []string
}
func (f FieldMissing) String() string {
	return FieldNameWithPath(f.Field, f.Path)
}
type FieldMismatch struct {
	Field string
	Expected string
	Actual string
	Path []string
}
func (f FieldMismatch) Message() string {
	return fmt.Sprintf(
		`expected %s but it's %s`,
		TypeNameWithArticle(f.Expected),
		TypeNameWithArticle(f.Actual),
	)
}
// Message returns the field mismatch error as a string, and includes the field name
// with its path in the message.
// e.g: "expected Cat.Foo to be an int but it's a string"
func (f FieldMismatch) MessageWithField() string {
	return fmt.Sprintf(
		`expected "%s" to be %s but it's %s`,
		FieldNameWithPath(f.Field, f.Path),
		TypeNameWithArticle(f.Expected),
		TypeNameWithArticle(f.Actual),
	)
}
func (f FieldMismatch) String() string {
	return f.MessageWithField()
}
type CompareOpts struct {
	ConvertibleFunc ConvertibleFunc
	TypeNameFunc TypeNameFunc
}
type ConvertibleFunc func(t reflect.Type, v reflect.Value) bool
type TypeNameFunc func(t reflect.Type) string