maps

package module
v3.0.1+incompatible Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2019 License: MIT Imports: 9 Imported by: 0

README

vespucci Build Status

Utility functions for golang maps

Documentation

Overview

Package maps is a set of utility functions for working with maps. Generally, maps and slices of any kind will work, but performance is optimized for maps returned by json.Unmarshal(b, &interface{}). If all the maps are map[string]interface{}, and all the slices are []interface{}, and all the rest of the values are primitives, then reflection is avoided.

Index

Constants

This section is empty.

Variables

View Source
var EmptyMapValuesMatchAny = EmptyValuesMatchAny

EmptyMapValuesMatchAny is an alias for EmptyValuesMatchAny.

View Source
var ErrStop = errors.New("stop")

ErrStop can be returned by transform functions to end recursion early. The Transform function will not return an error.

View Source
var IndexOutOfBoundsError = merry.New("Index out of bounds")

IndexOutOfBoundsError indicates the index doesn't exist in the slice.

View Source
var PathNotFoundError = merry.New("Path not found")

PathNotFoundError indicates the requested path was not present in the value.

View Source
var PathNotMapError = merry.New("Path not map")

PathNotMapError indicates the value at the path is not a map.

View Source
var PathNotSliceError = merry.New("Path not slice")

PathNotSliceError indicates the value at the path is not a slice.

Functions

func Conflicts

func Conflicts(m1, m2 map[string]interface{}) bool

Conflicts returns true if trees share common key paths, but the values at those paths are not equal. i.e. if the two maps were merged, no values would be overwritten conflicts == !contains(v1, v2) && !excludes(v1, v2) conflicts == !contains(merge(v1, v2), v1)

func Contains

func Contains(v1, v2 interface{}, options ...ContainsOption) bool

Contains tests whether v1 "contains" v2. The notion of containment is based on postgres' JSONB containment operators.

A map v1 "contains" another map v2 if v1 has contains all the keys in v2, and if the values in v2 are contained by the corresponding values in v1.

{"color":"red"} contains {}
{"color":"red"} contains {"color":"red"}
{"color":"red","flavor":"beef"} contains {"color":"red"}
{"labels":{"color":"red","flavor":"beef"}} contains {"labels":{"flavor":"beef"}}
{"tags":["red","green","blue"]} contains {"tags":["red","green"]}

A scalar value v1 contains value v2 if they are equal.

5 contains 5
"red" contains "red"

A slice v1 contains a slice v2 if all the values in v2 are contained by at least one value in v1:

["red","green"] contains ["red"]
["red"] contains ["red","red","red"]
// In this case, the single value in v1 contains each of the values
// in v2, so v1 contains v2
[{"type":"car","color":"red","wheels":4}] contains [{"type":"car"},{"color","red"},{"wheels":4}]

A slice v1 also can contain a *scalar* value v2:

["red"] contains "red"

A struct v1 contains a struct v2 if they are deeply equal (using reflect.DeepEquals)

func Empty

func Empty(v interface{}) bool

Empty returns true if v is nil, empty, or a zero value.

If v is a pointer, it is empty if the pointer is nil or invalid, but not empty if it points to a value, even if that value is zero. For example:

Empty(0)  // true
i := 0
Empty(&i) // false
Empty(Widget{}) // true, zero value
Empty(&Widget{}) // false, non-nil pointer

Maps, slices, arrays, and channels are considered empty if their length is zero.

Strings are empty if they contain nothing but whitespace.

func Get

func Get(v interface{}, path string) (interface{}, error)

Get extracts the value at path from v. Path is in the form:

response.things[2].color.red

You can use `merry` to test the types of return errors:

