使用AWS Go SDK反序列化IAM政策:处理字段多类型问题
Hey there, let's fix that frustrating unmarshaling issue with AWS IAM policies! The core problem here is that fields like Resource and Action in IAM statements can be either a single string or a slice of strings, but your current StatementEntry struct uses []string which fails when the JSON has a single string value (like "Resource": "*" in your first example).
Best Practice: Custom Type with Custom Unmarshaling
The cleanest and most maintainable solution is to create a custom type that can handle both string and array inputs by implementing the json.Unmarshaler interface. This way, Go will automatically use your logic when unmarshaling these fields.
Step 1: Define the Custom Type
First, create a type that wraps a slice of strings, then implement UnmarshalJSON for it:
type StringOrSlice []string func (s *StringOrSlice) UnmarshalJSON(data []byte) error { // Try to unmarshal as a single string first var single string if err := json.Unmarshal(data, &single); err == nil { *s = []string{single} return nil } // If that fails, try unmarshaling as a slice of strings var slice []string if err := json.Unmarshal(data, &slice); err == nil { *s = slice return nil } // If neither works, return the error return fmt.Errorf("failed to unmarshal as string or slice: %w", err) }
This logic first checks if the JSON value is a single string (and converts it to a slice with one element), then falls back to parsing it as a string slice. If neither works, it returns a descriptive error.
Step 2: Update Your Structs
Modify your StatementEntry struct to use this custom type instead of []string:
type StatementEntry struct { Effect string `json:"Effect"` Action StringOrSlice `json:"Action"` Resource StringOrSlice `json:"Resource"` Sid string `json:"Sid,omitempty"` // Add Sid since some policies have it }
I also added the Sid field with omitempty to handle policies that include a statement ID (like your first example).
Step 3: Update Your Main Logic
Your existing code mostly stays the same, but now it will correctly unmarshal both single-string and array values for Action and Resource. Here's the full updated code:
package main import ( "encoding/json" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/iam" "log" "net/url" ) type PolicyDocument struct { Version string `json:"Version"` Statement []StatementEntry `json:"Statement"` } type StatementEntry struct { Effect string `json:"Effect"` Action StringOrSlice `json:"Action"` Resource StringOrSlice `json:"Resource"` Sid string `json:"Sid,omitempty"` } type StringOrSlice []string func (s *StringOrSlice) UnmarshalJSON(data []byte) error { var single string if err := json.Unmarshal(data, &single); err == nil { *s = []string{single} return nil } var slice []string if err := json.Unmarshal(data, &slice); err == nil { *s = slice return nil } return fmt.Errorf("invalid type for StringOrSlice: %s", string(data)) } func main() { sess, err := session.NewSession(&aws.Config{ Region: aws.String("us-west-2")}, ) if err != nil { log.Fatalf("failed to create session: %v", err) } svc := iam.New(sess) results, err := svc.ListPolicies(&iam.ListPoliciesInput{}) if err != nil { log.Fatalf("failed to list policies: %v", err) } for _, policy := range results.Policies { arn := policy.Arn version := policy.DefaultVersionId if arn == nil || version == nil { log.Println("skipping policy with missing ARN or version ID") continue } pv, err := svc.GetPolicyVersion(&iam.GetPolicyVersionInput{ PolicyArn: arn, VersionId: version, }) if err != nil { log.Printf("failed to get policy version for %s: %v", *arn, err) continue } decodedValue, err := url.QueryUnescape(aws.StringValue(pv.PolicyVersion.Document)) if err != nil { log.Printf("failed to decode policy document for %s: %v", *arn, err) continue } var doc PolicyDocument if err := json.Unmarshal([]byte(decodedValue), &doc); err != nil { log.Printf("failed to unmarshal policy document for %s: %v", *arn, err) continue } fmt.Printf("\n---- Policy ARN: %s ----\n%+v\n---\n", *arn, doc) } }
Key Improvements in the Updated Code
- Error Handling: I added proper error checking for session creation, policy listing, and policy version retrieval (your original code ignored errors, which is bad practice!).
- Custom Type: The
StringOrSlicetype handles both single-string and array inputs seamlessly. - Robustness: Added checks for nil ARN/version IDs to avoid panics.
Do You Need Retry Logic?
Short answer: No in this case. The error you're facing is a structural issue (invalid type during unmarshaling), not a transient network error or AWS service throttling. Retrying won't fix it—you need to handle the type compatibility properly, which the custom type solution does.
If you were dealing with transient errors (like AWS API rate limiting), you could use the AWS SDK's built-in retry logic (it's enabled by default for most operations), but that's unrelated to your current problem.
Testing this code with both of your example policies will work perfectly—it'll parse the single "Resource": "*" as a slice with one element, and the array values as normal slices.
内容的提问来源于stack exchange,提问作者tommy_o

