Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

json.Unmarshal() accepts a pointer to a pointer

I noticed, quite by accident, that I can successfully pass both a pointer to a struct, and a pointer to a pointer to a struct to json.Unmarshal(), and both work just fine:

package main

import (
    "testing"
    "encoding/json"
)

type Person struct {
    Name string
    Age  int
}

func TestMarshaling(t *testing.T) {
    foo := &Person{Name: "bob", Age: 23}

    // marshal it to bytes
    b, err := json.Marshal(foo)
    if err != nil {
        t.Error(err)
    }

    bar := &Person{}             // pointer to new, empty struct
    err = json.Unmarshal(b, bar) // unmarshal to bar, which is a *Person
    if err != nil {
        t.Error(err)
    }
    testBob(t, bar)  // ok

    bar = &Person{}               // pointer to new, empty struct
    err = json.Unmarshal(b, &bar) // wait a minute, passing in a **Person, yet it still works?
    if err != nil {
        t.Error(err)
    }
    testBob(t, bar) // ok
}

func testBob(t *testing.T, person *Person) {
    if person.Name != "bob" || person.Age != 23 {
        t.Error("not equal")
    }
}

I was really surprised that the second one (unmarshal to **Person) worked.

What's going on in json.Unmarshal()? Is it dereferencing the pointers until it finds a struct?

The documentation offers:

To unmarshal JSON into a pointer, Unmarshal first handles the case of the JSON being the JSON literal null. In that case, Unmarshal sets the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into the value pointed at by the pointer

It seems to be doing a bit more than that. What's really going on?

Fleshing out my question more: how does it know to automatically dereference my pointer to a pointer? The documentation says it will unmarshal "into the value pointed at by the pointer". Since the value of my pointer is in fact another pointer, and has no Name/Age fields, I expected it to stop there.

To be clear: I'm not saying there's a bug or misfeature in Unmarshal(); I'm trying to satisfy my astonishment that it works at all when given a ptr-to-ptr, and avoid any potential pitfalls in my use of it.

like image 302
George Armhold Avatar asked Feb 24 '16 14:02

George Armhold


People also ask

How does JSON Unmarshal work?

To unmarshal a JSON array into a slice, Unmarshal resets the slice length to zero and then appends each element to the slice. As a special case, to unmarshal an empty JSON array into a slice, Unmarshal replaces the slice with a new empty slice.

What does Unmarshal do in Golang?

Golang Unmarshal It allows you to convert byte data into the original data structure. In go, unmarshaling is handled by the json. Unmarshal() method.

What is JSON Marshal Unmarshal?

Generally, encoding/decoding JSON refers to the process of actually reading/writing the character data to a string or binary form. Marshaling/Unmarshaling refers to the process of mapping JSON types from and to Go data types and primitives.


1 Answers

The json package has no reason to "stop at a pointer", since a pointer means nothing in json. It has to keep walking the tree in order to find a value to write. Since the json package is going to allow unmarshaling the same value into Type or *Type, it stands to reason that it should be able to unmarshal that into **Type, which is also a valid type in Go.

For a example, if Person were defined using pointers to differentiate between nil and zero values, and you were unmarshaling into a slice of []*Person, the json package needs to follow those pointers, and allocate values if necessary. The same applies if a field in Person were defined as a **string.

type Person struct {
    Name **string
    Age  *int
}

type People []*Person

http://play.golang.org/p/vLq0nJPG5M

like image 199
JimB Avatar answered Oct 12 '22 09:10

JimB