_, err := maps.Get("","")
if merry.Is(err, maps.PathNotFoundError) {
  ...

Returns PathNotFoundError if the next key in the path is not found.

Returns PathNotMapError if evaluating a key against a value which is not a map (e.g. a slice or a primitive value, against which we can't evaluate a key name).

Returns IndexOutOfBoundsError if evaluating a slice index against a slice value, and the index is out of bounds.

Returns PathNotSliceError if evaluating a slice index against a value which isn't a slice.

func Keys

func Keys(m map[string]interface{}) (keys []string)

Keys returns a slice of the keys in the map

func Merge

func Merge(v1, v2 interface{}) interface{}

Merge returns a new map, which is the deep merge of the normalized values of v1 and v2.

Values in v2 override values in v1.

Slices are merged simply by adding any v2 values which aren't already in v1's slice. This won't do anything fancy with slices that have duplicate values. Order is ignored. E.g.:

[5, 6, 7] + [5, 5, 5, 4] = [5, 6, 7, 4]

The return value is a copy. v1 and v2 are not modified.

func Normalize

func Normalize(v1 interface{}) (interface{}, error)

Normalize recursively converts v1 into a tree of maps, slices, and primitives. The types in the result will be the types the json package uses for unmarshalling into interface{}. The rules are:

1. All maps with string keys will be converted into map[string]interface{} 2. All slices will be converted to []interface{} 3. All primitive numeric types will be converted into float64 4. string, bool, and nil are unmodified 5. All other values will be converted into the above types by doing a json.Marshal and Unmarshal

Values in v1 will be modified in place if possible

func NormalizeWithOptions

func NormalizeWithOptions(v interface{}, opt NormalizeOptions) (interface{}, error)

NormalizeWithOptions does the same as Normalize, but with options.

func Transform

func Transform(v interface{}, transformer func(in interface{}) (interface{}, error)) (interface{}, error)

Transform applies a transformation function to each value in tree. Values are normalized before being passed to the transformer function. Any maps and slices are passed to the transform function as the whole value first, then each child value of the map/slice is passed to the transform function.

The value returned by the transformer will replace the original value.

If the transform function returns a non-primitive value, it will recurse into the new value.

If the transformer function returns the error ErrStop, the process will abort with no error.

Types

type ContainsOption

type ContainsOption func(*containsOptions)

ContainsOption is an option which modifies the behavior of the Contains() function

func AllowTimeDelta

func AllowTimeDelta(d time.Duration) ContainsOption

AllowTimeDelta configures the precision of time comparison. Time values will be considered equal if the difference between the two values is less than d.

Implies ParseTimes

func EmptyValuesMatchAny

func EmptyValuesMatchAny() ContainsOption

EmptyValuesMatchAny is a ContainsOption which allows looser matching of empty values. If set, a value in v1 will match a value in v2 if:

- v1 contains v2 - OR v2 is nil - OR v2 is the zero value of the type of v1's value

This is convenient when testing whether a struct contains another struct. Structs are normalized by marshalling them to JSON. Fields which don't have the `omitempty` option will appear in the normalized v2 value as map keys with zero values. Using this option will allow that to match.

This option can also be used to test for the presence of keys in v1 without needing to test the value:

v1 := map[string]interface{}{"color":"blue"}
v2 := map[string]interface{}{"color":nil}
Contains(v1, v2)  // false
Contains(v1, v2, EmptyMapValuesMatchAny()) // true
v1 := map[string]interface{}{}
Contains(v1, v2, EmptyMapValuesMatchAny()) // false, because v1 doesn't have "color" key

Another use is testing the general type of the value:

v1 := map[string]interface{}{"size":5}
v2 := map[string]interface{}{"size":0}
Contains(v1, v2)  // false
Contains(v1, v2, EmptyMapValuesMatchAny()) // true
v2 := map[string]interface{}{"size":""}
Contains(v1, v2, EmptyMapValuesMatchAny()) // false, because type of value doesn't match (v1: number, v2: string)

func IgnoreTimeZones

func IgnoreTimeZones(b bool) ContainsOption

IgnoreTimeZones will ignore the time zones of time values (otherwise the time zones must match).

Implies ParseTimes

func ParseTimes

func ParseTimes() ContainsOption

ParseTimes enables special processing for date values. Contains typically marshals time.Time values to a string before comparison. This means the EmptyValuesMatchAny() option will not work as expected for time values.

When ParseTimes is specified, after the values are normalized to strings, the code will attempt to parse any string values back into time.Time values. This allows correct processing of the time.Time zero values.

func RoundTimes

func RoundTimes(d time.Duration) ContainsOption

RoundTimes will round time values (see time.Time#Round)

Implies ParseTimes

func StringContains

func StringContains() ContainsOption

StringContains is a ContainsOption which uses strings.Contains(v1, v2) to test for string containment.

Without this option, strings (like other primitive values) must match exactly.

Contains("brown fox", "fox") // false
Contains("brown fox", "fox", StringContains()) // true

func Trace added in v1.0.2

func Trace(s *string) ContainsOption

Trace sets `s` to a string describing the path to the values where containment was false. Helps debugging why one value doesn't contain another. Sample output:

-> v1: map[time:2017-03-03T14:08:30.097698864-05:00]
-> v2: map[time:0001-01-01T00:00:00Z]
-> "time"
--> v1: 2017-03-03T14:08:30.097698864-05:00
--> v2: 0001-01-01T00:00:00Z

If `s` is nil, it does nothing.

func TruncateTimes

func TruncateTimes(d time.Duration) ContainsOption

TruncateTimes will truncate time values (see time.Time#Truncate)

Implies ParseTimes

type NormalizeOptions

type NormalizeOptions struct {
	// Make copies of all maps and slices.  The result will not share
	// any maps or slices with input value.
	Copy bool

	// if values are encountered which are not primitives, maps, or slices, attempt to
	// turn them into primitives, maps, and slices by running through json.Marshal and json.Unmarshal
	Marshal bool

	// Perform the operation recursively.  If false, only v is normalized, but nested values are not
	Deep bool
}

NormalizeOptions are options for the Normalize function.

type Path

type Path []interface{}

Path is a slice of either strings or slice indexes (ints).

func ParsePath

func ParsePath(path string) (Path, error)

ParsePath parses a string path into a Path slice. String paths look like:

user.name.first
user.addresses[3].street

func (Path) String

func (p Path) String() string

String implements the Stringer interface. It returns the string representation of a Path. Path.String() and ParsePath() are inversions of each other.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL