Fix list response keys derived from the API name (#162)

CloudStack keys a list response's items by the response object name, not by the
API name the generator derived the tag from, so 16 list APIs decoded as a
permanently empty slice while count parsed from the same body and no error was
returned. The keys are now data in a documented listResponseKeys map rather than
24 hand-written switch arms, each one observed on a real server and pinned by a
test, leaving only genuine shape differences in the switch; listCaCertificate and
listUsageServerMetrics also become pointers, since they return a single object
with no count. Also moves the getRawValue empty-array guard from #154 into the
generator, where it had been edited into the generated file only and so was
reverted by every `make code`.
diff --git a/cloudstack/ASNumberRangeService.go b/cloudstack/ASNumberRangeService.go
index e781764..95f73c6 100644
--- a/cloudstack/ASNumberRangeService.go
+++ b/cloudstack/ASNumberRangeService.go
@@ -385,7 +385,7 @@
 
 type ListASNRangesResponse struct {
 	Count     int         `json:"count"`
-	ASNRanges []*ASNRange `json:"asnrange"`
+	ASNRanges []*ASNRange `json:"asnumberrange"`
 }
 
 type ASNRange struct {
diff --git a/cloudstack/BackupService.go b/cloudstack/BackupService.go
index 35f1f48..0abbe71 100644
--- a/cloudstack/BackupService.go
+++ b/cloudstack/BackupService.go
@@ -3544,7 +3544,7 @@
 
 type ListBackupProvidersResponse struct {
 	Count           int               `json:"count"`
-	BackupProviders []*BackupProvider `json:"backupprovider"`
+	BackupProviders []*BackupProvider `json:"providers"`
 }
 
 type BackupProvider struct {
diff --git a/cloudstack/CertificateService.go b/cloudstack/CertificateService.go
index 26c5843..8939c35 100644
--- a/cloudstack/CertificateService.go
+++ b/cloudstack/CertificateService.go
@@ -366,8 +366,7 @@
 }
 
 type ListCaCertificateResponse struct {
-	Count         int              `json:"count"`
-	CaCertificate []*CaCertificate `json:"cacertificate"`
+	CaCertificate *CaCertificate `json:"cacertificates"`
 }
 
 type CaCertificate struct {
diff --git a/cloudstack/ClusterService.go b/cloudstack/ClusterService.go
index 1e10470..e16583d 100644
--- a/cloudstack/ClusterService.go
+++ b/cloudstack/ClusterService.go
@@ -2686,7 +2686,7 @@
 
 type ListClustersMetricsResponse struct {
 	Count           int               `json:"count"`
-	ClustersMetrics []*ClustersMetric `json:"clustersmetric"`
+	ClustersMetrics []*ClustersMetric `json:"cluster"`
 }
 
 type ClustersMetric struct {
diff --git a/cloudstack/ExtensionService.go b/cloudstack/ExtensionService.go
index fe3b281..818b4c6 100644
--- a/cloudstack/ExtensionService.go
+++ b/cloudstack/ExtensionService.go
@@ -1197,7 +1197,7 @@
 
 type ListCustomActionsResponse struct {
 	Count         int             `json:"count"`
-	CustomActions []*CustomAction `json:"customaction"`
+	CustomActions []*CustomAction `json:"extensioncustomaction"`
 }
 
 type CustomAction struct {
diff --git a/cloudstack/FirewallService.go b/cloudstack/FirewallService.go
index e6b78c0..d00d50b 100644
--- a/cloudstack/FirewallService.go
+++ b/cloudstack/FirewallService.go
@@ -4094,7 +4094,7 @@
 
 type ListRoutingFirewallRulesResponse struct {
 	Count                int                    `json:"count"`
-	RoutingFirewallRules []*RoutingFirewallRule `json:"routingfirewallrule"`
+	RoutingFirewallRules []*RoutingFirewallRule `json:"firewallrule"`
 }
 
 type RoutingFirewallRule struct {
diff --git a/cloudstack/HostService.go b/cloudstack/HostService.go
index 792b2bc..7706952 100644
--- a/cloudstack/HostService.go
+++ b/cloudstack/HostService.go
@@ -4193,7 +4193,7 @@
 
 type ListHostsMetricsResponse struct {
 	Count        int            `json:"count"`
-	HostsMetrics []*HostsMetric `json:"hostsmetric"`
+	HostsMetrics []*HostsMetric `json:"host"`
 }
 
 type HostsMetric struct {
diff --git a/cloudstack/NetworkService.go b/cloudstack/NetworkService.go
index 4bf894f..fe99e20 100644
--- a/cloudstack/NetworkService.go
+++ b/cloudstack/NetworkService.go
@@ -3775,7 +3775,7 @@
 
 type ListNetworkIsolationMethodsResponse struct {
 	Count                   int                       `json:"count"`
-	NetworkIsolationMethods []*NetworkIsolationMethod `json:"networkisolationmethod"`
+	NetworkIsolationMethods []*NetworkIsolationMethod `json:"isolationmethod"`
 }
 
 type NetworkIsolationMethod struct {
@@ -6424,7 +6424,7 @@
 
 type ListSupportedNetworkServicesResponse struct {
 	Count                    int                        `json:"count"`
-	SupportedNetworkServices []*SupportedNetworkService `json:"supportednetworkservice"`
+	SupportedNetworkServices []*SupportedNetworkService `json:"networkservice"`
 }
 
 type SupportedNetworkService struct {
diff --git a/cloudstack/SystemVMService.go b/cloudstack/SystemVMService.go
index c9e573b..2fe912f 100644
--- a/cloudstack/SystemVMService.go
+++ b/cloudstack/SystemVMService.go
@@ -1108,7 +1108,7 @@
 
 type ListSystemVmsUsageHistoryResponse struct {
 	Count                 int                      `json:"count"`
-	SystemVmsUsageHistory []*SystemVmsUsageHistory `json:"systemvmsusagehistory"`
+	SystemVmsUsageHistory []*SystemVmsUsageHistory `json:"virtualmachine"`
 }
 
 type SystemVmsUsageHistory struct {
diff --git a/cloudstack/UsageService.go b/cloudstack/UsageService.go
index ef89ced..d7310a7 100644
--- a/cloudstack/UsageService.go
+++ b/cloudstack/UsageService.go
@@ -1086,7 +1086,7 @@
 
 type ListTrafficTypeImplementorsResponse struct {
 	Count                   int                       `json:"count"`
-	TrafficTypeImplementors []*TrafficTypeImplementor `json:"traffictypeimplementor"`
+	TrafficTypeImplementors []*TrafficTypeImplementor `json:"traffictypeimplementorresponse"`
 }
 
 type TrafficTypeImplementor struct {
@@ -2128,8 +2128,7 @@
 }
 
 type ListUsageServerMetricsResponse struct {
-	Count              int                  `json:"count"`
-	UsageServerMetrics []*UsageServerMetric `json:"usageservermetric"`
+	UsageServerMetrics *UsageServerMetric `json:"usageMetrics"`
 }
 
 type UsageServerMetric struct {
diff --git a/cloudstack/UserService.go b/cloudstack/UserService.go
index 10fc217..7c09679 100644
--- a/cloudstack/UserService.go
+++ b/cloudstack/UserService.go
@@ -955,7 +955,7 @@
 
 type ListUserTwoFactorAuthenticatorProvidersResponse struct {
 	Count                               int                                   `json:"count"`
-	UserTwoFactorAuthenticatorProviders []*UserTwoFactorAuthenticatorProvider `json:"usertwofactorauthenticatorprovider"`
+	UserTwoFactorAuthenticatorProviders []*UserTwoFactorAuthenticatorProvider `json:"providers"`
 }
 
 type UserTwoFactorAuthenticatorProvider struct {
diff --git a/cloudstack/VolumeService.go b/cloudstack/VolumeService.go
index 386c72e..0b7f985 100644
--- a/cloudstack/VolumeService.go
+++ b/cloudstack/VolumeService.go
@@ -4490,7 +4490,7 @@
 
 type ListVolumesMetricsResponse struct {
 	Count          int              `json:"count"`
-	VolumesMetrics []*VolumesMetric `json:"volumesmetric"`
+	VolumesMetrics []*VolumesMetric `json:"volume"`
 }
 
 type VolumesMetric struct {
diff --git a/cloudstack/ZoneService.go b/cloudstack/ZoneService.go
index a9f0329..40440f7 100644
--- a/cloudstack/ZoneService.go
+++ b/cloudstack/ZoneService.go
@@ -2113,7 +2113,7 @@
 
 type ListIpv4SubnetsForZoneResponse struct {
 	Count              int                   `json:"count"`
-	Ipv4SubnetsForZone []*Ipv4SubnetsForZone `json:"ipv4subnetsforzone"`
+	Ipv4SubnetsForZone []*Ipv4SubnetsForZone `json:"zoneipv4subnet"`
 }
 
 type Ipv4SubnetsForZone struct {
@@ -3076,7 +3076,7 @@
 
 type ListZonesMetricsResponse struct {
 	Count        int            `json:"count"`
-	ZonesMetrics []*ZonesMetric `json:"zonesmetric"`
+	ZonesMetrics []*ZonesMetric `json:"zone"`
 }
 
 type ZonesMetric struct {
diff --git a/generate/generate.go b/generate/generate.go
index d1d0802..a0b24e4 100644
--- a/generate/generate.go
+++ b/generate/generate.go
@@ -179,6 +179,59 @@
 	"findHostsForMigration": "HostForMigration",
 }
 
+// listResponseKeys records the JSON key CloudStack uses for the items of a list
+// response, for every API where that key differs from the one derived from the
+// API name.
+var listResponseKeys = map[string]string{
+	"listAsyncJobs":                           "asyncjobs",
+	"listDomainChildren":                      "domain",
+	"listEgressFirewallRules":                 "firewallrule",
+	"listGuestNetworkIpv6Prefixes":            "guestnetworkipv6prefix",
+	"listHostHAProviders":                     "haprovider",
+	"listHostHAResources":                     "hostha",
+	"listHypervisorCapabilities":              "hypervisorCapabilities",
+	"listImageStoreObjects":                   "datastoreobject",
+	"listLBHealthCheckPolicies":               "healthcheckpolicies",
+	"listLBStickinessPolicies":                "stickinesspolicies",
+	"listManagementServersMetrics":            "managementserver",
+	"listObjectStoragePools":                  "objectstore",
+	"listSecondaryStorageSelectors":           "heuristics",
+	"listStoragePoolObjects":                  "datastoreobject",
+	"listStoragePoolsMetrics":                 "storagepool",
+	"listVirtualMachinesMetrics":              "virtualmachine",
+	"listVirtualMachinesUsageHistory":         "virtualmachine",
+	"listVmwareDcVms":                         "unmanagedinstance",
+	"listVolumesUsageHistory":                 "volume",
+	"quotaSummary":                            "summary",
+	"quotaTariffList":                         "quotatariff",
+	"registerTemplate":                        "template",
+	"listVnfAppliances":                       "virtualmachine",
+	"listVnfTemplates":                        "template",
+	"listBackupProviders":                     "providers",
+	"listClustersMetrics":                     "cluster",
+	"listCustomActions":                       "extensioncustomaction",
+	"listHostsMetrics":                        "host",
+	"listNetworkIsolationMethods":             "isolationmethod",
+	"listRoutingFirewallRules":                "firewallrule",
+	"listSupportedNetworkServices":            "networkservice",
+	"listSystemVmsUsageHistory":               "virtualmachine",
+	"listTrafficTypeImplementors":             "traffictypeimplementorresponse",
+	"listUserTwoFactorAuthenticatorProviders": "providers",
+	"listVolumesMetrics":                      "volume",
+	"listZonesMetrics":                        "zone",
+	"listASNRanges":                           "asnumberrange",
+	"listIpv4SubnetsForZone":                  "zoneipv4subnet",
+}
+
+// listResponseKey returns the JSON key for an API's list items, preferring an
+// observed key over the one derived from the API name.
+func listResponseKey(apiName, listName string) string {
+	if key, ok := listResponseKeys[apiName]; ok {
+		return key
+	}
+	return strings.ToLower(parseSingular(listName))
+}
+
 // We prefill this one value to make sure it is not
 // created twice, as this is also a top level type.
 var typeNames = map[string]bool{"Nic": true}
@@ -726,6 +779,9 @@
 	pn("				if err := json.Unmarshal(v, &resp); err != nil {")
 	pn("					return nil, err")
 	pn("				}")
+	pn("				if len(resp) == 0 {")
+	pn("					return nil, fmt.Errorf(\"Unable to extract raw value: empty array for key %%q in:\\n\\n%%s\\n\\n\", k, string(b))")
+	pn("				}")
 	pn("				return resp[0], nil")
 	pn("			}")
 	pn("		}")
@@ -2078,40 +2134,28 @@
 		a.Name == "registerCniConfiguration" || a.Name == "quotaBalance" || a.Name == "quotaSummary" || a.Name == "quotaTariffList" {
 		pn("type %s struct {", tn)
 
-		// This nasty check is for some specific response that do not behave consistent
+		// Responses whose *shape* differs: a single object instead of an array,
+		// no count, more than one collection, or a hand-written field list.
+		// Responses that differ only in the item key are handled by the default
+		// arm through listResponseKeys.
 		switch a.Name {
-		case "listAsyncJobs":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "asyncjobs")
 		case "listCapabilities":
 			pn("    %s *%s `json:\"%s\"`", ln, parseSingular(ln), "capability")
-		case "listEgressFirewallRules":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "firewallrule")
+		case "listCaCertificate":
+			// Returns a single object under "cacertificates", with no count.
+			pn("	%s *%s `json:\"%s\"`", ln, parseSingular(ln), "cacertificates")
+		case "listUsageServerMetrics":
+			// Returns a single object under "usageMetrics", with no count.
+			pn("	%s *%s `json:\"%s\"`", ln, parseSingular(ln), "usageMetrics")
 		case "listLoadBalancerRuleInstances":
 			pn("	Count int `json:\"count\"`")
 			pn("	LBRuleVMIDIPs []*%s `json:\"%s\"`", parseSingular(ln), "lbrulevmidip")
 			pn("	LoadBalancerRuleInstances []*VirtualMachine `json:\"%s\"`", strings.ToLower(parseSingular(ln)))
-		case "listVirtualMachinesMetrics":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "virtualmachine")
-		case "listManagementServersMetrics":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "managementserver")
 		case "listDbMetrics":
 			pn("	%s %s `json:\"%s\"`", ln, parseSingular(ln), "dbMetrics")
-		case "registerTemplate":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "template")
-		case "listDomainChildren":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "domain")
 		case "findHostsForMigration":
 			pn(" Count int `json:\"count\"`")
 			pn(" Host []*%s `json:\"%s\"`", customResponseStructTypes[a.Name], "host")
-		case "listVmwareDcVms":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "unmanagedinstance")
 		case "registerUserData":
 			pn("    Account string `json:\"account\"`")
 			pn("    Accountid string `json:\"accountid\"`")
@@ -2126,69 +2170,14 @@
 			pn("    Userdata string `json:\"userdata\"`")
 		case "registerCniConfiguration":
 			pn("    CniConfiguration *UserData `json:\"cniconfig\"`")
-		case "listObjectStoragePools":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "objectstore")
-		case "listStoragePoolObjects":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "datastoreobject")
-		case "listImageStoreObjects":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "datastoreobject")
-		case "listVolumesUsageHistory":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "volume")
-		case "listHostHAProviders":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "haprovider")
-		case "listSecondaryStorageSelectors":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "heuristics")
-		case "listVirtualMachinesUsageHistory":
-			pn("    Count                       int                            `json:\"count\"`")
-			pn("    VirtualMachinesUsageHistory []*VirtualMachinesUsageHistory `json:\"virtualmachine\"`")
-		case "listHostHAResources":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "hostha")
 		case "listInfrastructure":
 			pn("	Count int `json:\"count\"`")
 			pn("	%s *%s `json:\"%s\"`", ln, parseSingular(ln), "infrastructure")
-		case "listStoragePoolsMetrics":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "storagepool")
-		case "quotaTariffList":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "quotatariff")
 		case "quotaBalance":
 			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "balance")
