重命名JSON字段

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.

Go Playground Link

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": { ... }
}