Documentation
¶
Overview ¶
Package alt contains functions and types for altering values.
Conversions ¶
Simple conversion from one to to another include converting to string, bool, int64, float64, and time.Time. Each of these functions takes between one and three arguments. The first is the value to convert. The second argument is the value to return if the value can not be converted. For example, if the value is an array then the second argument, the first default would be returned. If the third argument is present then any input that is not the correct type will cause the third default to be returned. The conversion functions are Int(), FLoat(), Bool(), String(), and Time(). The reason for the defaults are to allow a single return from a conversion unlike a type assertion.
i := alt.Int("123", 0)
Generify ¶
It is often useful to work with generic values that can be converted to JSON and also provide type safety so that code can be checked at compile time. Those value types are defined in the gen package. The Genericer interface defines the Generic() function as
Generic() gen.Node
A Generify() function is used to convert values to gen.Node types.
type Genny struct { val int } func (g *Genny) Generic() gen.Node { return gen.Object{"type": gen.String("genny"), "val": gen.Int(g.val)} } ga := []*Genny{&Genny{val: 3}} v := alt.Generify(ga) // v: [{"type":"Genny","val":3}]
Decompose ¶
The Decompose() functions creates a simple type converting non simple to simple types using either the Simplify() interface or reflection. Unlike Alter() a deep copy is returned leaving the original data unchanged.
type Sample struct { Int int Str string } sample := Sample{Int: 3, Str: "three"} simple := alt.Decompose(&sample, &alt.Options{CreateKey: "^", FullTypePath: true}) // simple: {"^":"github.com/ohler55/ojg/alt_test/Sample","int":3,"str":"three"}
Recompose ¶
Recompose simple data into more complex go types using either the Recompose() function or the Recomposer struct that adds some efficiency by reusing buffers. The package takes a best effort approach to recomposing matching against not only json tags but also against member names and member names starting with a lower case character.
type Sample struct { Int int Str string } r, err := alt.NewRecomposer("^", map[any]alt.RecomposeFunc{&Sample{}: nil}) var v any if err == nil { v, err = r.Recompose(map[string]any{"^": "Sample", "int": 3, "str": "three"}) } // sample: {Int: 3, Str: "three"}
Alter ¶
The GenAlter() function converts a simple go data element into Node compliant data. A best effort is made to convert values that are not simple into generic Nodes. It modifies the values inplace if possible by altering the original.
m := map[string]any{"a": 1, "b": 4, "c": 9} v := alt.GenAlter(m) // v: gen.Object{"a": gen.Int(1), "b": gen.Int(4), "c": gen.Int(9)}, v)
Index ¶
- Variables
- func Alter(v any, options ...*ojg.Options) any
- func Bool(v any, defaults ...bool) (b bool)
- func Decompose(v any, options ...*ojg.Options) any
- func Dup(v any, options ...*ojg.Options) any
- func Float(v any, defaults ...float64) (f float64)
- func GenAlter(v any, options ...*Options) (n gen.Node)
- func Generify(v any, options ...*Options) (n gen.Node)
- func Int(v any, defaults ...int64) (i int64)
- func Match(fingerprint, target any) bool
- func MustRecompose(v any, tv ...any) (out any)
- func Recompose(v any, tv ...any) (out any, err error)
- func String(v any, defaults ...string) (s string)
- func Time(v any, defaults ...time.Time) (t time.Time)
- type AttrSetter
- type Builder
- type Converter
- type Filter
- type Genericer
- type Options
- type Path
- type RecomposeAnyFunc
- type RecomposeFunc
- type Recomposer
- func (r *Recomposer) MustRecompose(v any, tv ...any) (out any)
- func (r *Recomposer) Recompose(v any, tv ...any) (out any, err error)
- func (r *Recomposer) RegisterAnyComposer(val any, fun RecomposeAnyFunc) error
- func (r *Recomposer) RegisterComposer(val any, fun RecomposeFunc) error
- func (r *Recomposer) RegisterUnmarshalerComposer(fun RecomposeAnyFunc)
- type Simplifier
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // DefaultOptions are the default options for the this package. DefaultOptions = ojg.DefaultOptions // BrightOptions are the bright color options. BrightOptions = ojg.BrightOptions // GoOptions are the options that match the go json.Marshal behavior. GoOptions = ojg.GoOptions // HTMLOptions are the options that can be used to encode as HTML JSON. HTMLOptions = ojg.HTMLOptions // TimeRFC3339Converter converts RFC3339 string into time.Time when // parsing. TimeRFC3339Converter = ojg.TimeRFC3339Converter // TimeNanoConverter converts integer values to time.Time assuming the // integer are nonoseconds, TimeNanoConverter = ojg.TimeNanoConverter // MongoConverter converts mongodb decorations into the correct times. MongoConverter = ojg.MongoConverter )
var DefaultRecomposer = Recomposer{ // contains filtered or unexported fields }
DefaultRecomposer provides a shared Recomposer. Note that this should not be shared across go routines unless all types that will be used are registered first. That can be done explicitly or with a warm up run.
var TimeTolerance = time.Millisecond
TimeTolerance is the tolerance when comparing time elements
Functions ¶
func Alter ¶
Alter the data into all simple types converting non simple to simple types using either the Simplify() interface or reflection. Unlike Decompose() map and slice members are modified if necessary to assure all elements are simple types.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" "github.com/ohler55/ojg/oj" "github.com/ohler55/ojg/sen" ) func main() { src := map[string]any{"a": 1, "b": 4, "c": 9} // Alter the src as needed avoiding duplicating when possible. val := alt.Alter(src) // Modify src should change val since they are the same map. src["d"] = 16 fmt.Println(sen.String(val, &oj.Options{Sort: true})) }
Output: {a:1 b:4 c:9 d:16}
func Bool ¶
Bool convert the value provided to a bool. If conversion is not possible such as if the provided value is an array then the first option default value is returned or if not provided false is returned. If the type is not a bool nor a gen.Bool and there is a second optional default then that second default value is returned. This approach keeps the return as a single value and gives the caller the choice of how to indicate a bad value.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" ) func main() { for _, src := range []any{true, "tRuE", "x", 1, nil} { fmt.Printf("alt.Bool(%T(%v)) = %t alt.Bool(%T(%v), false) = %t alt.Bool(%T(%v), false, true) = %t\n", src, src, alt.Bool(src), src, src, alt.Bool(src, false), src, src, alt.Bool(src, false, true)) } }
Output: alt.Bool(bool(true)) = true alt.Bool(bool(true), false) = true alt.Bool(bool(true), false, true) = true alt.Bool(string(tRuE)) = true alt.Bool(string(tRuE), false) = true alt.Bool(string(tRuE), false, true) = true alt.Bool(string(x)) = false alt.Bool(string(x), false) = false alt.Bool(string(x), false, true) = true alt.Bool(int(1)) = false alt.Bool(int(1), false) = false alt.Bool(int(1), false, true) = false alt.Bool(<nil>(<nil>)) = false alt.Bool(<nil>(<nil>), false) = false alt.Bool(<nil>(<nil>), false, true) = true
func Decompose ¶
Decompose creates a simple type converting non simple to simple types using either the Simplify() interface or reflection. Unlike Alter() a deep copy is returned leaving the original data unchanged.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg" "github.com/ohler55/ojg/alt" "github.com/ohler55/ojg/oj" ) func main() { type Sample struct { Int int Str string } sample := Sample{Int: 3, Str: "three"} // Decompose and add a CreateKey to indicate the type with a full path. simple := alt.Decompose(&sample, &ojg.Options{CreateKey: "^", FullTypePath: true}) fmt.Println(oj.JSON(simple, &oj.Options{Sort: true})) }
Output: {"^":"github.com/ohler55/ojg/alt_test/Sample","int":3,"str":"three"}
func Dup ¶ added in v1.8.0
Dup is an alias for Decompose.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg" "github.com/ohler55/ojg/alt" "github.com/ohler55/ojg/oj" ) func main() { type Sample struct { Int int Str string } sample := []any{&Sample{Int: 3, Str: "three"}, 42} // Dup creates a deep duplicate of a simple type and decomposes any // structs according to the optional options just like alt.Decompose does. simple := alt.Decompose(sample, &ojg.Options{CreateKey: "^"}) fmt.Println(oj.JSON(simple, &ojg.Options{Sort: true})) }
Output: [{"^":"Sample","int":3,"str":"three"},42]
func Float ¶
Float convert the value provided to a float64. If conversion is not possible such as if the provided value is an array then the first option default value is returned or if not provided 0.0 is returned. If the type is not one of the float types and there is a second optional default then that second default value is returned. This approach keeps the return as a single value and gives the caller the choice of how to indicate a bad value.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" ) func main() { for _, src := range []any{1, "1,5", "x", 1.5, true} { fmt.Printf("alt.Float(%T(%v)) = %.1f alt.Float(%T(%v), 2.5) = %.1f alt.Float(%T(%v), 2.5, 3.5) = %.1f\n", src, src, alt.Float(src), src, src, alt.Float(src, 2.5), src, src, alt.Float(src, 2.5, 3.5)) } }
Output: alt.Float(int(1)) = 1.0 alt.Float(int(1), 2.5) = 1.0 alt.Float(int(1), 2.5, 3.5) = 3.5 alt.Float(string(1,5)) = 0.0 alt.Float(string(1,5), 2.5) = 2.5 alt.Float(string(1,5), 2.5, 3.5) = 3.5 alt.Float(string(x)) = 0.0 alt.Float(string(x), 2.5) = 2.5 alt.Float(string(x), 2.5, 3.5) = 3.5 alt.Float(float64(1.5)) = 1.5 alt.Float(float64(1.5), 2.5) = 1.5 alt.Float(float64(1.5), 2.5, 3.5) = 1.5 alt.Float(bool(true)) = 0.0 alt.Float(bool(true), 2.5) = 2.5 alt.Float(bool(true), 2.5, 3.5) = 3.5
func GenAlter ¶
GenAlter converts a simple go data element into Node compliant data. A best effort is made to convert values that are not simple into generic Nodes. It modifies the values inplace if possible by altering the original.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" "github.com/ohler55/ojg/gen" "github.com/ohler55/ojg/oj" "github.com/ohler55/ojg/sen" ) func main() { m := map[string]any{"a": 1, "b": 4, "c": 9} // Convert to a gen.Node. node := alt.GenAlter(m) fmt.Println(sen.String(node, &oj.Options{Sort: true})) obj, _ := node.(gen.Object) fmt.Printf("member type: %T\n", obj["b"]) }
Output: {a:1 b:4 c:9} member type: gen.Int
func Generify ¶
Generify converts a value into Node compliant data. A best effort is made to convert values that are not simple into generic Nodes.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" "github.com/ohler55/ojg/gen" "github.com/ohler55/ojg/oj" ) type genny struct { val int } func (g *genny) Generic() gen.Node { return gen.Object{"type": gen.String("genny"), "val": gen.Int(g.val)} } func main() { // Non public types can be encoded with the Genericer interface which // should decompose into a gen.Node. ga := []*genny{{val: 3}} v := alt.Generify(ga) // Encode to JSON after decomposing using the Genericer interface. fmt.Println(oj.JSON(v, &oj.Options{Sort: true})) }
Output: [{"type":"genny","val":3}]
func Int ¶
Int convert the value provided to an int64. If conversion is not possible such as if the provided value is an array then the first option default value is returned or if not provided 0 is returned. If the type is not one of the int or uint types and there is a second optional default then that second default value is returned. This approach keeps the return as a single value and gives the caller the choice of how to indicate a bad value.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" ) func main() { for _, src := range []any{1, "1", "x", 1.5, []any{}} { fmt.Printf("alt.Int(%T(%v)) = %d alt.Int(%T(%v), 2) = %d alt.Int(%T(%v), 2, 3) = %d\n", src, src, alt.Int(src), src, src, alt.Int(src, 2), src, src, alt.Int(src, 2, 3)) } }
Output: alt.Int(int(1)) = 1 alt.Int(int(1), 2) = 1 alt.Int(int(1), 2, 3) = 1 alt.Int(string(1)) = 1 alt.Int(string(1), 2) = 1 alt.Int(string(1), 2, 3) = 3 alt.Int(string(x)) = 0 alt.Int(string(x), 2) = 2 alt.Int(string(x), 2, 3) = 3 alt.Int(float64(1.5)) = 1 alt.Int(float64(1.5), 2) = 1 alt.Int(float64(1.5), 2, 3) = 3 alt.Int([]interface {}([])) = 0 alt.Int([]interface {}([]), 2) = 2 alt.Int([]interface {}([]), 2, 3) = 2
func Match ¶ added in v1.11.0
Match returns true if all elements in the fingerprint match those in target. Fields in target but not in the fingerprint are ignored. An explicit nil in the fingerprint will match either a nil in the target or a missing value in the target.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" ) func main() { fingerprint := map[string]any{"x": 1, "z": 3} match := alt.Match( fingerprint, map[string]any{"x": 1, "y": 2, "z": 3}, ) fmt.Printf("match: %t\n", match) }
Output: match: true
func MustRecompose ¶ added in v1.11.0
MustRecompose simple data into more complex go types and panics on error.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" ) func main() { type Sample struct { Int int Str string } // Simplified sample data or JSON as a map[string]any. data := map[string]any{"int": 3, "str": "three"} var sample Sample // Recompose into the sample struct. Panic on failure. v := alt.MustRecompose(data, &sample) fmt.Printf("type: %T\n", v) fmt.Printf("sample: {Int: %d, Str: %q}\n", sample.Int, sample.Str) }
Output: type: *alt_test.Sample sample: {Int: 3, Str: "three"}
func Recompose ¶ added in v1.9.0
Recompose simple data into more complex go types.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" ) func main() { type Sample struct { Int int Str string } // Simplified sample data or JSON as a map[string]any. data := map[string]any{"int": 3, "str": "three"} var sample Sample // Recompose into the sample struct. Panic on failure. v, err := alt.Recompose(data, &sample) if err != nil { panic(err) } fmt.Printf("type: %T\n", v) fmt.Printf("sample: {Int: %d, Str: %q}\n", sample.Int, sample.Str) }
Output: type: *alt_test.Sample sample: {Int: 3, Str: "three"}
func String ¶
String converts the value provided to a string. If conversion is not possible such as if the provided value is an array then the first option default value is returned or if not provided and empty string is returned. If the type is not a string or gen.String and there is a second optional default then that second default value is returned. This approach keeps the return as a single value and gives the caller the choice of how to indicate a bad value.
Example ¶
package main import ( "fmt" "time" "github.com/ohler55/ojg/alt" ) func main() { tm := time.Date(2021, time.February, 9, 12, 13, 14, 0, time.UTC) for _, src := range []any{"xyz", 1, 1.5, true, tm, []any{}} { fmt.Printf("alt.String(%T(%v)) = %s alt.String(%T(%v), default) = %s alt.String(%T(%v), default, picky) = %s\n", src, src, alt.String(src), src, src, alt.String(src, "default"), src, src, alt.String(src, "default", "picky")) } }
Output: alt.String(string(xyz)) = xyz alt.String(string(xyz), default) = xyz alt.String(string(xyz), default, picky) = xyz alt.String(int(1)) = 1 alt.String(int(1), default) = 1 alt.String(int(1), default, picky) = picky alt.String(float64(1.5)) = 1.5 alt.String(float64(1.5), default) = 1.5 alt.String(float64(1.5), default, picky) = picky alt.String(bool(true)) = true alt.String(bool(true), default) = true alt.String(bool(true), default, picky) = picky alt.String(time.Time(2021-02-09 12:13:14 +0000 UTC)) = 2021-02-09T12:13:14Z alt.String(time.Time(2021-02-09 12:13:14 +0000 UTC), default) = 2021-02-09T12:13:14Z alt.String(time.Time(2021-02-09 12:13:14 +0000 UTC), default, picky) = picky alt.String([]interface {}([])) = alt.String([]interface {}([]), default) = default alt.String([]interface {}([]), default, picky) = picky
func Time ¶
Time convert the value provided to a time.Time. If conversion is not possible such as if the provided value is an array then the first option default value is returned or if not provided zero time is returned. If the type is not one of the int or uint types and there is a second optional default then that second default value is returned. This approach keeps the return as a single value and gives the caller the choice of how to indicate a bad value.
Example ¶
package main import ( "fmt" "time" "github.com/ohler55/ojg/alt" ) func main() { tm := time.Date(2021, time.February, 9, 12, 13, 14, 0, time.UTC) td := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) // default tp := time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) // picky default for _, src := range []any{tm, 1612872711000000000, "2021-02-09T01:02:03Z", "x", 1612872722.0, true} { fmt.Printf("alt.Time(%T(%v)) = %s\nalt.Time(%T(%v), td) = %s\nalt.Time(%T(%v), td, tp) = %s\n", src, src, alt.Time(src).Format(time.RFC3339), src, src, alt.Time(src, td).Format(time.RFC3339), src, src, alt.Time(src, td, tp).Format(time.RFC3339)) } }
Output: alt.Time(time.Time(2021-02-09 12:13:14 +0000 UTC)) = 2021-02-09T12:13:14Z alt.Time(time.Time(2021-02-09 12:13:14 +0000 UTC), td) = 2021-02-09T12:13:14Z alt.Time(time.Time(2021-02-09 12:13:14 +0000 UTC), td, tp) = 2021-02-09T12:13:14Z alt.Time(int(1612872711000000000)) = 2021-02-09T12:11:51Z alt.Time(int(1612872711000000000), td) = 2021-02-09T12:11:51Z alt.Time(int(1612872711000000000), td, tp) = 2000-01-01T00:00:00Z alt.Time(string(2021-02-09T01:02:03Z)) = 2021-02-09T01:02:03Z alt.Time(string(2021-02-09T01:02:03Z), td) = 2021-02-09T01:02:03Z alt.Time(string(2021-02-09T01:02:03Z), td, tp) = 2000-01-01T00:00:00Z alt.Time(string(x)) = 0001-01-01T00:00:00Z alt.Time(string(x), td) = 2021-01-01T00:00:00Z alt.Time(string(x), td, tp) = 2000-01-01T00:00:00Z alt.Time(float64(1.612872722e+09)) = 2021-02-09T12:12:02Z alt.Time(float64(1.612872722e+09), td) = 2021-02-09T12:12:02Z alt.Time(float64(1.612872722e+09), td, tp) = 2000-01-01T00:00:00Z alt.Time(bool(true)) = 0001-01-01T00:00:00Z alt.Time(bool(true), td) = 2021-01-01T00:00:00Z alt.Time(bool(true), td, tp) = 2000-01-01T00:00:00Z
Types ¶
type AttrSetter ¶
type AttrSetter interface { // SetAttr sets an attribute of the object associated with the path. SetAttr(attr string, val any) error }
AttrSetter interface is for objects that can set attributes using the SetAttr() function.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" ) type Setter struct { a int64 b string } func (s *Setter) String() string { return fmt.Sprintf("Setter{a:%d,b:%s}", s.a, s.b) } func (s *Setter) SetAttr(attr string, val any) error { switch attr { case "a": s.a = alt.Int(val) case "b": s.b, _ = val.(string) default: return fmt.Errorf("%s is not an attribute of Setter", attr) } return nil } func main() { src := map[string]any{"a": 3, "b": "bee"} r := alt.MustNewRecomposer("", nil) var setter Setter _ = r.MustRecompose(src, &setter) fmt.Println(setter.String()) }
Output: Setter{a:3,b:bee}
type Builder ¶ added in v1.11.0
type Builder struct {
// contains filtered or unexported fields
}
Builder is a basic type builder. It uses a stack model to build where maps (objects) and slices (arrays) add pushed on the stack and closed with a pop.
func (*Builder) Array ¶ added in v1.11.0
Array pushs a []any onto the stack. A key must be provided if the top of the stack is an object (map) and must not be provided if the op of the stack is an array or slice.
func (*Builder) Object ¶ added in v1.11.0
Object pushs a map[string]any onto the stack. A key must be provided if the top of the stack is an object (map) and must not be provided if the op of the stack is an array or slice.
func (*Builder) Pop ¶ added in v1.11.0
func (b *Builder) Pop()
Pop the stack, closing an array or object.
func (*Builder) PopAll ¶ added in v1.11.0
func (b *Builder) PopAll()
PopAll repeats Pop until all open arrays or objects are closed.
type Filter ¶ added in v1.17.5
Filter is a simple filter for matching against arbitrary date.
func NewFilter ¶ added in v1.17.5
NewFilter creates a new filter from the spec which should be a map where the keys are simple paths of keys delimited by the dot ('.') character. An example is "top.child.grandchild". The matching will either match the key when the data is traversed directly or in the case of a slice the elements of the slice are also traversed. Generally a Filter is created and reused as there is some overhead in creating the Filter. An alternate format is a nested set of maps.
type Genericer ¶
type Genericer interface { // Generic should return a Node that represents the object. Generally this // includes the use of a creation key consistent with call to the // reflection based Generic() function. Generic() gen.Node }
Genericer is the interface for the Generic() function that converts types to generic types.
type Path ¶ added in v1.8.0
type Path []any
Path is a list of keys that can be either a string, int, or nil. Strings are used for keys in a map, ints are for indexes to a slice/array, and nil is a wildcard that matches either.
func Compare ¶ added in v1.8.0
Compare returns a path to the first difference encountered between two values. Any ignore paths are ignored in the comparison.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" ) func main() { diff := alt.Compare( map[string]any{"x": 1, "y": 2, "z": []any{1, 2, 3}}, map[string]any{"x": 1, "y": 2, "z": []any{1, 3, 5}}, ) fmt.Printf("diff: %v\n", diff) }
Output: diff: z[1]
func Diff ¶ added in v1.8.0
Diff returns the paths to the differences between two values. Any ignore paths are ignored in the comparison.
Example ¶
package main import ( "fmt" "sort" "strings" "github.com/ohler55/ojg/alt" ) func main() { diffs := alt.Diff( map[string]any{"x": 1, "y": 2, "z": []any{1, 2, 3}}, map[string]any{"x": 1, "y": 4, "z": []any{1, 3, 5}}, ) sort.Slice(diffs, func(i, j int) bool { return 0 < strings.Compare(fmt.Sprintf("%v", diffs[j]), fmt.Sprintf("%v", diffs[i])) }) fmt.Printf("diff: %v\n", diffs) }
Output: diff: [y z[1] z[2]]
type RecomposeAnyFunc ¶ added in v1.11.0
RecomposeAnyFunc should build an object from data in an any returning the recomposed object or an error.
type RecomposeFunc ¶
RecomposeFunc should build an object from data in a map returning the recomposed object or an error.
type Recomposer ¶
type Recomposer struct { // CreateKey identifies the creation key in decomposed objects. CreateKey string // NumConvMethod specifies the json.Number conversion method. NumConvMethod ojg.NumConvMethod // contains filtered or unexported fields }
Recomposer is used to recompose simple data into structs.
func MustNewRecomposer ¶ added in v1.11.0
func MustNewRecomposer( createKey string, composers map[any]RecomposeFunc, anyComposers ...map[any]RecomposeAnyFunc) *Recomposer
MustNewRecomposer creates a new instance. The composers are a map of objects expected and functions to recompose them. If no function is provided then reflection is used instead. Panics on error.
Example ¶
package main import ( "fmt" "time" "github.com/ohler55/ojg/alt" ) func main() { type Sample struct { Int int When time.Time } // Create a new Recomposer that uses "^" as the create key and register a // default reflection recompose function (nil). A time recomposer from an // integer is also included in the new recomposer compser options. r := alt.MustNewRecomposer("^", map[any]alt.RecomposeFunc{&Sample{}: nil}, map[any]alt.RecomposeAnyFunc{&time.Time{}: func(v any) (any, error) { if s, _ := v.(string); 0 < len(s) { return time.ParseInLocation(time.RFC3339, s, time.UTC) } return nil, fmt.Errorf("can not convert a %v to a time.Time", v) }}) // Simplified sample data or JSON as a map[string]any with an // included create key using "^" to avoid possible conflicts with other // fields in the struct. data := map[string]any{"^": "Sample", "int": 3, "when": "2021-02-09T01:02:03Z"} v := r.MustRecompose(data) if sample, _ := v.(*Sample); sample != nil { fmt.Printf("sample: {Int: %d, When: %q}\n", sample.Int, sample.When.Format(time.RFC3339)) } }
Output: sample: {Int: 3, When: "2021-02-09T01:02:03Z"}
func NewRecomposer ¶
func NewRecomposer( createKey string, composers map[any]RecomposeFunc, anyComposers ...map[any]RecomposeAnyFunc) (rec *Recomposer, err error)
NewRecomposer creates a new instance. The composers are a map of objects expected and functions to recompose them. If no function is provided then reflection is used instead.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" ) func main() { type Sample struct { Int int Str string } // Recomposers are reuseable. Create one and use the default reflect composer (nil). r, err := alt.NewRecomposer("^", map[any]alt.RecomposeFunc{&Sample{}: nil}) if err != nil { panic(err) } var v any // Recompose without providing a struct to populate. v, err = r.Recompose(map[string]any{"^": "Sample", "int": 3, "str": "three"}) if err != nil { panic(err) } fmt.Printf("type: %T\n", v) if sample, _ := v.(*Sample); sample != nil { fmt.Printf("sample: {Int: %d, Str: %q}\n", sample.Int, sample.Str) } }
Output: type: *alt_test.Sample sample: {Int: 3, Str: "three"}
func (*Recomposer) MustRecompose ¶ added in v1.11.0
func (r *Recomposer) MustRecompose(v any, tv ...any) (out any)
MustRecompose simple data into more complex go types.
Example ¶
package main import ( "fmt" "time" "github.com/ohler55/ojg/alt" ) func main() { type Sample struct { Int int When time.Time } // Create a new Recomposer that uses "^" as the create key and register a // default reflection recompose function (nil). A time recomposer from an // integer is also included in the new recomposer compser options. r := alt.MustNewRecomposer("^", map[any]alt.RecomposeFunc{&Sample{}: nil}, map[any]alt.RecomposeAnyFunc{&time.Time{}: func(v any) (any, error) { if s, _ := v.(string); 0 < len(s) { return time.ParseInLocation(time.RFC3339, s, time.UTC) } return nil, fmt.Errorf("can not convert a %v to a time.Time", v) }}) // Simplified sample data or JSON as a map[string]any with an // included create key using "^" to avoid possible conflicts with other // fields in the struct. data := map[string]any{"^": "Sample", "int": 3, "when": "2021-02-09T01:02:03Z"} v := r.MustRecompose(data) if sample, _ := v.(*Sample); sample != nil { fmt.Printf("sample: {Int: %d, When: %q}\n", sample.Int, sample.When.Format(time.RFC3339)) } }
Output: sample: {Int: 3, When: "2021-02-09T01:02:03Z"}
func (*Recomposer) Recompose ¶
func (r *Recomposer) Recompose(v any, tv ...any) (out any, err error)
Recompose simple data into more complex go types.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" ) func main() { type Sample struct { Int int Str string } // Recomposers are reuseable. Create one and use the default reflect composer (nil). r, err := alt.NewRecomposer("^", map[any]alt.RecomposeFunc{&Sample{}: nil}) if err != nil { panic(err) } var v any // Recompose without providing a struct to populate. v, err = r.Recompose(map[string]any{"^": "Sample", "int": 3, "str": "three"}) if err != nil { panic(err) } fmt.Printf("type: %T\n", v) if sample, _ := v.(*Sample); sample != nil { fmt.Printf("sample: {Int: %d, Str: %q}\n", sample.Int, sample.Str) } }
Output: type: *alt_test.Sample sample: {Int: 3, Str: "three"}
Example (Animals) ¶
package main import ( "fmt" "github.com/ohler55/ojg/alt" "github.com/ohler55/ojg/oj" ) // Encode and decode slice of interfaces. Similar behavior is available with // oj.Unmarshal and sen.Unmarshal. type Animal interface { Kind() string } type Dog struct { Size string } func (d *Dog) Kind() string { return fmt.Sprintf("%s dog", d.Size) } type Cat struct { Color string } func (c *Cat) Kind() string { return fmt.Sprintf("%s cat", c.Color) } func main() { pets := []Animal{&Dog{Size: "big"}, &Cat{Color: "black"}} // Decompose and use a create key to identify the encoded type. simple := alt.Decompose(pets, &alt.Options{CreateKey: "^"}) // Sort the object members in the output for repeatability. fmt.Printf("as JSON: %s\n", oj.JSON(simple, &oj.Options{Sort: true})) // Create a new Recomposer. This can be use over and over again. Register // the types with a nil creation function to let reflection do the work // since the types are exported. r, err := alt.NewRecomposer("^", map[any]alt.RecomposeFunc{&Dog{}: nil, &Cat{}: nil}) if err != nil { panic(err) } // Recompose from the simplified data without providing a target which // returns a []any populated with the correct types. var result any if result, err = r.Recompose(simple); err != nil { panic(err) } list, _ := result.([]any) for _, item := range list { animal, _ := item.(Animal) fmt.Printf(" %s\n", animal.Kind()) } // Recompose with a target. var animals []Animal if _, err = r.Recompose(simple, &animals); err != nil { panic(err) } fmt.Println("Recompose into a target struct") for _, animal := range animals { fmt.Printf(" %T - %s\n", animal, animal.Kind()) } }
Output: as JSON: [{"^":"Dog","size":"big"},{"^":"Cat","color":"black"}] big dog black cat Recompose into a target struct *alt_test.Dog - big dog *alt_test.Cat - black cat
func (*Recomposer) RegisterAnyComposer ¶ added in v1.11.0
func (r *Recomposer) RegisterAnyComposer(val any, fun RecomposeAnyFunc) error
RegisterAnyComposer regsiters a composer function for a value type. A nil function will still register the default composer which uses reflection.
Example ¶
package main import ( "fmt" "time" "github.com/ohler55/ojg/alt" ) func main() { type Sample struct { Int int When time.Time } r := alt.MustNewRecomposer("^", nil) err := r.RegisterComposer(&Sample{}, nil) if err != nil { panic(err) } err = r.RegisterAnyComposer(time.Time{}, func(v any) (any, error) { if secs, ok := v.(int); ok { return time.Unix(int64(secs), 0), nil } return nil, fmt.Errorf("can not convert a %T to a time.Time", v) }) if err != nil { panic(err) } data := map[string]any{"^": "Sample", "int": 3, "when": 1612872722} sample, _ := r.MustRecompose(data).(*Sample) fmt.Printf("sample.Int: %d\n", sample.Int) fmt.Printf("sample.When: %d\n", sample.When.Unix()) }
Output: sample.Int: 3 sample.When: 1612872722
func (*Recomposer) RegisterComposer ¶ added in v1.11.0
func (r *Recomposer) RegisterComposer(val any, fun RecomposeFunc) error
RegisterComposer regsiters a composer function for a value type. A nil function will still register the default composer which uses reflection.
Example ¶
package main import ( "fmt" "time" "github.com/ohler55/ojg/alt" ) func main() { type Sample struct { Int int When time.Time } r := alt.MustNewRecomposer("^", nil) err := r.RegisterComposer(&Sample{}, nil) if err != nil { panic(err) } err = r.RegisterAnyComposer(time.Time{}, func(v any) (any, error) { if secs, ok := v.(int); ok { return time.Unix(int64(secs), 0), nil } return nil, fmt.Errorf("can not convert a %T to a time.Time", v) }) if err != nil { panic(err) } data := map[string]any{"^": "Sample", "int": 3, "when": 1612872722} sample, _ := r.MustRecompose(data).(*Sample) fmt.Printf("sample.Int: %d\n", sample.Int) fmt.Printf("sample.When: %d\n", sample.When.Unix()) }
Output: sample.Int: 3 sample.When: 1612872722
func (*Recomposer) RegisterUnmarshalerComposer ¶ added in v1.12.6
func (r *Recomposer) RegisterUnmarshalerComposer(fun RecomposeAnyFunc)
RegisterUnmarshalerComposer regsiters a composer function for a named value. This is only used to register cross package json.Unmarshaler composer which returns []byte.
type Simplifier ¶
type Simplifier interface { // Simplify should return one of the simple types which are: nil, bool, // int64, float64, string, time.Time, []any, or // map[string]any. Simplify() any }
Simplifier interface is for objects that can decompose themselves into simple data.
Example ¶
package main import ( "fmt" "github.com/ohler55/ojg/oj" ) type simmer struct { val int } func (s *simmer) Simplify() any { return map[string]any{"type": "simmer", "val": s.val} } func main() { // Non public types can be encoded with the Simplifier interface which // should decompose into a simple type. fmt.Println(oj.JSON(&simmer{val: 3}, &oj.Options{Sort: true})) }
Output: {"type":"simmer","val":3}
Source Files
¶
- alt.go
- attrsetter.go
- bool.go
- builder.go
- composer.go
- decompose.go
- diff.go
- doc.go
- fbool.go
- ffloat32.go
- ffloat64.go
- filter.go
- finfo.go
- fint.go
- fint16.go
- fint32.go
- fint64.go
- fint8.go
- float.go
- fuint.go
- fuint16.go
- fuint32.go
- fuint64.go
- fuint8.go
- generifier.go
- int.go
- recomposer.go
- simplifier.go
- sinfo.go
- string.go
- time.go