Home > front end >  Unmarshal slice of structs with interfaces
Unmarshal slice of structs with interfaces

Time:03-23

I have a very interesting case, let's say we have this struct

type Test struct {
 Field1 string `json:"field1"`
 Field2 ABC `json:"abc"`
}

type ABC interface {
  Rest()
}

Unmarshalling this struct is not a problem, you could just point to the right struct which implements the interface, unless you have []Test

Is there a way to unmarshall slice of structs when one of the field is interface?

Thanks

CodePudding user response:

You need to implement Unmarshaler interface to Test,

Then in UnmarshalJSON func you need to check it one by one (line 45-55 in the example)

Luckily there is now generic, this is the example :

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

type Test struct {
    Field1 string `json:"field1"`
    Field2 ABC    `json:"abc"`
}

type ABCImplements interface {
    A | B
}

func UnmarshalABC[T ABCImplements](b []byte) (T, error) {
    var x T
    err := json.Unmarshal(b, &x)
    if err != nil {
        return x, err
    }

    rv := reflect.ValueOf(x)
    if rv.IsZero() {
        return x, fmt.Errorf("T is zero value")
    }

    return x, nil
}

func (m *Test) UnmarshalJSON(data []byte) error {
    temp := make(map[string]interface{}, 2)
    err := json.Unmarshal(data, &temp)
    if err != nil {
        return err
    }
    m.Field1 = temp["field1"].(string)
    b, err := json.Marshal(temp["abc"])
    if err != nil {
        return err
    }

    xB, err := UnmarshalABC[B](b)
    if err == nil {
        m.Field2 = xB
        return nil
    }

    xA, err := UnmarshalABC[A](b)
    if err == nil {
        m.Field2 = &xA
        return nil
    }

    return nil
}

type A struct {
    B string `json:"b"`
}

func (a *A) Rest() {
    fmt.Println(a.B)
}

type B struct {
    C string `json:"c"`
}

func (b B) Rest() {
    fmt.Println(b.C)
}

type ABC interface {
    Rest()
}

func main() {
    a := &A{"coba"}
    t := Test{"oke", a}

    arrT := []Test{t, t, t}

    b, err := json.Marshal(arrT)
    if err != nil {
        panic(err)
    }

    var xT []Test
    err = json.Unmarshal(b, &xT)

    fmt.Printf("%#v\n", xT)
    fmt.Println(xT[1].Field2)
}

playground

CodePudding user response:

Use the following code to Unmarshal the interface field to a specific concrete type. See the commentary for more details:

// Example is the concrete type.
type Example struct{ Hello string }
func (e *Example) Rest() {}

// Implement the json.Unmarshaler interface to control how
// values of type Test are unmarsheled.  
func (m *Test) UnmarshalJSON(data []byte) error {
    // Setup fields as needed.
    m.Field2 = &Example{}

    // We cannot call json.Unmarshal(data, m) to do the work
    // because the json.Unmarshal will recurse back to this 
    // function. To prevent the recursion, we declare a new 
    // type with the same field layout as Test, but no methods:
    type t Test

    // Convert receiver m to a t* and unmarshal using that pointer.
    v := (*t)(m)

    return json.Unmarshal(data, v)
}

Run the code on the playground.

  • Related