Golang - Customer Unmarshaler/Marshaler on pointer with nil/null value - go

Golang - Customer Unmarshaler/Marshaler on pointer with nil/null value
I'm trying to implement custom UnmarshalJSON and MarshalJSON on pointer type, however none of this function is called when data from json is null/nil like in example below:
package main
import (
"encoding/json"
"fmt"
)
type A struct {
B *B `json:"b,omitempty"`
}
type B int
// Only for displaying value instead of
// pointer address when calling `fmt.Println`
func (b *B) String() string {
if b == nil {
return "nil"
}
return fmt.Sprintf("%d", *b)
}
// This function is not triggered when json
// data contains null instead of number value
func (b *B) UnmarshalJSON(data []byte) error {
fmt.Println("UnmarshalJSON on B called")
var value int
if err := json.Unmarshal(data, &value); err != nil {
return err
}
if value == 7 {
*b = B(3)
}
return nil
}
// This function is not triggered when `B`
// is pointer type and has `nil` value
func (b *B) MarshalJSON() ([]byte, error) {
fmt.Println("MarshalJSON on B called")
if b == nil {
return json.Marshal(0)
}
if *b == 3 {
return json.Marshal(7)
}
return json.Marshal(*b)
}
func main() {
var a A
// this won't call `UnmarshalJSON`
json.Unmarshal([]byte(`{ "b": null }`), &a)
fmt.Printf("a: %+v\n", a)
// this won't call `MarshalJSON`
b, _ := json.Marshal(a)
fmt.Printf("b: %s\n\n", string(b))
// this would call `UnmarshalJSON`
json.Unmarshal([]byte(`{ "b": 7 }`), &a)
fmt.Printf("a: %+v\n", a)
// this would call `MarshalJSON`
b, _ = json.Marshal(a)
fmt.Printf("b: %s\n\n", string(b))
}
output:
a: {B:nil}
b: {}
UnmarshalJSON on B called
a: {B:3}
MarshalJSON on B called
b: {"b":7}
My questions are:
why UnmarshalJSON/MarshalJSON is not called with null/nil value on pointer type
how we can call UnmarshalJSON/MarshalJSON everytime when data is null/nil and type is a pointer instead of implementing UnmarshalJSON/MarshalJSON on A type and modify b property from level of A

For Short
Currently, unmarshaling/marshaling a Go struct will emit only the non-zero fields, since nil pointer is one zero value in Go, the UnmarshalJSON/MarshalJSON is not called in this case.
Also, it seems there are some related proposals
proposal: encoding/json: add omitzero option
proposal: encoding/json: allow returning nil from MarshalJSON to omit the field
However, there is no solution to resolve it now.
Per code Unmarshalers
Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op
// By convention, to approximate the behavior of Unmarshal itself,
// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}

Related

Go - Enforce that an interface is only satisfied by types with a pointer receiver on a method?

