I have been trying to write a custom Unmarshal to deal with some XML code.
I know that you can write a custom unmarshal method to deal with an individual field within a struct, however I was trying to write one on the whole struct:
type LowestPricedPricedOffers struct {
ASIN string `xml:"GetLowestPricedOffersForASINResult>Identifier>ASIN"`
TimeOfOfferChange string `xml:"GetLowestPricedOffersForASINResult>Identifier>TimeOfOfferChange"`
TotalOfferCount int `xml:"GetLowestPricedOffersForASINResult>Summary>TotalOfferCount"`
ListPrice float32 `xml:"GetLowestPricedOffersForASINResult>Summary>ListPrice>Amount"`
LowestNew float32 //lowest new landed price
LowestUsed float32 //lowest used landed price
OfferCount map[string]map[string]int
CurrencyCode string //currency code taken from LowestPrices
NumberOfOffers []struct {
OfferCount string `xml:",innerxml"` //Will not work with int
Condition string `xml:"condition,attr"`
FulfillmentChannel string `xml:"fulfillmentChannel,attr"`
} `xml:"GetLowestPricedOffersForASINResult>Summary>NumberOfOffers>OfferCount"`
LowestPrices []struct {
Condition string `xml:"condition,attr"`
FulfillmentChannel string `xml:"fulfillmentChannel,attr"`
LandedPrice float32Unmarsh `xml:"LandedPrice>Amount"`
ListingPrice float32 `xml:"ListingPrice>Amount"`
Shipping float32 `xml:"Shipping>Amount"`
CurrencyCode string `xml:"LandedPrice>CurrencyCode"`
} `xml:"GetLowestPricedOffersForASINResult>Summary>LowestPrices>LowestPrice"`
}
func (n *LowestPricedPricedOffers) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
t, err := d.Token()
if err == io.EOF {
break
}
switch tt := t.(type) {
case xml.StartElement:
var value int
if tt.Name.Local == "OfferCount" {
d.DecodeElement(&value, &start)
var condition string
var fullfillmentChannel string
for _, x := range tt.Attr {
//fmt.Println(x.Name.Local, x.Value)
if x.Name.Local == "condition" {
condition = x.Value
fmt.Println(condition)
} else {
fullfillmentChannel = x.Value
}
}
if n.OfferCount == nil {
n.OfferCount = make(map[string]map[string]int)
n.OfferCount[condition] = make(map[string]int)
n.OfferCount[condition][fullfillmentChannel] = value
} else {
if n.OfferCount[condition] == nil {
n.OfferCount[condition] = make(map[string]int)
}
n.OfferCount[condition][fullfillmentChannel] = value
}
} else if tt.Name.Local == "ASIN" {
d.DecodeElement(&n.ASIN, &start)
}
// More else if's to deal with rest of XML
}
}
return nil
}
This seems to work in that I can control how my struct is populated, e.g in generating the map for 'OfferCount'. With my generalized approach, I then need to manually populate the rest of the struct using e.g
} else if tt.Name.Local == "ASIN" {
d.DecodeElement(&n.ASIN, &start)
}
Which I would have to do for all the elements in the XML, having to specify the struct field for each element. Is there a way using my approach that I can override for certain elements but just auto-populate the rest utilizing the tags in the struct as per a standard unmarshal without generating individual types for struct fields?
I have posted the full code on the go playground :
I hope this makes some sense.
I have updated my approach and question, to use custom types and custom unmarshalling on them.
The main struct has been updated as follows:
type LowestPricedPricedOffers struct {
Error AmazonError `xml:"Error"`
All struct {
/*The only way I found to retrieve 'status' from the GetLowestPricedOffersForASINResult element was to wrap in the struct 'All'.
This is the only reason the All struct exists. Ideally would like to remove*/
Status string `xml:"status,attr"`
ASIN string `xml:"Identifier>ASIN"`
ItemCondition string `xml:"Identifier>ItemCondition"`
TimeOfOfferChange string `xml:"Identifier>TimeOfOfferChange"`
TotalOfferCount int `xml:"Summary>TotalOfferCount"`
ListPrice float32 `xml:"Summary>ListPrice>Amount"`
OfferCount offerCount `xml:"Summary>NumberOfOffers"`
//Want to take Currency code from LowestPrices below but cannot think of a way to achieve this against the lowestPrices type
//CurrencyCode string `xml:"CurrencyCode"`
BuyBoxPrices buyBoxPrices `xml:"Summary>BuyBoxPrices"`
LowestPrices lowestPrices `xml:"Summary>LowestPrices"`
BuyBoxEligibleOffers buyBoxEligibleOffers `xml:"Summary>BuyBoxEligibleOffers"`
Offers []struct {
SubCondition string `xml:"SubCondition"`
SellerPositiveFeedbackRating float32 `xml:"SellerFeedbackRating>SellerPositiveFeedbackRating"`
FeedbackCount int `xml:"SellerFeedbackRating>FeedbackCount"`
ShippingTime struct {
MinimumHours int `xml:"minimumHours,attr"`
MaximumHours int `xml:"maximumHours,attr"`
AvailabilityType string `xml:"availabilityType,attr"`
}
ListingPrice float32 `xml:"ListingPrice>Amount"`
Shipping float32 `xml:"Shipping>Amount"`
ShipsFrom string `xml:"ShipsFrom>Country"`
IsFulfilledByAmazon bool `xml:"IsFulfilledByAmazon"`
IsBuyBoxWinner bool `xml:"IsBuyBoxWinner"`
IsFeaturedMerchant bool `xml:"IsFeaturedMerchant"` //true if the seller of the item is eligible to win the Buy Box.
} `xml:"Offers>Offer"`
} `xml:"GetLowestPricedOffersForASINResult"`
}
It includes the 'All' struct which seemed to be the only way to get the unmarshalling of 'status' from the GetLowestPricedOffersForASINResult element. Ideally I would like to get rid of this wrapper struct, but I am not sure that this is possible.
Some of the custom types are maps, to allow for easy retrieval of data:
type offerCount map[string]map[string]int
func (m *offerCount) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var content map[string]map[string]int
for {
t, err := d.Token()
if err == io.EOF {
break
}
switch tt := t.(type) {
case xml.StartElement:
var value int
if tt.Name.Local == "OfferCount" {
d.DecodeElement(&value, &start)
var condition string
var fullfillmentChannel string
for _, x := range tt.Attr {
if x.Name.Local == "condition" {
condition = x.Value
} else {
fullfillmentChannel = x.Value
}
}
if content == nil {
content = make(map[string]map[string]int)
content[condition] = make(map[string]int)
content[condition][fullfillmentChannel] = value
} else {
if content[condition] == nil {
content[condition] = make(map[string]int)
}
content[condition][fullfillmentChannel] = value
}
}
}
*m = offerCount(content)
}
return nil
}
I am also trying to work out how to set CurrencyCode via the lowestPrices type, not sure if that is possible?
The full updated code is on the go playground for general comment or advice.