Gaurav Ojha Gaurav Ojha - 1 month ago 24
JSON Question

Golang bson structs - use multiple field names for a single field in json but only one for writing to the database

I have a struct like this -

type Address struct {
AddressLine1 string `json:"addressLine1" bson:"addressLine1"`
AddressLine2 string `json:"addressLine2" bson:"addressLine2"`
Landmark string `json:"landmark" bson:"landmark"`
Zipcode string `json:"zipcode" bson:"zipcode"`
City string `json:"city" bson:"city"`
}


Due to some compatibility issues between the previous build and the latest yet-to-be-released build, I want to make sure that if someone posts json data that decodes using this struct they should be able to use either 'zipcode' or 'pincode' as the field name in their json. But when this value is written to my database, the field name should only be 'zipcode'.

In short,

{
"city": "Mumbai",
"zipcode": "400001"
}


or

{
"city": "Mumbai",
"pincode": "400001"
}


should both appear inside the database as -

{
"city": "Mumbai",
"zipcode": "400001"
}


How do I allow this?

Answer

You can have both fields as pointer to string:

type Address struct {
    AddressLine1 string        `json:"addressLine1" bson:"addressLine1"`
    AddressLine2 string        `json:"addressLine2" bson:"addressLine2"`
    Landmark     string        `json:"landmark" bson:"landmark"`
    Zipcode      *string       `json:"zipcode,omitempty" bson:"zipcode"`
    Pincode      *string       `json:"pincode,omitempty" bson:"zipcode"`
    City         string        `json:"city" bson:"city"`
}

As you may note we're using omitempty in the json tag, so if one of the fields is not present in the json it will be ignored as a nil pointer and it will not be present after Marshal() or Unmarshal()

Edit:

In this case all we have to do is implement the method UnmarshalJSON([]byte) error to satisfy the interface Unmarshaler, the method json.Unmarshal() will always try to call that method and we can add our own logic after Unmarshal the struct, in this case we want to know if pincode is settled if it's we assigned to zipcode: full example here: https://play.golang.org/p/zAOPMtCwBs

type Address struct {
    AddressLine1 string  `json:"addressLine1" bson:"addressLine1"`
    AddressLine2 string  `json:"addressLine2" bson:"addressLine2"`
    Landmark     string  `json:"landmark" bson:"landmark"`
    Zipcode      *string `json:"zipcode,omitempty" bson:"zipcode"`
    Pincode      *string `json:"pincode,omitempty"`
    City         string  `json:"city" bson:"city"`
}

// private alias of Address to avoid recursion in UnmarshalJSON()
type address Address

func (a *Address) UnmarshalJSON(data []byte) error {
    b := address{}
    if err := json.Unmarshal(data, &b); err != nil {
        return nil
    }
    *a = Address(b) // convert the alias to address

    if a.Pincode != nil && a.Zipcode == nil {
        a.Zipcode = a.Pincode
        a.Pincode = nil // unset pincode
    }

    return nil
}

Note that the field Zipcode has a bson tag and Pincode not, also we have to create an alias of type address to avoid calling UnmarshalJSON recursively