0

I have a stream of objects of different but known, types and I'm struggling to figure out how to unmarshal the JSON encoded stream into Go structs.

Here is a sample slice for illustration purpose

[
    {
        "uid": "xyz1",
        "type": "person",
        "name": "John",
        "surname": "King"
    },
    {
        "uid": "xyz2",
        "type": "thing",
        "car": "benz",
        "shoes": "nike"
    },
    {
        "uid": "xyz3",
        "type": "person",
        "name": "Mary",
        "surname": "Queen"
    }
]

As you can see there is a type field that informs the receiver about what is the type of the object.

Data maps into the following Go types in the stream which "share" UID and Type fields:

type Person struct {
    UID     string
    Type    string
    Name    string
    Surname string
}

type Thing struct {
    UID   string
    Type  string
    Car   string
    Shoes string
}

I found a similar question here, though unanswered, it's a bit different from what I need to deal with. Instead of being guaranteed a transaction keyspace as I've no guarantee what the payload should look like besides the type and uid fields.

Is there some way to this other than drowning deeply in reflect package?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
milosgajdos
  • 851
  • 1
  • 14
  • 32
  • The *easiest* path forward is probably to unmarshal into a `[]map[string]interface{}`, inspect each item for its `type`, and then use [`mapstructure`](github.com/mitchellh/mapstructure) to convert the map into the appropriate struct. – Adrian Feb 25 '21 at 19:17
  • I will have a look at that thanks @Adrian. Before I took a deep breath someone managed to downvote my question again :D Classic. – milosgajdos Feb 25 '21 at 19:28
  • 1
    Possible [duplicate](https://stackoverflow.com/questions/53453782/unmarshal-dynamic-json-based-on-a-type-key). – mkopriva Feb 25 '21 at 19:52
  • 1
    «Before I took a deep breath someone managed to downvote my question again :D Classic.» your question lacks any display of your attempted solution. That is, you have formulated a question, hinted at that you have googled for a solution but appear to have ended up empty-handed, and posted a question on SO instead of trying to solve the problem. This is classic to no lesser a degree than downvoting such questions so you may consider taking no offence about that. – kostix Feb 25 '21 at 19:57
  • 1
    FWIW I did not downvote, and I'm with Adrian on this (just in your case it appears you can unmarshal to `[]map[string]string` which is more effective and easy to wield). – kostix Feb 25 '21 at 20:00
  • I have made more than one attempt at solving this, ultimately ending up neck-deep in `reflect` package which is what I'm currently doing. I've asked whether there is a better less reflect-y way. – milosgajdos Feb 25 '21 at 20:18

1 Answers1

1

Unmarshal to a slice of raw messages:

var elems []json.RawMessage
err := json.Unmarshal(data, &elems)
if err != nil {
    log.Fatal(err)
}

For each element in the slice, unmarshal once to find the type and a second time to the specific type:

for _, elem := range elems {

    // Pick out the type field.

    var t struct {
        Type string
    }
    err := json.Unmarshal(elem, &t)
    if err != nil {
        log.Fatal(err)
    }

    // Decode to specific type based on value of the type field.

    switch t.Type {
    case "person":
        var p Person
        err := json.Unmarshal(elem, &p)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%#v\n", p)
    case "thing":
        var t Thing
        err := json.Unmarshal(elem, &t)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%#v\n", t)
    default:
        fmt.Printf("unknown type %s\n", t.Type)
    }

}

Run it on the Go playground.