I'm doing some experimentation with type parameters to come up with a generic way of wiring up structs that generate a response to JSON HTTP requests.
The Method interface which the structs must implement has a SetParams method. This will work as expected as long as the implementation uses a pointer receiver.
My question: Is there any way of making this a compile time error if SetParams has a value receiver?
Here is an example demonstrating the problem with a SetParams that has a value receiver:
package main
import (
"encoding/json"
"fmt"
"log"
)
type PingParams struct {
Name string
}
type PingResponse struct {
Message string
}
func (p PingParams) Greeting() string {
if p.Name != "" {
return fmt.Sprintf("Hello, %s", p.Name)
}
return fmt.Sprintf("Hello, nobody!")
}
type GoodPing struct {
Params PingParams
}
// SetParams has a pointer receiver.
func (m *GoodPing) SetParams(p PingParams) {
fmt.Printf("assign %v with pointer receiver, Good!\n", p)
m.Params = p
}
func (m GoodPing) Run() (*PingResponse, error) {
return &PingResponse{Message: fmt.Sprintf("%T %s", m, m.Params.Greeting())}, nil
}
type BadPing struct {
Params PingParams
}
// SetParams has a value receiver.
func (m BadPing) SetParams(p PingParams) {
fmt.Printf("assign %v with value receiver, Bad!\n", p)
m.Params = p
}
func (m BadPing) Run() (*PingResponse, error) {
return &PingResponse{Message: fmt.Sprintf("%T %s", m, m.Params.Greeting())}, nil
}
type Method[M, RQ, RS any] interface {
// Run builds the RPC result.
Run() (*RS, error)
// SetParams is intended to set the request parameters in the struct implementing the RPC method.
// This then allows the request parameters to be easily available to all methods of the Method struct.
// The method MUST have a pointer receiver. This is NOT enforced at compile time.
SetParams(p RQ)
// The following line requires the implementing type is a pointer to M.
*M
// https://stackoverflow.com/a/72090807
}
func HandlerMethod[M, RQ, RS any, T Method[M, RQ, RS]](in json.RawMessage) (*RS, error) {
// A real implementation of this would return a func for wiring into a request router
var req RQ
err := json.Unmarshal(in, &req)
if err != nil {
return nil, err
}
var m T = new(M)
m.SetParams(req)
return m.Run()
}
func main() {
payload := []byte(`{"Name": "Mark"}`)
bad, err := HandlerMethod[BadPing, PingParams, PingResponse](payload)
if err != nil {
log.Fatal(err)
}
fmt.Println(bad.Message)
good, err := HandlerMethod[GoodPing, PingParams, PingResponse](payload)
if err != nil {
log.Fatal(err)
}
fmt.Println(good.Message)
}
https://go.dev/play/p/Eii8ADkmDxE
You can't do that.
When in your code you do this:
var m T = new(M)
even if T's type set includes only *M as a type term, *M's method set includes methods declared on M. The compiler can't check for you how the method ends up in *M's method set.
It is your responsibility when declaring the method SetParam on BadPing to make sure that the method doesn't attempt to unfruitfully modify the receiver.

Is there a way to define a constraint on the pointer to the type parameter? [duplicate]

