json unmarshal embedded struct
Asked Answered
P

5

16

I would like to unmarshal to struct Outer defined as:

type Outer struct {
    Inner
    Num int
}

type Inner struct {
    Data string
}
func (i *Inner) UnmarshalJSON(data []byte) error {
    i.Data = string(data)
    return nil
}

Using json.Unmarshal(data, &Outer{}) seems only to use Inner's UnmarshalJSON and ignores the Num field: https://play.golang.org/p/WUBfzpheMl

I have an unwieldy solution where I set the Num field manually, but I was wondering if anybody had a cleaner or simpler way to do it.

Thanks!

Planetesimal answered 16/8, 2016 at 18:52 Comment(2)
Note that the reason is explained in this part of the Go language reference: golang.org/ref/spec#Struct_types (look for word "promoted")Enriqueenriqueta
This answer explains this interesting interaction better and more completely.Tomokotomorrow
T
3

This is happening because Inner is being embedded in Outer. That means when json library calls unmarshaler on Outer, it instead ends up calling it on Inner.

Therefore, inside func (i *Inner) UnmarshalJSON(data []byte), the data argument contains the entire json string, which you are then processing for Inner only.

You can fix this by making Inner explicit field in Outer

Outer struct {
    I Inner // make Inner an explicit field
    Num int `json:"Num"`
}

Working example

Turk answered 16/8, 2016 at 19:1 Comment(4)
Thanks for the explanation!Planetesimal
Sure, this solves the problem of not unmarshaling Num, however, it doesn't work with embedded data such as {"data": "test", "num": 1}, it would only work with something like {"inner": {"data": "test"}, "num": 1}.Zoes
Agreed with Eric. I think this answer is only barely applicable in this case but in general case it's misleading.Counsel
This is a misleading answer. I understand that the question itself is a bit unclear, but the proposed solution has nothing to do with embedded type.Crossgarnet
E
3

One way to do this is to forgo the custom UnmarshalJSON function entirely and just use the basic JSON notation, i.e.:

type Outer struct {
    Inner
    Num int `json:"num"`
}

type Inner struct {
   Data string `json:"data"`
}

You lose some of the more granular capabilities you could have with a custom unmarshalling method, but when you're unmarshalling a struct with mostly primitive fields like strings, you really don't need to worry about that.

Example in go playground

If you really need custom unmarshalling, you could use composition, and give the struct a custom json encoding tag and have the struct contain the fields you want to play with. So if data is something that will contain multiple complex fields, you could just change Inner to reflect those fields, like this:

type Outer struct {
    Data Inner `json:"data"`
    Num int `json:"num"`
}

type Inner struct {
    Thing string `json:"thing"`
    OtherThing int `json:"otherThing"`
}

Example in go playground

Once again, this doesn't have a custom unmarshalling function, but that can be scraped together for Inner easily enough. (Personally, I'd forgo the use of custom unmarshal functions entirely in any given case and just use encoding tags unless I absolutely had to use the unmarshalling function.)

Ermina answered 16/4, 2021 at 14:58 Comment(0)
L
2

Faced same issue. One solution is to unmarshal twice for each sub structs. E.g.

type Inner struct {
        Data string
}

type NumField struct {
        Num int
}

type Outer struct {
        Inner
        NumField
}

func (i *Inner) UnmarshalJSON(data []byte) error {
        i.Data = string(data)
        return nil
}

func (o *Outer) UnmarshalJSON(data []byte) error {
        if err := json.Unmarshal(data, &o.Inner); err != nil {
                return err
        }
        if err := json.Unmarshal(data, &o.NumField); err != nil {
                return err
        }
        return nil
}

func main() {
        x := Outer{}
        data := []byte(`{"Num": 4}`)
        _ = json.Unmarshal(data, &x)
        fmt.Printf("%#v\n", x)
}

This requires moving the extra fields into a separate struct.

Lattimore answered 29/5, 2022 at 20:51 Comment(1)
Same idea. Explained better: stackoverflow.com/a/52305457Lattimore
C
1

Just delete UnmarshalJSON in your example because it's used in unmarshaling of Outer since Inner is inlined. Otherwise, you need to override it if you want to do something custom.

https://play.golang.org/p/D6V6vKpx9J

Cowell answered 16/8, 2016 at 19:1 Comment(0)
L
-1

Actually you not need explicit field, you need properly Marshal/UnMarshal

Example: https://play.golang.org/p/mWPM7m44wfK

package main

import (
    "encoding/json"
    "fmt"
)

type Outer struct {
    Inner
    Num int `json:"Num"`
}

type Inner struct{ Data string }

type InnerRaw struct {Data string}

func (i *Inner) UnmarshalJSON(data []byte) error {
    ir:=&InnerRaw{}
    json.Unmarshal(data, ir)
    i.Data = ir.Data
    return nil
}

func main() {
    x := Outer{}
    data := []byte(`{"Num": 4, "Data":"234"}`)
    _ = json.Unmarshal(data, &x)
    fmt.Printf("%+v\n", x)
    js, _:=json.Marshal(x)
    fmt.Printf("JSON:%s", string(js))
}
Lodhia answered 11/2, 2019 at 4:33 Comment(3)
But Num is still 0, even though it should be 4Aseity
Hmm, yes we can remove InnerRaw struct and UnmarshallJSON function. Result: play.golang.org/p/JkKCLQOnsHp and it works fineLodhia
The OP wants to parse Num when there is UnmarshalJSON for Inner. If we remove UnmarshalJSON, then it wouldn't answer the questionAseity

© 2022 - 2024 — McMap. All rights reserved.