-		case "quotaSummary":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "summary")
-		case "listHypervisorCapabilities":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "hypervisorCapabilities")
-		case "listGuestNetworkIpv6Prefixes":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "guestnetworkipv6prefix")
-		case "listLBHealthCheckPolicies":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "healthcheckpolicies")
-		case "listLBStickinessPolicies":
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "stickinesspolicies")
-		case "listVnfTemplates":
-			// ListVnfTemplatesCmd is an empty subclass of ListTemplatesCmd, so the
-			// server returns the items under "template", not "vnftemplate".
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "template")
-		case "listVnfAppliances":
-			// ListVnfAppliancesCmd inherits execute() from ListVMsCmd, so the server
-			// returns the items under "virtualmachine", not "vnfappliance".
-			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), "virtualmachine")
 		default:
 			pn("	Count int `json:\"count\"`")
-			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), strings.ToLower(parseSingular(ln)))
+			pn("	%s []*%s `json:\"%s\"`", ln, parseSingular(ln), listResponseKey(a.Name, ln))
 		}
 		pn("}")
 		pn("")
diff --git a/test/ListResponseJSONTagsRegression_test.go b/test/ListResponseJSONTagsRegression_test.go
index 1552328..575e6fc 100644
--- a/test/ListResponseJSONTagsRegression_test.go
+++ b/test/ListResponseJSONTagsRegression_test.go
@@ -71,3 +71,193 @@
 		}
 	})
 }
