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 (github.com/Jeffail/gabs
, for example), but it would be cleaner to just create the JSON structure since it is fairly simple.
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.
Use interface{}
if the field's type you're going to parse is not guaranteed:
type Statement struct {
Sid string `json:"Sid"`
Effect string `json:"Effect"`
Principal Principal `json:"Principal"`
Action interface{} `json:"Action"`
Resource interface{} `json:"Resource"`
}
And access the underlying original datatype using a type switch:
//Example: Trying to access Action member of a statement myStatement.
switch a := myStatement.Action.(type) {
case []string:
//Action is a slice. Handle it accordingly.
case string:
//Action is a string. Handle it accordingly.
default:
//Some other datatype that can be returned by aws?
}
Or you can have separate structs for both cases, if Unmarshaling one failed, Unmarshal it into the other struct, something like this:
err := json.Unmarshal(jsonStr, &struct1)
if err != nil {
fmt.Println(err)
err = json.Unmarshal(jsonStr, &struct2)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With