api – How can I discover if a []byte is an array or a single object-ThrowExceptions

Exception or error:

I’m creating an API that will receive an object in a specific route. In this route I can receive a single object or a bulk of it.

Example:

  [{"foo":"bar"}]
  {"foo":"bar"}

How can I know if the body request is a slice or a single object before de json.Unmarshal. Moreover, if this is not possible, what is the best way to accept this two types of body requests and convert them to a list of objects?

I expect something like this:

  type Foo struct {
    Foo string `json:"foo"`
  }

  func Bla() []Foo {

    fakeBody := []byte(`[{"foo":"bar"}]`)
    fakeBody2 := []byte(`{"foo":"bar"}`)     

    var foo []Foo

    // If fakeBody contains a array of objects
    // then parse it to the foo slice variable normally

    // So, if the fakeBody2 is a single object then 
    // parse this single object to the foo slice that will contain only
    // one element.

    return foo
  }
How to solve:

This is what I would consider doing in this situation, in this order:

  • You can read the body, and check the first non-space character to see if it is ‘[‘ or ‘{‘, and unmarshal based on that.
  • You can first unmarshal as an array, then if that fails, as a single object.
  • You can unmarshal to an interface{}, do a type assertion, and parse the contents yourself.

Answer:

Check the first non-whitespace byte to determine if the JSON document is an array or object. Decode accordingly.

func decode(body []byte) ([]Foo, error) {
    b = bytes.TrimLeft(body, " \t\n\r")
    if len(b) > 0 && b[0] == '[' {
        var v []Foo
        err := json.Unmarshal(body, &v)
        return v, err
    }
    var v [1]Foo
    err := json.Unmarshal(body, &v[0])
    return v[:], err
}

Answer:

Why not just add the [ the and ] if it’s not their, and then always treat it as an array?

body := []byte(`{"foo":"bar"}`)
body = bytes.TrimSpace(body)
if len(body) > 0 && body[0] != '[' {
    tmp := make([]byte, len(body)+2, len(body)+2)
    tmp[0] = '['
    tmp[len(tmp)-1] = ']'
    copy(tmp[1:len(tmp)-1], body)
    body = tmp
}

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

Or, create the array first, and then based on the first character either marshal into the array or the first item:

f := make([]Foo, 1)
body := []byte(`{"foo":"bar"}`)
if len(body) > 0 && body[0] != '[' {
    json.Unmarshal(body, &f[0])
} else {
    json.Unmarshal(body, &f)
}
fmt.Println(f)

https://play.golang.org/p/1fxBKH3ZJyH

Leave a Reply

Your email address will not be published. Required fields are marked *