I am implementing some methods for serializing objects (which probably already exists in the standard library, but I want the experience). I was able to get both json.Marshal and json.Unmarshal to correctly convert my data to a string and then read back into an object, but I noticed some interesting behavior that I want to understand better.
To test the functions of the json package, I created a test struct:
type Test struct{
T int
S string
F float32
}
Running
o := Test{32, "struct", 12.07}
b, e := json.Marshal(o)
fmt.Println(string(b), e)
prints
{"T":32,"S":"struct","F":12.07} <nil>
which is what I would expect. However, I am able to get two different results when unmarshalling, depending on when I convert an object to a pointer:
// Test 1
var o2 interface{} = &o
e = json.Unmarshal(b, o2)
fmt.Println(o2, e)
prints
// Output 1
&{32 struct 12.07} <nil>
while defining o2 as a value rather than a pointer and then calling Unmarshal with &o2, ie.
// Test 2
var o2 interface{} = o
e = json.Unmarshal(b, &o2)
fmt.Println(o2, e)
prints
// Output 2
map[T:32 S:struct F:12.07] <nil>
Interestingly enough, all four of
// Test 3
var o2 Test = o
e = json.Unmarshal(b, &o2)
fmt.Println(o2, e)
,
// Test 4
var o2 *Test = &o
e = json.Unmarshal(b, &o2)
fmt.Println(*o2, e)
,
// Test 5
var o2 *Test = &o
e = json.Unmarshal(b, o2)
fmt.Println(o2, e)
, and
// Test 6
var o2 *Test = &o
e = json.Unmarshal(b, &o2)
fmt.Println(o2, e)
print one of the following:
// Output 3, 4
{32 struct 12.07} <nil>
// Output 5, 6
&{32 struct 12.07} <nil>
Tests 1 and 2 lead me to believe that some runtime information is lost when something is assigned to a "superclass"... it appears that assigning a pointer to my struct to o2 gives the Unmarshal function more type data about what it should be reading than does assigning my struct to o2 and then passing a pointer to o2 to Unmarshal. This makes a little bit of sense; at compile-time, Test 1 passes an interface{} to Unmarshal, while Test 2 passes a pointer to an interface{}. Even still, I would have thought that the runtime type of the argument would have been the same (a *Test). Can someone explain why Go works this way?
Supporting my conclusion is the fact that declaring o2 as either a Test or a *Test allows Unmarshal (which thus receives a *Test as its argument) to always read my data as a struct rather than a map.
Strangely, Test 4 and Test 6 show that passing a pointer to a pointer to my struct is totally acceptable, and Unmarshal correctly(?) sets the value of the struct. I would have expected a runtime error for those tests, as Unmarshal should have tried to dereference the double pointer and set the resulting pointer to the serialized struct it was reading. Automatic multi-pointer dereferencing might just be a feature of this function, but it wasn't mentioned in the official documentation. I'm not too concerned about these latter tests. I'm mostly just interested in what is going on to cause the difference between Test 1 and Test 2.
**Edited 14 Jan 2018 to change second json.Marshal to json.Unmarshal, remove improper brackets from copy/pasted code