Skip to content

Instantly share code, notes, and snippets.

@valsteen
Created December 30, 2022 15:39
Show Gist options
  • Save valsteen/40a1b0fa8eda11d07764aebaf4524924 to your computer and use it in GitHub Desktop.
Save valsteen/40a1b0fa8eda11d07764aebaf4524924 to your computer and use it in GitHub Desktop.
Option generics with (un)marshalling
package main
import (
"encoding/json"
"fmt"
)
type TenantProfileEntity struct {
SomeOtherValue string
}
type MyType struct {
Name string
Description string
MyVarGotByFK Option[TenantProfileEntity]
}
type Option[T any] struct {
isSet bool
inner T
}
func (o Option[T]) MarshalJSON() ([]byte, error) {
if obj := o.Unwrap(); obj != nil {
if inner, ok := any(&o.inner).(json.Marshaler); ok {
return inner.MarshalJSON()
}
return json.Marshal(obj)
}
return []byte("null"), nil
}
func (o *Option[T]) UnmarshalJSON(bytes []byte) error {
if string(bytes) == "null" {
o.isSet = false
return nil
}
if inner, ok := any(&o.inner).(json.Unmarshaler); ok {
return inner.UnmarshalJSON(bytes)
}
if err := json.Unmarshal(bytes, &o.inner); err != nil {
return err
}
o.isSet = true
return nil
}
func (o *Option[T]) Unwrap() *T {
if o.isSet {
return &o.inner
}
return nil
}
func None[T any]() Option[T] {
return Option[T]{}
}
func Some[T any](obj T) Option[T] {
return Option[T]{
inner: obj,
isSet: true,
}
}
func Get(id int) Option[MyType] {
switch id {
case 0:
return None[MyType]()
case 1:
return Some(MyType{
Name: "MyVarGotByFK not set",
})
default:
return Some(MyType{
MyVarGotByFK: Some(TenantProfileEntity{SomeOtherValue: "hi"}),
})
}
}
func main() {
// below escape analysis results on go1.19.4 , using go build -gcflags=-m=2
for i := 0; i < 3; i++ {
obj := Get(i) // won't escape
if myObj := obj.Unwrap(); myObj != nil { // won't escape
fmt.Printf("myObj %d is set\n", i) // i escapes due to Printf
if profile := myObj.MyVarGotByFK.Unwrap(); profile != nil { // won't escape
// note : profile.SomeOtherValue escapes to the heap due to Printf
fmt.Printf("profile of %d is set: %s\n", i, profile.SomeOtherValue)
}
} else {
fmt.Printf("myObj %d is not set\n", i)
}
marshalled, err := json.Marshal(obj)
if err != nil {
fmt.Println("marshalling error", err)
} else {
fmt.Printf("mashalled: %s\n", string(marshalled))
}
var unmarshalled Option[MyType]
if err := unmarshalled.UnmarshalJSON(marshalled); err != nil {
fmt.Println("unmarshalling error", err)
} else {
fmt.Printf("unmarshalled: %+v\n", unmarshalled)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment