Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serialize a mixed type JSON array in Go

Tags:

json

arrays

go

I want to return a structure that looks like this:

{
    results: [
        ["ooid1", 2.0, "Söme text"],
        ["ooid2", 1.3, "Åther text"],
    ]
}

That's an array of arrags that is string, floating point number, unicode character.

If it was Python I'd be able to:

import json
json.dumps({'results': [["ooid1", 2.0, u"Söme text"], ...])

But in Go you can't have an array (or slice) of mixed types.

I thought of using a struct like this:

type Row struct {
    Ooid string
    Score float64
    Text rune
}

But I don't want each to become a dictionary, I want it to become an array of 3 elements each.

like image 949
Peter Bengtsson Avatar asked Jan 18 '15 22:01

Peter Bengtsson


2 Answers

We can customize how an object is serialized by implementing the json.Marshaler interface. For our particular case, we seem to have a slice of Row elements that we want to encode as an array of heterogenous values. We can do so by defining a MarshalJSON function on our Row type, using an intermediate slice of interface{} to encode the mixed values.

This example demonstrates:

package main

import (
    "encoding/json"
    "fmt"
)

type Row struct {
    Ooid  string
    Score float64
    Text  string
}

func (r *Row) MarshalJSON() ([]byte, error) {
    arr := []interface{}{r.Ooid, r.Score, r.Text}
    return json.Marshal(arr)
}

func main() {
    rows := []Row{
        {"ooid1", 2.0, "Söme text"},
        {"ooid2", 1.3, "Åther text"},
    }
    marshalled, _ := json.Marshal(rows)
    fmt.Println(string(marshalled))
}

Of course, we also might want to go the other way around, from JSON bytes back to structs. So there's a similar json.Unmarshaler interface that we can use.

func (r *Row) UnmarshalJSON(bs []byte) error {
    arr := []interface{}{}
    json.Unmarshal(bs, &arr)
    // TODO: add error handling here.
    r.Ooid = arr[0].(string)
    r.Score = arr[1].(float64)
    r.Text = arr[2].(string)
    return nil
}

This uses a similar trick of first using an intermediate slice of interface{}, using the unmarshaler to place values into this generic container, and then plop the values back into our structure.

package main

import (
    "encoding/json"
    "fmt"
)

type Row struct {
    Ooid  string
    Score float64
    Text  string
}

func (r *Row) UnmarshalJSON(bs []byte) error {
    arr := []interface{}{}
    json.Unmarshal(bs, &arr)
    // TODO: add error handling here.
    r.Ooid = arr[0].(string)
    r.Score = arr[1].(float64)
    r.Text = arr[2].(string)
    return nil
}

func main() {
    rows := []Row{}
    text := `
    [
          ["ooid4", 3.1415, "pi"],
          ["ooid5", 2.7182, "euler"]
        ]
    `
    json.Unmarshal([]byte(text), &rows)
    fmt.Println(rows)
}

You can read a full example here.

like image 192
dyoo Avatar answered Nov 03 '22 01:11

dyoo


Use []interface{}

type Results struct {
     Rows []interface{} `json:"results"`
}

You will then have to use type assertion if you want to access the values stored in []interface{}

for _, row := range results.Rows {
    switch r := row.(type) {
    case string:
        fmt.Println("string", r)
    case float64:
        fmt.Println("float64", r)
    case int64:
        fmt.Println("int64", r)
    default:
        fmt.Println("not found")
    } 
}
like image 36
jmaloney Avatar answered Nov 03 '22 01:11

jmaloney