+
+// These assertions exist because the key cannot be derived from the API name, so
+// a regeneration can otherwise quietly reintroduce any of them.
+func TestListResponseKeysObservedFromServer(t *testing.T) {
+	sliceCases := []struct {
+		name   string
+		key    string
+		body   string
+		decode func([]byte) (int, error)
+	}{
+		{
+			name: "ASNRanges", key: "asnumberrange",
+			body: `{"count":1,"asnumberrange":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListASNRangesResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.ASNRanges), err
+			},
+		},
+		{
+			name: "Ipv4SubnetsForZone", key: "zoneipv4subnet",
+			body: `{"count":1,"zoneipv4subnet":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListIpv4SubnetsForZoneResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.Ipv4SubnetsForZone), err
+			},
+		},
+		{
+			name: "BackupProviders", key: "providers",
+			body: `{"count":1,"providers":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListBackupProvidersResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.BackupProviders), err
+			},
+		},
+		{
+			name: "ClustersMetrics", key: "cluster",
+			body: `{"count":1,"cluster":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListClustersMetricsResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.ClustersMetrics), err
+			},
+		},
+		{
+			name: "CustomActions", key: "extensioncustomaction",
+			body: `{"count":1,"extensioncustomaction":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListCustomActionsResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.CustomActions), err
+			},
+		},
+		{
+			name: "HostsMetrics", key: "host",
+			body: `{"count":1,"host":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListHostsMetricsResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.HostsMetrics), err
+			},
+		},
+		{
+			name: "NetworkIsolationMethods", key: "isolationmethod",
+			body: `{"count":1,"isolationmethod":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListNetworkIsolationMethodsResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.NetworkIsolationMethods), err
+			},
+		},
+		{
+			name: "RoutingFirewallRules", key: "firewallrule",
+			body: `{"count":1,"firewallrule":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListRoutingFirewallRulesResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.RoutingFirewallRules), err
+			},
+		},
+		{
+			name: "SupportedNetworkServices", key: "networkservice",
+			body: `{"count":1,"networkservice":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListSupportedNetworkServicesResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.SupportedNetworkServices), err
+			},
+		},
+		{
+			name: "SystemVmsUsageHistory", key: "virtualmachine",
+			body: `{"count":1,"virtualmachine":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListSystemVmsUsageHistoryResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.SystemVmsUsageHistory), err
+			},
+		},
+		{
+			name: "TrafficTypeImplementors", key: "traffictypeimplementorresponse",
+			body: `{"count":1,"traffictypeimplementorresponse":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListTrafficTypeImplementorsResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.TrafficTypeImplementors), err
+			},
+		},
+		{
+			name: "UserTwoFactorAuthenticatorProviders", key: "providers",
+			body: `{"count":1,"providers":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListUserTwoFactorAuthenticatorProvidersResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.UserTwoFactorAuthenticatorProviders), err
+			},
+		},
+		{
+			name: "VolumesMetrics", key: "volume",
+			body: `{"count":1,"volume":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListVolumesMetricsResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.VolumesMetrics), err
+			},
+		},
+		{
+			name: "ZonesMetrics", key: "zone",
+			body: `{"count":1,"zone":[{}]}`,
+			decode: func(b []byte) (int, error) {
+				var r cloudstack.ListZonesMetricsResponse
+				err := json.Unmarshal(b, &r)
+				return len(r.ZonesMetrics), err
+			},
+		},
+	}
+
+	for _, tc := range sliceCases {
+		t.Run(tc.name, func(t *testing.T) {
+			n, err := tc.decode([]byte(tc.body))
+			if err != nil {
+				t.Fatalf("decoding key %q: %v", tc.key, err)
+			}
+			if n != 1 {
+				t.Fatalf("expected 1 item under key %q, got %d (nil slice means the json tag does not match the key the server sends)", tc.key, n)
+			}
+		})
+	}
+
+	// These two return a single object and no count, so the field is a pointer
+	// rather than a slice.
+	objectCases := []struct {
+		name   string
+		key    string
+		body   string
+		decode func([]byte) (bool, error)
+	}{
+		{
+			name: "CaCertificate", key: "cacertificates",
+			body: `{"cacertificates":{}}`,
+			decode: func(b []byte) (bool, error) {
+				var r cloudstack.ListCaCertificateResponse
+				err := json.Unmarshal(b, &r)
+				return r.CaCertificate != nil, err
+			},
+		},
+		{
+			name: "UsageServerMetrics", key: "usageMetrics",
+			body: `{"usageMetrics":{}}`,
+			decode: func(b []byte) (bool, error) {
+				var r cloudstack.ListUsageServerMetricsResponse
+				err := json.Unmarshal(b, &r)
+				return r.UsageServerMetrics != nil, err
+			},
+		},
+	}
+
+	for _, tc := range objectCases {
+		t.Run(tc.name, func(t *testing.T) {
+			ok, err := tc.decode([]byte(tc.body))
+			if err != nil {
+				t.Fatalf("decoding key %q: %v", tc.key, err)
+			}
+			if !ok {
+				t.Fatalf("expected an object under key %q, got nil", tc.key)
+			}
+		})
+	}
+}