1

In Go I have to parse this json:

{ 
  "response": [
    {
      "message": [
         "hello world"
      ],
      "misc": [
        {
          "timestamp": [
             "2017-06-28T05:52:39.347Z"
          ],
          "server": [
             "server-0101"
          ]
        }
      ]
    }
  ]
}

I'd like to get an object in Go that doesn't include all the unnecessary arrays of with a single string. The source json will never have more than one string in each array.

So the end result that I'd like to get would be this json:

{ 
  "response": {
    "message": "hello world",
    "misc": {
      "timestamp": "2017-06-28T05:52:39.347Z",
      "server": "server-0101"
    }
  }
}

Or an equivalent object in Go.

Right now I have to use Response[0].Misc[0].Timestamp[0] to access the data which seems weird.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Henry
  • 21
  • 4

3 Answers3

1

You can override the default behaviour of json.Marshal / json.Unmarshal methods for a struct, by defining its own MarshalJSON or UnmarshalJSON properly.

Here there is an excerpt for the code of a simplified version of the struct you need to decode.

type Response struct {
    Message string `json:"message"`
}

// UnmarshalJSON overrides the default behaviour for JSON unmarshal method.
func (r *Response) UnmarshalJSON(data []byte) error {
    auxResponse := &struct {
        Message []string `json:"message"`
    }{}
    if err := json.Unmarshal(data, &auxResponse); err != nil {
        return err
    }

    // Consider to add some checks on array length :)
    r.Message = auxResponse.Message[0]

    return nil
}

You can access the full working example here.

I suggest you to read this interesting article about custom JSON encode/decode with golang.

0

I'd like to get an object in Go that doesn't include all the unnecessary arrays of with a single string.

The hard way: Parse the JSON by hand (write our own parser).

The sensible way: Unmarshal via package encoding/json into some Go type matching the JSON or into some generic interface{} and copy the pieces into a different, simpler Go type afterwards.

Volker
  • 40,468
  • 7
  • 81
  • 87
0

Creating your own unmarshaller is probably best, but this is a quick way to simulate what you want to achieve.

package main

import (
    "encoding/json"
    "fmt"
)

// JSON ...
var JSON = `{ 
  "response": [
    {
      "message": [
         "hello world"
      ],
      "misc": [
        {
          "timestamp": [
             "2017-06-28T05:52:39.347Z"
          ],
          "server": [
             "server-0101"
          ]
        }
      ]
    }
  ]
}
`

type rawObject struct {
    Response []struct {
        Message []string      `json:"message"`
        Misc    []interface{} `json:"misc"`
    } `json:"response"`
}

type clean struct {
    Message string                 `json:"message"`
    Misc    map[string]interface{} `json:"misc"`
}

func main() {
    var o rawObject
    var c clean
    // init map
    c.Misc = make(map[string]interface{})

    // unmarshall the raw data
    json.Unmarshal([]byte(JSON), &o)

    for _, res := range o.Response { // I assume there should only be one response, don't know why this is wrapped as an array
        //  assume message is not really an array
        c.Message = res.Message[0]
        // convert []interface to map[string]interface
        for _, m := range res.Misc {
            for k, v := range m.(map[string]interface{}) {
                c.Misc[k] = v
            }
        }
    }
    fmt.Printf("%+v\n", c)
}

What i don't like about this answer is that it isn't very reusable..so a function should probably be made and more error checking (part of creating a custom unmarshaller). If this were used in heavy production it might run into some memory issues, as I have to create a raw object to create a clean object.. but as a one off script it does the job. I my clean struct doesn't add response as a type because i find it to be redundant.

reticentroot
  • 3,612
  • 2
  • 22
  • 39