4

I have some JSON that I am unmarshalling into various structs so that I can then process the data, seemingly this is turning into the hardest part of the project!!!

The format of this JSON is that if the field is missing then it is essentially nil. This is following on from Default struct values but thought it deserved it's own question on SO.

A zero therefore is a valid value and I need to be able to discern this in my Go code. Is there a way to get Go to unmarshal into this struct with pointers at all?

In the example playground you can see what I mean, it "appears" to work but when I come to print out one of the pointer values it always prints the pointer address and not the actual value.

package main

import "fmt"
import "log"
import "encoding/json"

const inputMissing = `
["AAAAAA", {"testCode" : "Sss"}, 123456]
`

const inputZero = `
["AAAAAA", {"testCode" : "Sss", "missingInt" : 0, "defaultInt" : 0,"missingString" : "", "defaultString" : ""}, 123456]
`

type RawMessage struct {
    AlwaysString  string
    ClientData    ClientData
    ReceptionTime int
}

type ClientData struct {
    TestCode   string
    MissingInt *int
    DefaultInt int

    MissingString *string
    DefaultString string
}

func main() {

    var n RawMessage
    if err := json.Unmarshal([]byte(inputMissing), &n); err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%#v\n", n)

    var o RawMessage
    if err := json.Unmarshal([]byte(inputZero), &o); err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%#v\n", o)

    fmt.Printf("Should print the value of the int, not pointer... %i", o.ClientData.MissingInt)
}

func (n *RawMessage) UnmarshalJSON(buf []byte) error {
    tmp := []interface{}{&n.AlwaysString, &n.ClientData, &n.ReceptionTime}
    wantLen := len(tmp)
    if err := json.Unmarshal(buf, &tmp); err != nil {
        return err
    }
    if g, e := len(tmp), wantLen; g != e {
        return fmt.Errorf("wrong number of fields in RawMessage: %d != %d", g, e)
    }
    return nil
}
Community
  • 1
  • 1
Lee Armstrong
  • 11,420
  • 15
  • 74
  • 122

2 Answers2

4

Your code is correct. Test the pointer against nil and dereference the pointer if you want the value:

fmt.Printf("Should print the value of the int, not pointer... %d", *o.ClientData.MissingInt)
Franck Jeannin
  • 6,107
  • 3
  • 21
  • 33
1

Your confusion arises from the default formatting of the fmt package, which prints the hex value in case of pointer fields, and not the pointed value.

If you're using the %#v verb for printing, you may implement the fmt.GoStringer interface on your struct to override this "behavior":

func (c ClientData) GoString() string {
    mi := "<missing>"
    if c.MissingInt != nil {
        mi = strconv.Itoa(*c.MissingInt)
    }
    ms := "<missing>"
    if c.MissingString != nil {
        ms = *c.MissingString
    }

    return fmt.Sprintf("{TestCode: %s, MissingInt: %s, DefaultInt: %d, MissingString: %s, DefaultString: %s}",
        c.TestCode, mi, c.DefaultInt, ms, c.DefaultString)
}

And changing the last printing line to:

fmt.Printf("Should print the value of the int, not pointer... %d",
    *o.ClientData.MissingInt)

Your output becomes more readable (try it on the Go Playground):

main.RawMessage{AlwaysString:"AAAAAA", ClientData:{TestCode: Sss, MissingInt: <missing>, DefaultInt: 0, MissingString: <missing>, DefaultString: }, ReceptionTime:123456}
main.RawMessage{AlwaysString:"AAAAAA", ClientData:{TestCode: Sss, MissingInt: 0, DefaultInt: 0, MissingString: , DefaultString: }, ReceptionTime:123456}
Should print the value of the int, not pointer... 0

As you can see from the output above, the MissingXX fields have the static value <missing> displayed.

Note: If you were to use the %v verb, you'd have to implement the fmt.Stringer interface, which is basically the same, just the method name is not GoString() but simply String().

icza
  • 389,944
  • 63
  • 907
  • 827