From cac598d8c3ade92ba5c1a14fbab3ed8916bc588e Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Tue, 3 Mar 2026 23:27:36 -0500 Subject: [PATCH] fix: handle string-or-array MAC address fields from ABM API Apple's ABM API inconsistently returns wifiMacAddress, bluetoothMacAddress, ethernetMacAddress, IMEI, and MEID as either a JSON string or an array of strings depending on the device. This causes unmarshal failures when the response format doesn't match the expected []string type. Add a StringOrStrings type implementing UnmarshalerFrom that handles both a single JSON string and a JSON array of strings, as well as null values. Co-Authored-By: Claude Opus 4.6 --- types.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/types.go b/types.go index 2cb2f74..458b42a 100644 --- a/types.go +++ b/types.go @@ -17,7 +17,10 @@ package abm import ( + "fmt" "time" + + "github.com/go-json-experiment/json/jsontext" ) // OrgDevicesResponse contains a list of organization device resources. @@ -71,6 +74,55 @@ const ( StatusUnAssigned OrgDeviceAttributesStatus = "UNASSIGNED" ) +// StringOrStrings is a flexible type that can unmarshal from either a JSON string +// or an array of strings. The Apple Business Manager API inconsistently returns +// fields like wifiMacAddress as either a string or an array depending on the device. +type StringOrStrings []string + +// UnmarshalJSONFrom implements [json.UnmarshalerFrom] for the go-json-experiment library. +func (s *StringOrStrings) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + kind := dec.PeekKind() + switch kind { + case '[': + var arr []string + if _, err := dec.ReadToken(); err != nil { + return err + } + for dec.PeekKind() != ']' { + tok, err := dec.ReadToken() + if err != nil { + return err + } + arr = append(arr, tok.String()) + } + if _, err := dec.ReadToken(); err != nil { + return err + } + *s = arr + return nil + case '"': + tok, err := dec.ReadToken() + if err != nil { + return err + } + str := tok.String() + if str != "" { + *s = []string{str} + } else { + *s = nil + } + return nil + case 'n': + if _, err := dec.ReadToken(); err != nil { + return err + } + *s = nil + return nil + default: + return fmt.Errorf("StringOrStrings: unexpected JSON kind %v", kind) + } +} + // OrgDeviceAttributes contains attributes for an organization device resource. type OrgDeviceAttributes struct { AddedToOrgDateTime time.Time `json:"addedToOrgDateTime,omitzero"` @@ -79,11 +131,11 @@ type OrgDeviceAttributes struct { DeviceCapacity string `json:"deviceCapacity,omitzero"` DeviceModel string `json:"deviceModel,omitzero"` EID string `json:"eid,omitzero"` - IMEI []string `json:"imei,omitempty"` - MEID []string `json:"meid,omitempty"` - WifiMacAddress []string `json:"wifiMacAddress,omitempty"` - BluetoothMacAddress []string `json:"bluetoothMacAddress,omitempty"` - EthernetMacAddress []string `json:"ethernetMacAddress,omitempty"` + IMEI StringOrStrings `json:"imei,omitempty"` + MEID StringOrStrings `json:"meid,omitempty"` + WifiMacAddress StringOrStrings `json:"wifiMacAddress,omitempty"` + BluetoothMacAddress StringOrStrings `json:"bluetoothMacAddress,omitempty"` + EthernetMacAddress StringOrStrings `json:"ethernetMacAddress,omitempty"` OrderDateTime time.Time `json:"orderDateTime,omitzero"` OrderNumber string `json:"orderNumber,omitzero"` PartNumber string `json:"partNumber,omitzero"`