将类型转换为结构的编组流

I'm currently dealing with a stream of json objects coming in to my application, and am having some difficulties figuring out what the best way of parsing them is. The stream consists of objects that have a defined type. The problem is that one of the fields in the object is of changing type. It looks like this:

[{
  "status": "closed",
  "type": "transaction",
  "transaction": {
    "TransactionType": "TypeA",
    "Account": "Some string",
    "Fee": "14",
    "date": 45325680
  },
  "validated": true
},

{
  "status": "closed",
  "type": "transaction",
  "transaction": {
    "TransactionType" : "TypeB",
    "Account" : "Some string",
    "Fee": "42",
    "Destination" : "Some string"
  },
  "validated": true
}]

You can see that the "parent" does not change, but the "transaction" does. I removed a lot of fields from the "transaction" field to make it easier to explain, but this is a type with 10-ish common fields and 10-ish changing fields that are dependent on the type. There are also 10 transaction types, this makes it pretty annoying to put everything into one struct and have a ton of optional fields. I was also thinking about one struct for every transaction type, but this does not work because then there is no way of specifying which type the field should have in the parent.

How would I parse these objects effectively? Unfortunately, I can't change the structure of the elements coming out of the stream. What would be the best way to solve this?

Perhaps keep your transaction as map[string]interface{}

Example https://play.golang.com/p/j_u_ztw04M

package main

import (
    "encoding/json"
    "fmt"
)

type Stream []struct {
    Status string `json:"status"`
    Type   string `json:"type"`

    // map instead of struct
    Transaction map[string]interface{} `json:"transaction"`

    Validated bool `json:"validated"`
}

func main() {

    stream := Stream{}

    err := json.Unmarshal(rawj, &stream)
    if err != nil {
        fmt.Println("error:", err)
        return
    }

    for i, s := range stream {
        fmt.Println("thing:", i)
        for k, v := range s.Transaction {

            // do manual stuff here, perhaps long switch on k and/or
            // as suggested by Cerise Limón type switch on v
            fmt.Printf("key: %s, val: %v, val type: %T
", k, v, v)

        }
        fmt.Println()
    }

}

var rawj = []byte(`[{
  "status": "closed",
  "type": "transaction",
  "transaction": {
    "TransactionType": "TypeA",
    "Account": "Some string",
    "Fee": "14",
    "date": 45325680
  },
  "validated": true
},

{
  "status": "closed",
  "type": "transaction",
  "transaction": {
    "TransactionType" : "TypeB",
    "Account" : "Some string",
    "Fee": "42",
    "Destination" : "Some string"
  },
  "validated": true
}]`)

Have fun!

For the "transaction" struct I would make the common keys direct types (e.g. string, int, etc.) and the optional keys pointers to their types (*string, *int, etc.), with the "omitempty" tag so they are set to nil if they aren't present in an occurrence:

type Transaction struct {
  // Common fields are direct types...
  TransactionType string
  Account         string
  Fee             string

  // Optional fields are pointers with "omitempty"...
  Date        *int    `json:",omitempty"`
  Destination *string `json:",omitempty"`
}

This way the common fields are always there but possibly the default (empty) values and optional fields are populated if present but nil if omitted:

ts[0].Date = (*int) 45325680
ts[0].Destination = nil
ts[1].Date = nil
ts[1].Destination = (*string) "Some string"

Define structs for each transaction type and do the un-marshall in two steps. First use

type Stream []struct {
    ...
    TransactionRaw json.RawMessage `json:"transaction"`
    ...
}

Then use Regexp.FindStringSubmatch on TransactionRaw to determine the type for the second call to json.Unmarshal with

type Transaction struct {
    TransactionType string `json:"TransactionType"`
    Account         string `json:"Account"`
    Fee             string `json:"Fee"`
}

type TypeA struct {
    Transaction
    Date int    `json:"date"`
}

etc...

Playground example here

I think using reflect is one way []map[string]interface{}

And in this answer I had gave an example about using reflect to decode changing type.