我如何在切片内进行json解组切片

I am trying to unmarshal some pretty ugly json but can't figure out how. I have:

package main

import "fmt"
import "encoding/json"

type PublicKey struct {
    ID     int    `json:"id"`
    Key    string `json:"key"`
    MyData []struct {
        ID    string `json:"id"`
        Value int    `json:"value"`
    }
}

func main() {
    b := `[
  {
    "id": 1,
    "key": "my_key"
  },
  [
    {
      "id": "some_id",
      "value": 12
    },
    {
      "id": "anorther_id",
      "value": 13
    }
  ]
]`

    var pk []PublicKey
    err := json.Unmarshal([]byte(b), &pk)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(pk)

}

For the result I am getting:

[{1 my_key []} {0  []}]

The second slice is empty when it shouldn't be.

EDIT: The error I get is:

json: cannot unmarshal array into Go struct field PublicKey.key of type main.PublicKey

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

That is some truly hideous JSON! I have two approaches to handling the mixed array elements and I like the 2nd one better. Here's the first approach using interface and a type switch:

package main

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

type PublicKey struct {
    ID  int    `json:"id"`
    Key string `json:"key"`
}

type MyData struct {
    ID    string `json:"id"`
    Value int    `json:"value"`
}

type MixedData struct {
    Key    []PublicKey
    MyData [][]MyData
}

func (md *MixedData) UnmarshalJSON(b []byte) error {
    md.Key = []PublicKey{}
    md.MyData = [][]MyData{}
    var obj []interface{}
    err := json.Unmarshal([]byte(b), &obj)
    if err != nil {
        return err
    }
    for _, o := range obj {
        switch o.(type) {
        case map[string]interface{}:
            m := o.(map[string]interface{})
            id, ok := m["id"].(float64)
            if !ok {
                return errors.New("public key id must be an int")
            }
            pk := PublicKey{}
            pk.ID = int(id)
            pk.Key, ok = m["key"].(string)
            if !ok {
                return errors.New("public key key must be a string")
            }
            md.Key = append(md.Key, pk)
        case []interface{}:
            a := o.([]interface{})
            myData := make([]MyData, len(a))
            for i, x := range a {
                m, ok := x.(map[string]interface{})
                if !ok {
                    return errors.New("data array contains unexpected object")
                }
                val, ok := m["value"].(float64)
                if !ok {
                    return errors.New("data value must be an int")
                }
                myData[i].Value = int(val)
                myData[i].ID, ok = m["id"].(string)
                if !ok {
                    return errors.New("data id must be a string")
                }
                md.MyData = append(md.MyData, myData)
            }
        default:
            // got something unexpected, handle somehow
        }
    }
    return nil
}

func main() {
    b := `[
  {
    "id": 1,
    "key": "my_key"
  },
  [
    {
      "id": "some_id",
      "value": 12
    },
    {
      "id": "another_id",
      "value": 13
    }
  ]
]`

    m := MixedData{}
    err := json.Unmarshal([]byte(b), &m)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(m)

}

https://play.golang.org/p/g8d_AsH-pYY

Hopefully there aren't any unexpected other elements, but they can be handled similarly.

Here is the second that relies more on Go's internal JSON parsing with the help of json.RawMessage. It makes the same assumptions about the contents of the array. It assumes that any objects will Unmarshal into PublicKey instances and any arrays consist of only MyData instances. I also added how to marshal back into the target JSON for symmetry:

package main

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

type PublicKey struct {
    ID  int    `json:"id"`
    Key string `json:"key"`
}

type MyData struct {
    ID    string `json:"id"`
    Value int    `json:"value"`
}

type MixedData struct {
    Keys   []PublicKey
    MyData [][]MyData
}

func (md *MixedData) UnmarshalJSON(b []byte) error {
    md.Keys = []PublicKey{}
    md.MyData = [][]MyData{}
    obj := []json.RawMessage{}
    err := json.Unmarshal([]byte(b), &obj)
    if err != nil {
        return err
    }
    for _, o := range obj {
        switch o[0] {
        case '{':
            pk := PublicKey{}
            err := json.Unmarshal(o, &pk)
            if err != nil {
                return err
            }
            md.Keys = append(md.Keys, pk)
        case '[':
            myData := []MyData{}
            err := json.Unmarshal(o, &myData)
            if err != nil {
                return err
            }
            md.MyData = append(md.MyData, myData)
        default:
            // got something unexpected, handle somehow
        }
    }
    return nil
}

func (md *MixedData) MarshalJSON() ([]byte, error) {
    out := make([]interface{}, len(md.Keys)+len(md.MyData))
    i := 0
    for _, x := range md.Keys {
        out[i] = x
        i++
    }
    for _, x := range md.MyData {
        out[i] = x
        i++
    }
    return json.Marshal(out)
}

func main() {
    b := `[
  {
    "id": 1,
    "key": "my_key"
  },
  [
    {
      "id": "some_id",
      "value": 12
    },
    {
      "id": "another_id",
      "value": 13
    }
  ]
]`

    m := MixedData{}
    err := json.Unmarshal([]byte(b), &m)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    fmt.Println(m)

    enc := json.NewEncoder(os.Stdout)
    enc.SetIndent("", "    ")
    if err := enc.Encode(m); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

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

Here's an approach that combines json.RawMessage with the trick of using the default unmarshaler in a type that implements json.Unmarshaler by creating a new temporary type that aliases the target type.

The idea is that we unmarshal the incoming array into a raw message and ensure that the array length is what we expect. Then we unmarshal the individual array elements into the custom struct types using their JSON tag annotations. The end result is that we can unmarshal the PublicKey type in the usual way and the UnmarshalJSON code is not terribly difficult to follow once you understand the tricks.

For example (Go Playground):

type PublicKey struct {
  ID   int    `json:"id"`
  Key  string `json:"key"`
  Data []MyData
}

type MyData struct {
  ID    string `json:"id"`
  Value int    `json:"value"`
}

func (pk *PublicKey) UnmarshalJSON(bs []byte) error {
  // Unmarshal into a RawMessage so we can inspect the array length.
  var rawMessage []json.RawMessage
  err := json.Unmarshal(bs, &rawMessage)
  if err != nil {
    return err
  }
  if len(rawMessage) != 2 {
    return fmt.Errorf("expected array of length 2, got %d", len(rawMessage))
  }

  // Parse the first object as PublicKey using the default unmarshaler
  // using a temporary type that is an alias for the target type.
  type PublicKey2 PublicKey
  var pk2 PublicKey2
  err = json.Unmarshal(rawMessage[0], &pk2)
  if err != nil {
    return err
  }

  // Parse the second object as []MyData in the usual way.
  err = json.Unmarshal(rawMessage[1], &pk2.Data)
  if err != nil {
    return err
  }

  // Finally, assign the aliased object to the target object.
  *pk = PublicKey(pk2)
  return nil
}

func main() {
  var pk PublicKey
  err := json.Unmarshal([]byte(jsonstr), &pk)
  if err != nil {
    panic(err)
  }
  fmt.Printf("%#v
", pk)
  // main.PublicKey{ID:1, Key:"my_key", Data:[]main.MyData{main.MyData{ID:"some_id", Value:12}, main.MyData{ID:"anorther_id", Value:13}}}

}