anon01 anon01 - 1 year ago 90
JSON Question

Golang structure for json to allow a value to optionally be an array

I am trying to change the policy for an s3 bucket on aws. I have created the following json structure for a policy:

type Policy struct {
Version string `json:"Version"`
Id string `json:"Id"`
Statement []Statement `json:"Statement"`

type Statement struct {
Sid string `json:"Sid"`
Effect string `json:"Effect"`
Principal Principal `json:"Principal"`
Action []string `json:"Action"`
Resource []string `json:"Resource"`

type Principal struct {
AWS[]string `json:"AWS"`

Which works fine for putting bucket policies in place. The issue comes when I try to get the current policy and modify it.

If there is a statement that only has one AWS, Action, or Resource value, Amazon will convert it from an array to a simple value, causing my unmarshalling to fail.

Is there any way that I can specify AWS/Action/Resource values to be either a string slice or just a string?

I know that there are packages available that I could use to get around this to some extent (
, for example), but it would be cleaner to just create the JSON structure since it is fairly simple.

Answer Source

As an alternative to interface{}, you can create a type called MaybeSlice and implement custom MarshalJSON and UnmarshalJSON methods on it.

type MaybeSlice []string

func (ms *MaybeSlice) MarshalJSON() ([]byte, error) {
    // Use normal json.Marshal for subtypes
    if len(*ms) == 1 {
        return json.Marshal(([]string)(*ms)[0])
    return json.Marshal(*ms)

func (ms *MaybeSlice) UnmarshalJSON(data []byte) error {
    // Use normal json.Unmarshal for subtypes
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        var v []string
        if err := json.Unmarshal(data, &v); err != nil {
             return err
        *ms = v
        return nil
    *ms = []string{s}
    return nil

By implementing these methods the MaybeSlice type will satisfy the interfaces expected by json.Marshal and json.Unmarshal so you won't need to implement custom marshallers for all your types, just this one.

MaybeSlice is a terrible name but hopefully you get the idea.

These custom methods are easier with struct types, but I think the above is correct. If I remember correctly you need to make Action a *MaybeSlice to use the above.