78 lines
1.9 KiB
Go
78 lines
1.9 KiB
Go
package cart
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
// cartItemAlias mirrors CartItem but has no custom (Un)MarshalJSON, so it is
|
|
// used to (de)serialize the typed fields without recursing.
|
|
type cartItemAlias CartItem
|
|
|
|
// knownItemKeys is the set of top-level JSON object keys owned by typed
|
|
// CartItem fields. Any other key in an item object is dynamic and kept in
|
|
// CartItem.Extra.
|
|
var knownItemKeys = buildKnownItemKeys()
|
|
|
|
func buildKnownItemKeys() map[string]struct{} {
|
|
keys := make(map[string]struct{})
|
|
t := reflect.TypeOf(CartItem{})
|
|
for i := 0; i < t.NumField(); i++ {
|
|
name, _, _ := strings.Cut(t.Field(i).Tag.Get("json"), ",")
|
|
if name == "" {
|
|
name = t.Field(i).Name // untagged fields serialize under their Go name
|
|
}
|
|
if name == "-" {
|
|
continue // e.g. Extra itself
|
|
}
|
|
keys[name] = struct{}{}
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// MarshalJSON emits the typed fields and flattens Extra onto the same object.
|
|
// Typed fields take precedence on key collisions.
|
|
func (c CartItem) MarshalJSON() ([]byte, error) {
|
|
core, err := json.Marshal(cartItemAlias(c))
|
|
if err != nil || len(c.Extra) == 0 {
|
|
return core, err
|
|
}
|
|
merged := make(map[string]json.RawMessage, len(c.Extra)+8)
|
|
if err := json.Unmarshal(core, &merged); err != nil {
|
|
return nil, err
|
|
}
|
|
for k, v := range c.Extra {
|
|
if _, taken := merged[k]; taken {
|
|
continue
|
|
}
|
|
merged[k] = v
|
|
}
|
|
return json.Marshal(merged)
|
|
}
|
|
|
|
// UnmarshalJSON reads the typed fields and collects every other key into Extra.
|
|
func (c *CartItem) UnmarshalJSON(data []byte) error {
|
|
var alias cartItemAlias
|
|
if err := json.Unmarshal(data, &alias); err != nil {
|
|
return err
|
|
}
|
|
*c = CartItem(alias)
|
|
|
|
all := make(map[string]json.RawMessage)
|
|
if err := json.Unmarshal(data, &all); err != nil {
|
|
return err
|
|
}
|
|
for k := range all {
|
|
if _, known := knownItemKeys[k]; known {
|
|
delete(all, k)
|
|
}
|
|
}
|
|
if len(all) > 0 {
|
|
c.Extra = all
|
|
} else {
|
|
c.Extra = nil
|
|
}
|
|
return nil
|
|
}
|