You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用AWS Go SDK反序列化IAM政策:处理字段多类型问题

Handling IAM Policy Fields That Are Either String or Array in Go

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 StringOrSlice type 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:23:34