Categorygithub.com/dhoelle/oneof
modulepackage
0.0.0-20241022210940-a435ecb88a4b
Repository: https://github.com/dhoelle/oneof.git
Documentation: pkg.go.dev

# README

oneof

Go Reference Build Status

Package oneof enables marshaling and unmarshaling of Go interface values using the Go JSON V2 experiment (github.com/go-json-experiment/json).

By default, marshaling and unmarshaling an interface value will fail:

var s1 fmt.Stringer = crypto.SHA256
b, _ := json.Marshal(s1) // b == []byte("5")

var s2 fmt.Stringer
err := json.Unmarshal(b, &s2)
fmt.Println(err)
// Output:
// json: cannot unmarshal JSON string into Go value of type fmt.Stringer: cannot derive concrete type for non-empty interface

oneof's MarshalFunc and UnmarshalFunc encode matching Go values alongside a type discriminator, which enables round-trip marshaling and unmarshaling:

// Implementations of fmt.Stringer that our program will marshal and unmarshal,
// keyed by an option type
opts := map[string]fmt.Stringer{
  "crypto.Hash": crypto.Hash(0),
  "net.IP":      net.IP{},
  "url.URL":     &url.URL{},
}

marshalFunc := oneof.MarshalFunc(opts, nil)
var s1 fmt.Stringer = crypto.SHA256
b, _ := json.Marshal(s1, json.WithMarshalers(marshalFunc))
// b == []byte(`{"_type": "crypto.Hash", "_value": 5}`)

unmarshalFunc := oneof.UnmarshalFunc(opts, nil)
var s2 fmt.Stringer
_ = json.Unmarshal(b, &s2, json.WithUnmarshalers(unmarshalFunc))
fmt.Printf("unmarshaled type = %T\n", s2)
fmt.Printf("string output = %s\n", s2.String())
// Output:
// unmarshaled type: crypto.Hash
// string output: SHA-256

Default encoding

By default, MarshalFunc encodes known Go values into a JSON object where:

  • The value of the key "_type" is the type discriminator, and,
  • The value of the key "_value" is the default JSON-encoding of the Go value

For example, given options:

opts := map[string]fmt.Stringer{
  "crypto.Hash": crypto.Hash(0),
  "net.IP":      net.IP{},
  "url.URL":     &url.URL{},
}

... the Go value crypto.SHA256 (which encodes to the JSON number 5), would be encoded by MarshalFunc as:

{
  "_type": "crypto.Hash",
  "_value": 5
}

... and the Go value &url.URL{Scheme: "https", Host: "example.com"}, which encodes to a JSON object, would be encoded by MarshalFunc as:

{
  "_type": "url.URL",
  "_value": {
    "Scheme": "https",
    "Host": "example.com"
    // other url.URL fields omitted
  }
}

Custom encoding

WrappedValue is the interface implemented by containers which can marshal and unmarshal Go types including type information. Configure the WrappedValue used by MarshalFunc and UnmarshalFunc by setting Config.WrapFunc:

cfg := oneof.Config{
  WrapFunc: oneof.WrapNested,
}
marshalFunc := oneof.MarshalFunc(opts, cfg)

If Config.WrapFunc is unset, MarshalFunc and UnmarshalFunc default to WrapNested, which wraps encoded values under the "_value" key.

The oneof package also defines WrapInline, which inlines the fields of JSON object values, e.g.,:

{
  "_type": "url.URL",
  "Scheme": "https",
  "Host": "example.com"
  // other url.URL fields omitted
}

For finer-grained control, you can create your own WrappedValue type, or use CustomValueWrapper (whose method Wrap can be used as Config.WrapFunc)

See the WrapFunc and CustomValueWrapper examples.

Handling missing keys

If oneof encounters a Go type for which there is no matching option key while marshaling, it will return an error.

You can override this behavior by setting Config.ReplaceMissingTypeFunc:

cfg := &oneof.Config{
  ReplaceMissingTypeFunc: func(v any) string {
    return fmt.Sprintf("MISSING_%T", v)
  },
}

With the above Config, MarshalFunc will use the string produced by ReplaceMissingTypeFunc as the option type for missing values, like:

{
  "_type": "MISSING_*crypto.Hash",
  "_value": 5
},

[!NOTE] UnmarshalFunc will likely fail to unmarshal output produced by ReplaceMissingTypeFunc. If you need to marshal and unmarshal a Go type, include it in the option set.

# Functions

No description provided by the author
MarshalFunc creates a [json.MarshalFuncV2] which can intercept marshaling behavior for values of type T and encode a JSON value that can be unmarshaled by [UnmarshalFunc] into a Go value of the original type T.
UnmarshalFunc creates a [json.UnmarshalFuncV2] which will intercept unmarshaling behavior for values of type T.
WrapInline wraps a oneof value with default "inline" behavior, specifically: - Type is stored under the "_type" key - JSON object values are inlined into the same object as the "_type" key - All non-object JSON values are nested under the "_value" key The marshaled JSON output looks like: { "my_stringers": [ { "_type": "crypto.Hash", "_value": 5 }, { "_type": "url.URL", "Scheme": "https", "Host": "example.com" }, ] }.
WrapNested nests the provided jsontext.Value underneath the "_value" key within the wrapper object.

# Structs

No description provided by the author
CustomValueWrapper can be used to quickly build a custom WrapFunc.
ErrUnknownDiscriminatorValue is the error returned by UnmarshalFunc when it encounters a JSON discriminator value which is not in the provided set of options.
ErrUnknownGoType is the error returned by MarshalFunc when it encounters a Go type that is not in the provided set of options.

# Interfaces

WrappedValue is the interface implemented by types that can encode a Go type and oneof option string into JSON, and can decode that JSON back into a matching Go type.