We want to rename a JSON field value
to v
in production. Till all our users use the new struct, we would continue to get old JSON structs into our code. So we want to handle this as well.
If you notice, First
is the original structure, Second
is the new structure. To handle both these structures, I have created a MyStruct
and based on version
, I copy the OldValue
into Value
if m.Version <= 1 {
m.Value = m.OldValue
}
Is there a better way to handle this, instead of my code.
package main
import "fmt"
import "encoding/json"
import "log"
type First struct {
Version int `json:"version"`
Value int `json:"value"`
}
type Second struct {
Version int `json:"version"`
Value int `json:"v"`
}
type MyStruct struct {
Version int `json:"version"`
OldValue int `json:"value"`
Value int `json:"v"`
}
func main() {
oldValue := []byte(`{"version":1, "value":5}`)
newValue := []byte(`{"version":2, "v":7}`)
var m MyStruct
err := json.Unmarshal(newValue, &m)
if err != nil {
log.Fatal(err)
}
fmt.Println("New Struct")
fmt.Println(m.Value)
err = json.Unmarshal(oldValue, &m)
if err != nil {
log.Fatal(err)
}
fmt.Println("Old Struct")
if m.Version <= 1 {
m.Value = m.OldValue
}
fmt.Println(m.Value)
}
EDIT: You can actually do it with one unmarshaling, albeit you'll need another type:
type Second struct {
Version int `json:"version"`
Value int `json:"v"`
}
type SecondWithOldValue struct {
OldValue int `json:"value"`
Second
}
type MyStruct SecondWithOldValue
func (v *MyStruct) UnmarshalJSON(b []byte) error {
if err := json.Unmarshal(b, (*SecondWithOldValue)(v)); err != nil {
return err
}
if v.Version <= 1 {
v.Value = v.OldValue
}
return nil
}
Playground: https://play.golang.org/p/yII-ncxnU4.
Old answer below.
If you're OK with double unmarshaling, you can do it like this:
type Second struct {
Version int `json:"version"`
Value int `json:"v"`
}
type MyStruct struct {
Second
}
func (v *MyStruct) UnmarshalJSON(b []byte) error {
if err := json.Unmarshal(b, &v.Second); err != nil {
return err
}
if v.Version <= 1 {
var oldV struct{ Value int }
if err := json.Unmarshal(b, &oldV); err != nil {
return err
}
v.Value = oldV.Value
}
return nil
}
First, unmarshal into the inner struct, check version, and if it's an old one, get the old value.
Playground: https://play.golang.org/p/AaULW6vJz_.
I would opt to not use 3 different struct here as they are all the same really.
Modify your struct MyStruct as you have and load the json:"value"
into a new variable as you are and part of your Unmarshal should copy the value into the Value variable if Value is not present.
However I would second Kostix's recommendation here. Your API or process that loads and saves data should try to account for versioning by the mechanism suggested. Add a v# to your URI or if you're saving the item down to disk, then perhaps save it with a version number as such and then process the inner section per struct that corresponds to the proper version:
{
"version": 1,
"struct": { ... }
}