Wednesday, July 31, 2019

go - Assign additional field when unmarshalling JSON object to struct

What is the best way to assign additional field to struct and all it references sub-structure when encoding it from []byte and this field is not a part of unmarshalling []byte?



Let me clarify by example...




  1. We have a few struct:





type Update struct {
UpdateID int32 `json:"update_id"`
Message *Message `json:"message,omitempty"`
...
}

type Message struct {
MessageID int32 `json:"message_id"`
From *User `json:"from,omitempty"`
Date int32 `json:"date"`
Chat Chat `json:"chat"`
...
}




  1. Our core struct is APIClient and it has many methods for send something or get something (API wrapper)





type API struct {
Token string
PollInt int32
URL string

client *http.Client
}

func(a *API) ... () {

}

func(a *API) ... () {

}

...




  1. Main loop poller make http request and return json response as "res" – []byte, so we can use json.Unmarshal to map it to special Update struct





func (a *API) GetUpdates(ctx context.Context, gu *GetUpdates) ([]Update, error) {
buf := bytes.Buffer{}
err := json.NewEncoder(&buf).Encode(gu)
if err != nil {
return nil, fmt.Errorf("getupdates: %s", err)
}

res, err := a.DoRequest(&ctx, "getUpdates", &buf)
if err != nil {
return nil, fmt.Errorf("getupdates: %s", err)
}

var result []Update
err = json.Unmarshal(res.Result, &result)
if err != nil {
return nil, fmt.Errorf("getupdates: %s", err)
}

return result, nil
}




  1. Is it any way to extend ALL structs in unmarhsaling chain for a additional field when use json.Unmarshal





type Update struct {
UpdateID int32 `json:"update_id"`
Message *Message `json:"message,omitempty"`
...

API `json:"-"`
}

type Message struct {
MessageID int32 `json:"message_id"`
From *User `json:"from,omitempty"`
Date int32 `json:"date"`
Chat Chat `json:"chat"`
...

API `json:"-"`
}



So we can do something like:





var result []Update
err = json.Unmarshal(res.Result, &result, api)
if err != nil {
return nil, fmt.Errorf("getupdates: %s", err)
}



And then use it:





result[0].RejectUpdate()
result[0].Message.SendReply()
...
func (u *Update) RejectUpdate(cause string) {
m.API.SendMessage(u.Chat.ID, text)
m.API.Reject(u.ID, cause)
}
func (m *Message) SendReply(text string) {
m.API.SendMessage(m.Chat.ID, text)
}



All struct will be extended by api (embedded struct) on unmarshaling...



My thoughts about solution:




  1. Patch standard encoding/json library – not a good choice

  2. Get custom encoding library and rewrite a little bit – questionable choice

  3. Manually unmarshal all object – awful code





type Bot struct {
superCLient string
}

type Image struct {
Name string
Path string

*Bot `json:"-"`
}

type FormFile struct {
Img *Image
Name string

*Bot `json:"-"`
}

func main() {
data := []byte(`{"Img": {"Name": "Vi", "Path": "/etc/log"}, "Name": "Josh"}`)
bot := &Bot{
superCLient: "ClientExample",
}
var omg FormFile
omg.CustomUnmarshal(data, bot)
fmt.Println(omg)
}

func (ff *FormFile) CustomUnmarshal(data []byte, bot *Bot) error {
var f map[string]*json.RawMessage
json.Unmarshal(data, &f)

var i Image
i.CustomUnmarshal(*f["Img"], bot)
ff.Img = &i

json.Unmarshal(*f["Name"], ff.Name)

ff.Bot = bot

return nil
}

func (img *Image) CustomUnmarshal(data []byte, bot *Bot) error {
err := json.Unmarshal(data, img)
img.Bot = bot
return err
}

No comments:

Post a Comment

plot explanation - Why did Peaches' mom hang on the tree? - Movies & TV

In the middle of the movie Ice Age: Continental Drift Peaches' mom asked Peaches to go to sleep. Then, she hung on the tree. This parti...