Now that type parameters are available on golang/go:master, I decided to give it a try. It seems that I'm running into a limitation I could not find in the Type Parameters Proposal. (Or I must have missed it).
I want to write a function which returns a slice of values of a generic type with the constraint of an interface type. If the passed type is an implementation with a pointer receiver, how can we instantiate it?
type SetGetter[V any] interface {
Set(V)
Get() V
}
// SetGetterSlice turns a slice of type V into a slice of type T,
// with T.Set() called for each entry in values.
func SetGetterSlice[V any, T SetGetter[V]](values []V) []T {
out := make([]T, len(values))
for i, v := range values {
out[i].Set(v) // panic if T has pointer receiver!
}
return out
}
When calling the above SetGetterSlice() function with the *Count type as T, this code will panic upon calling Set(v). (Go2go playground) To no surprise, as basically the code created a slice of nil pointers:
// Count implements SetGetter interface
type Count struct {
x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int { return c.x }
func main() {
ints := []int{1, 2, 3, 4, 5}
sgs := SetGetterSlice[int, *Count](ints)
for _, s := range sgs {
fmt.Println(s.Get())
}
}
Variations of the same problem
This ideas won't work, and I can't seem to find any simple way to instantiate the pointed value.
out[i] = new(T) will result in a compile failure, as it returns a *T where the type checker wants to see T.
Calling *new(T), compiles but will result in the same runtime panic because new(T) returns **Count in this case, where the pointer to Count is still nil.
Changing the return type to a slice of pointer to T will result in a compile failure:
func SetGetterSlice[V any, T SetGetter[V]](values []V) []*T {
out := make([]*T, len(values))
for i, v := range values {
out[i] = new(T)
out[i].Set(v) // panic if T has pointer receiver
}
return out
}
func main() {
ints := []int{1, 2, 3, 4, 5}
SetGetterSlice[int, Count](ints)
// Count does not satisfy SetGetter[V]: wrong method signature
}
Workaround
The only solution I found until now, is to require a constructor function to be passed to the generic function. But this just feels wrong and a bit tedious. Why would this be required if func F(T interface{})() []T is perfectly valid syntax?
func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T {
out := make([]T, len(values))
for i, v := range values {
out[i] = constructor()
out[i].Set(v)
}
return out
}
// ...
func main() {
ints := []int{1, 2, 3, 4, 5}
SetGetterSlice[int, *Count](ints, func() *Count { return new(Count) })
}
Summary
My questions, in order of priority:
Am I overlooking something obvious?
Is this a limitation of generics in Go and this is as good as it gets?
Is this limitation known or should I raise an issue at the Go project?
Basically you have to add one more type parameter to the constraint to make T convertible to its pointer type. In its most basic form, this technique looks like the following (with an anonymous constraint):
func Foo[T any, PT interface { *T; M() }]() {
p := PT(new(T))
p.M() // calling method on non-nil pointer
}
Playground: https://go.dev/play/p/L00tePwrDfx
Step by step solution
Your constraint SetGetter already declares a type param V, so we slightly modify the example above:
// V is your original type param
// T is the additional helper param
type SetGetter[V any, T any] interface {
Set(V)
Get() V
*T
}
Then you define the SetGetterSlice function with the type parameter T any, whose purpose is just to instantiate the constraint SetGetter.
You will then be able to convert the expression &out[i] to the pointer type, and successfully call the method on the pointer receiver:
// T is the type with methods with pointer receiver
// PT is the SetGetter constraint with *T
func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
out := make([]T, len(values))
for i, v := range values {
// out[i] has type T
// &out[i] has type *T
// PT constraint includes *T
p := PT(&out[i]) // valid conversion!
p.Set(v) // calling with non-nil pointer receiver
}
return out
}
Full program:
package main
import (
"fmt"
)
type SetGetter[V any, T any] interface {
Set(V)
Get() V
*T
}
func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
out := make([]T, len(values))
for i, v := range values {
p := PT(&out[i])
p.Set(v)
}
return out
}
// Count implements SetGetter interface
type Count struct {
x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int { return c.x }
func main() {
ints := []int{1, 2, 3, 4, 5}
// instantiate with base type
sgs := SetGetterSlice[int, Count](ints)
for _, s := range sgs {
fmt.Println(s.Get()) // prints 1,2,3,4,5 each in a newline
}
}
This becomes more verbose because SetGetterSlice now requires three type parameters: the original V plus T (the type with pointer receivers) and PT (the new constraint). However when you call the function, you can omit the third one – with type inference, both type params V and T required to instantiate PT SetGetter[V,T] are already known:
SetGetterSlice[int, Count](ints)
Playground: https://go.dev/play/p/gcQZnw07Wp3
Spent a few hours to understand it.
So decided to add my example.
package main
import (
"fmt"
)
type User struct {
FullName string
Removed bool
}
type Account struct {
Name string
Removed bool
}
type Scanner[T User | Account] interface {
Scan()
*T
}
type Model interface {
User | Account
}
func (user *User) Scan() {
user.FullName = `changed in scan method`
user.Removed = true
}
func (account *Account) Scan() {
account.Name = `changed in scan method`
account.Removed = true
}
func setRemovedState[T Model, PT Scanner[T]](state bool) *T {
var obj T
pointer := PT(&obj)
pointer.Scan() // calling method on non-nil pointer
return &obj
}
func main() {
user := setRemovedState[User](true)
account := setRemovedState[Account](true)
fmt.Printf("User: %v\n", *user)
fmt.Printf("Account: %v\n", *account)
}
you can also try to attack the problem slighty differently, to keep it simple.
package main
import (
"fmt"
)
func mapp[T any, V any](s []T, h func(T) V) []V {
z := make([]V, len(s))
for i, v := range s {
z[i] = h(v)
}
return z
}
func mappp[T any, V any](s []T, h func(T) V) []V {
z := make([]V, 0, len(s))
for _, v := range s {
z = append(z, h(v))
}
return z
}
// Count implements SetGetter interface
type Count struct {
x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int { return c.x }
func FromInt(x int) *Count {
var out Count
out.x = x
return &out
}
func main() {
ints := []int{1, 2, 3, 4, 5}
sgs := mapp(ints, FromInt)
fmt.Printf("%T\n",sgs)
for _, s := range sgs {
fmt.Println(s.Get())
}
fmt.Println()
sgs = mappp(ints, FromInt)
fmt.Printf("%T\n",sgs)
for _, s := range sgs {
fmt.Println(s.Get())
}
}
https://go2goplay.golang.org/p/vzViKwiJJkZ
It is like your func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T but without the complex verbosity. It also gave me zero pain to solve.
Edit: see blackgreen's answer, which I also found later on my own while scanning through the same documentation they linked. I was going to edit this answer to update based on that, but now I don't have to. :-)
There is probably a better way—this one seems a bit clumsy—but I was able to work around this with reflect:
if reflect.TypeOf(out[0]).Kind() == reflect.Ptr {
x := reflect.ValueOf(out).Index(i)
x.Set(reflect.New(reflect.TypeOf(out[0]).Elem()))
}
I just added the above four lines to your example. The temporary variable is left over from some debug and obviously can be removed. Playground link

How can I instantiate a non-nil pointer of type argument with generic Go?

Now that type parameters are available on golang/go:master, I decided to give it a try. It seems that I'm running into a limitation I could not find in the Type Parameters Proposal. (Or I must have missed it).
I want to write a function which returns a slice of values of a generic type with the constraint of an interface type. If the passed type is an implementation with a pointer receiver, how can we instantiate it?
type SetGetter[V any] interface {
Set(V)
Get() V
}
// SetGetterSlice turns a slice of type V into a slice of type T,
// with T.Set() called for each entry in values.
func SetGetterSlice[V any, T SetGetter[V]](values []V) []T {
out := make([]T, len(values))
for i, v := range values {
out[i].Set(v) // panic if T has pointer receiver!
}
return out
}
When calling the above SetGetterSlice() function with the *Count type as T, this code will panic upon calling Set(v). (Go2go playground) To no surprise, as basically the code created a slice of nil pointers:
// Count implements SetGetter interface
type Count struct {
x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int { return c.x }
func main() {
ints := []int{1, 2, 3, 4, 5}
sgs := SetGetterSlice[int, *Count](ints)
for _, s := range sgs {
fmt.Println(s.Get())
}
}
Variations of the same problem
This ideas won't work, and I can't seem to find any simple way to instantiate the pointed value.
out[i] = new(T) will result in a compile failure, as it returns a *T where the type checker wants to see T.
Calling *new(T), compiles but will result in the same runtime panic because new(T) returns **Count in this case, where the pointer to Count is still nil.
Changing the return type to a slice of pointer to T will result in a compile failure:
func SetGetterSlice[V any, T SetGetter[V]](values []V) []*T {
out := make([]*T, len(values))
for i, v := range values {
out[i] = new(T)
out[i].Set(v) // panic if T has pointer receiver
}
return out
}
func main() {
ints := []int{1, 2, 3, 4, 5}
SetGetterSlice[int, Count](ints)
// Count does not satisfy SetGetter[V]: wrong method signature
}
Workaround
The only solution I found until now, is to require a constructor function to be passed to the generic function. But this just feels wrong and a bit tedious. Why would this be required if func F(T interface{})() []T is perfectly valid syntax?
func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T {
out := make([]T, len(values))
for i, v := range values {
out[i] = constructor()
out[i].Set(v)
}
return out
}
// ...
func main() {
ints := []int{1, 2, 3, 4, 5}
SetGetterSlice[int, *Count](ints, func() *Count { return new(Count) })
}
Summary
My questions, in order of priority:
Am I overlooking something obvious?
Is this a limitation of generics in Go and this is as good as it gets?
Is this limitation known or should I raise an issue at the Go project?
Basically you have to add one more type parameter to the constraint to make T convertible to its pointer type. In its most basic form, this technique looks like the following (with an anonymous constraint):
func Foo[T any, PT interface { *T; M() }]() {
p := PT(new(T))
p.M() // calling method on non-nil pointer
}
Playground: https://go.dev/play/p/L00tePwrDfx
Step by step solution
Your constraint SetGetter already declares a type param V, so we slightly modify the example above:
// V is your original type param
// T is the additional helper param
type SetGetter[V any, T any] interface {
Set(V)
Get() V
*T
}
Then you define the SetGetterSlice function with the type parameter T any, whose purpose is just to instantiate the constraint SetGetter.
You will then be able to convert the expression &out[i] to the pointer type, and successfully call the method on the pointer receiver:
// T is the type with methods with pointer receiver
// PT is the SetGetter constraint with *T
func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
out := make([]T, len(values))
for i, v := range values {
// out[i] has type T
// &out[i] has type *T
// PT constraint includes *T
p := PT(&out[i]) // valid conversion!
p.Set(v) // calling with non-nil pointer receiver
}
return out
}
Full program:
package main
import (
"fmt"
)
type SetGetter[V any, T any] interface {
Set(V)
Get() V
*T
}
func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
out := make([]T, len(values))
for i, v := range values {
p := PT(&out[i])
p.Set(v)
}
return out
}
// Count implements SetGetter interface
type Count struct {
x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int { return c.x }
func main() {
ints := []int{1, 2, 3, 4, 5}
// instantiate with base type
sgs := SetGetterSlice[int, Count](ints)
for _, s := range sgs {
fmt.Println(s.Get()) // prints 1,2,3,4,5 each in a newline
}
}
This becomes more verbose because SetGetterSlice now requires three type parameters: the original V plus T (the type with pointer receivers) and PT (the new constraint). However when you call the function, you can omit the third one – with type inference, both type params V and T required to instantiate PT SetGetter[V,T] are already known:
SetGetterSlice[int, Count](ints)
Playground: https://go.dev/play/p/gcQZnw07Wp3
Spent a few hours to understand it.
So decided to add my example.
package main
import (
"fmt"
)
type User struct {
FullName string
Removed bool
}
type Account struct {
Name string
Removed bool
}
type Scanner[T User | Account] interface {
Scan()
*T
}
type Model interface {
User | Account
}
func (user *User) Scan() {
user.FullName = `changed in scan method`
user.Removed = true
}
func (account *Account) Scan() {
account.Name = `changed in scan method`
account.Removed = true
}
func setRemovedState[T Model, PT Scanner[T]](state bool) *T {
var obj T
pointer := PT(&obj)
pointer.Scan() // calling method on non-nil pointer
return &obj
}
func main() {
user := setRemovedState[User](true)
account := setRemovedState[Account](true)
fmt.Printf("User: %v\n", *user)
fmt.Printf("Account: %v\n", *account)
}
you can also try to attack the problem slighty differently, to keep it simple.
package main
import (
"fmt"
)
func mapp[T any, V any](s []T, h func(T) V) []V {
z := make([]V, len(s))
for i, v := range s {
z[i] = h(v)
}
return z
}
func mappp[T any, V any](s []T, h func(T) V) []V {
z := make([]V, 0, len(s))
for _, v := range s {
z = append(z, h(v))
}
return z
}
// Count implements SetGetter interface
type Count struct {
x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int { return c.x }
func FromInt(x int) *Count {
var out Count
out.x = x
return &out
}
func main() {
ints := []int{1, 2, 3, 4, 5}
sgs := mapp(ints, FromInt)
fmt.Printf("%T\n",sgs)
for _, s := range sgs {
fmt.Println(s.Get())
}
fmt.Println()
sgs = mappp(ints, FromInt)
fmt.Printf("%T\n",sgs)
for _, s := range sgs {
fmt.Println(s.Get())
}
}
https://go2goplay.golang.org/p/vzViKwiJJkZ
It is like your func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T but without the complex verbosity. It also gave me zero pain to solve.
Edit: see blackgreen's answer, which I also found later on my own while scanning through the same documentation they linked. I was going to edit this answer to update based on that, but now I don't have to. :-)
There is probably a better way—this one seems a bit clumsy—but I was able to work around this with reflect:
if reflect.TypeOf(out[0]).Kind() == reflect.Ptr {
x := reflect.ValueOf(out).Index(i)
x.Set(reflect.New(reflect.TypeOf(out[0]).Elem()))
}
I just added the above four lines to your example. The temporary variable is left over from some debug and obviously can be removed. Playground link

Convert type int to a string of the name of a const when marshaling to JSON

I have a struct that looks like this:
type Type int
const (
A Type = iota
B
C
)
type Character struct {
Type Type `json:"type"`
}
When I call json.Marshal(...) on the struct, is there a way that the json:"type" representation is a string called either "A", "B", or "C"?
When this is presented in JSON, nobody is going to know what 0, 1, or 2 is, so the name of the constant is more useful.
Apologies if this has been asked before. I googled all over and couldn't find anything.
Here is an example:
type Type int
const (
A Type = iota
B
C
)
type Character struct {
Type Type `json:"type,string"`
}
func main() {
c := Character{}
c.Type = A
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
fmt.Println(string(j))
}
I want fmt.Println(string(j)) to print {"type":"A"}, not {"type":0}.
You need to define a custom marshaller for your type.
type Type int
const (
A Type = iota
B
C
)
var typeToString = map[Type]string{
A: "A",
B: "B",
C: "C",
}
func (t Type) MarshalJSON() ([]byte, error) {
return json.Marshal(typeToString[t])
}
type Character struct {
Type Type `json:"type"`
}
func main() {
c := Character{}
c.Type = A
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
fmt.Println(string(j))
}
The function MarshalJSON defines how json.Marshal should marshal your type. You can do something similar for unmarshaling if you need to go the other direction as well.
See https://play.golang.org/p/mLxThWA19by.
Short reply: there's NO direct option to achieve your goal.
Long reply: truth is, you can actually override the way Go can encode structs.
Here's the working code:
https://play.golang.org/p/i92pUpNG-Wr
package main
import (
"encoding/json"
"fmt"
)
// please consider to rename this!
type Type int
const (
A Type = iota
B
C
)
type Character struct {
Type Type `json:"-"`
}
func (c *Character) mapTypeToString() string {
switch c.Type {
case B:
return "B"
case C:
return "C"
}
// defaults on A
return "A"
}
// MarshalJSON overwrites the standard JSON marshal.
func (c *Character) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Type string `json:"type"`
}{
Type: c.mapTypeToString(),
})
}
func main() {
c := &Character{}
c.Type = A
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
fmt.Println(string(j))
}
Be aware of the c := &Character{} initialization.
The tradeoff would be to have the mapTypeToString() method that needs to be updated according to the different types of ... Type.
Please, also consider to avoid naming structs and vars like Type :)
So your JSON API gives you the type A:
I want fmt.Println(string(j)) to print {"type":"A"}, not {"type":0}.
You could change your code like this, then your API works:
https://play.golang.org/p/ypvFvQpBw-C
type JSONType string
const (
A JSONType = "A"
B JSONType = "B"
C JSONType = "C"
)
type Character struct {
JSONType JSONType `json:"type,string"`
}
func main() {
c := Character{}
c.JSONType = A
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
fmt.Println(string(j))
}

