alt

package
v0.0.0-...-af45976 Latest Latest
Warning

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

Go to latest
Published: Jan 25, 2023 License: MIT Imports: 14 Imported by: 0

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/khaf/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

Examples

Constants

This section is empty.

Variables

View Source
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
)
View Source
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.

View Source
var TimeTolerance = time.Millisecond

TimeTolerance is the tolerance when comparing time elements

Functions

func Alter

func Alter(v any, options ...*ojg.Options) any

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/khaf/ojg/alt"
	"github.com/khaf/ojg/oj"
	"github.com/khaf/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

func Bool(v any, defaults ...bool) (b 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/khaf/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

func Decompose(v any, options ...*ojg.Options) any

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/khaf/ojg"
	"github.com/khaf/ojg/alt"
	"github.com/khaf/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/khaf/ojg/alt_test/Sample","int":3,"str":"three"}

func Dup

func Dup(v any, options ...*ojg.Options) any

Dup is an alias for Decompose.

Example
package main

import (
	"fmt"

	"github.com/khaf/ojg"
	"github.com/khaf/ojg/alt"
	"github.com/khaf/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

func Float(v any, defaults ...float64) (f float64)

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/khaf/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

func GenAlter(v any, options ...*Options) (n gen.Node)

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/khaf/ojg/alt"
	"github.com/khaf/ojg/gen"
	"github.com/khaf/ojg/oj"
	"github.com/khaf/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

func Generify(v any, options ...*Options) (n gen.Node)

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/khaf/ojg/alt"
	"github.com/khaf/ojg/gen"
	"github.com/khaf/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

func Int(v any, defaults ...int64) (i int64)

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/khaf/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

func Match(fingerprint, target any) bool

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/khaf/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

func MustRecompose(v any, tv ...any) (out any)

MustRecompose simple data into more complex go types and panics on error.

Example
package main

import (
	"fmt"

	"github.com/khaf/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

func Recompose(v any, tv ...any) (out any, err error)

Recompose simple data into more complex go types.

Example
package main

import (
	"fmt"

	"github.com/khaf/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

func String(v any, defaults ...string) (s 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/khaf/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

func Time(v any, defaults ...time.Time) (t time.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/khaf/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/khaf/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

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

func (b *Builder) Array(key ...string) error

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

func (b *Builder) Object(key ...string) error

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

func (b *Builder) Pop()

Pop the stack, closing an array or object.

func (*Builder) PopAll

func (b *Builder) PopAll()

PopAll repeats Pop until all open arrays or objects are closed.

func (*Builder) Reset

func (b *Builder) Reset()

Reset the builder.

func (*Builder) Result

func (b *Builder) Result() (result any)

Result of the builder is returned. This is the first item pushed on to the stack.

func (*Builder) Value

func (b *Builder) Value(value any, key ...string) error

Value pushs a value 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.

type Converter

type Converter = ojg.Converter

Converter is an alias for ojg.Converter

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 Options

type Options = ojg.Options

Options is an alias for ojg.Options

type Path

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

func Compare(v0, v1 any, ignores ...Path) Path

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/khaf/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

func Diff(v0, v1 any, ignores ...Path) (diffs []Path)

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/khaf/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

type RecomposeAnyFunc func(any) (any, error)

RecomposeAnyFunc should build an object from data in an any returning the recomposed object or an error.

type RecomposeFunc

type RecomposeFunc func(map[string]any) (any, error)

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
	// contains filtered or unexported fields
}

Recomposer is used to recompose simple data into structs.

func MustNewRecomposer

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/khaf/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/khaf/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

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/khaf/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/khaf/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/khaf/ojg/alt"
	"github.com/khaf/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

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/khaf/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

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/khaf/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

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/khaf/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}

Jump to

Keyboard shortcuts

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