How do I apply my custom UnmarshalJSON method to an embedded struct?

So, I have struct P. I need to unmarshal some json data into P but sometimes it comes embedded struct, Embedded. In either case, I unmarshal the json from the API and need to format the "Formatted" field. It seems in the Embedded case my unmarshaller doesn't get called.
I have the following code:
package main
import (
"encoding/json"
"fmt"
)
type P struct {
Name string `json:"name"`
Formatted string `json:"formatted"`
}
type Embedded struct {
A struct {
B struct {
*P
} `json:"b"`
} `json:"a"`
}
func (p *P) UnmarshalJSON(b []byte) error {
type Alias P
a := &struct {
*Alias
}{
Alias: (*Alias)(p),
}
if err := json.Unmarshal(b, &a); err != nil {
return err
}
a.Formatted = fmt.Sprintf("Hi, my name is %v", a.Name)
return nil
}
func simple() {
b := []byte(`{"name":"bob"}`)
p := &P{}
if err := json.Unmarshal(b, &p); err != nil {
panic(err)
}
fmt.Printf("normal: %+v\n", p)
}
func embedded() {
b := []byte(`{"a":{"b":{"name":"bob"}}}`)
e := &Embedded{}
if err := json.Unmarshal(b, &e); err != nil {
panic(err)
}
fmt.Printf("embedded: %+v\n", e.A.B.P)
}
func main() {
simple()
embedded()
}
(I realize I can get rid of the custom unmarshaller and create a method to format the name but wanted to see if this way was possible.)
I don't know enough to explain all the reasons, I will just list what works and what doesn't. Someone more knowledgeable can fill you in on the reasons behind it.
The following works when B is a *struct, not sure why.
type Embedded struct {
A struct {
B *struct {
P
} `json:"b"`
} `json:"a"`
}
The following also works. I'm guessing that using an anonymous struct had some effect in the last one since a *struct is not required here.
type embedP struct {
P
}
type Embedded struct {
A struct {
B embedP `json:"b"`
} `json:"a"`
}
The following works if *P is initialised.
type embedP struct {
*P
}
type intermediate struct {
B embedP `json:"b"`
}
type Embedded struct {
A intermediate `json:"a"`
}
e := &Embedded{A:intermediate{embedP{P:&P{}}}}
But the same thing doesn't work with anonymous structs.
type Embedded struct {
A struct {
B struct {
*P
} `json:"b"`
} `json:"a"`
}
e := &Embedded{A : struct{B struct{*P}`json:"b"`}{B: struct{*P}{&P{}}}}
Play link
Other improvements
If p := &P{} is already a pointer you don't need to pass &p in json.Unmarshal. json.Unmarshal(b, p) would suffice. Same with e := &Embedded{}.
To extent #John's answer, take a look at the source code of json decoder, especially method indirect(v reflect.Value, decodingNull bool) line 442-483.
// indirect walks down v allocating pointers as needed,
// until it gets to a non-pointer.
// if it encounters an Unmarshaler, indirect stops and returns that.
// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
The method returns, json.Unmarshaler, encoding.TextUnmarshaler and the value of v. In current implementation, inside the method, basically the following steps were executed
If argument v is not a pointer, it will return immediately without checking whether v implements json.Unmarshaler/encoding.TextUnmarshaler or not. The method assigns nil for both unmarshaller regardless B implements custom unmarshaller or not.
If argument v is a pointer, it will check whether v implements json.Unmarshaler/encoding.TextUnmarshaler or not. In this case, if v is nil, a new value will be assigned to v.
If Embedded is defined as
type Embedded struct {
A struct {
B struct {
*P
} `json:"b"`
} `json:"a"`
}
when, decoding "b":{"name":"bob"} to field B, since B is not a pointer, (1) is applicable. As the result, custom unmarshaller is returned as nil, thus never being called. The json decoder uses default unmarshaller to decode json value to B's fields.
If Embedded is defined as
type Embedded struct {
A struct {
*B struct {
P
} `json:"b"`
} `json:"a"`
}
since field B is a pointer, (2) is applicable. The decoder allocates new struct{*P} to B, detects that B implements custom unmarshaller, then call it as expected. The following declaration
type Embedded struct {
A struct {
*B struct {
*P
} `json:"b"`
} `json:"a"`
}
also works, if P is preallocated, i.e.
//...
e := Embedded{}
e.A.B = &struct{ *P }{P: &P{}}
//...
If it's not preallocated, in (2) the decoder will assign &struct{*P}{} to B, then call the custom unmarshaller with B.P == nil. As the result, json value can't be captured by B.P during unmarshall.
Note:
I'm not sure whether it is desired behavior or not, and I can't find a clear documentation related to embedded struct.

Resources