2

My question is very similar to this one, however instead of converting a float64 to a string which is handled using the string tag. In my case I'm trying to convert a ObjectID to a string using it's .Hex() method. And, vice versa using the .FromHex() function on the way back.

However, more generically how do I convert from type X to type Y & back during the Marshal & Unmarshaling?

My Example:

package main

import (
    "log"
    "fmt"
    "encoding/json"
    "github.com/mongodb/mongo-go-driver/bson/objectid"
)

type Greeting struct {
    Id          *objectid.ObjectID  `json:"id"`
    Greeting    string              `json:"greeting,omitempty"`
}


func main() {
    // Create ObjectID
    id, err := objectid.FromHex("5b14dd20f6418c8443a5ffec")
    if err != nil { log.Fatal(err) }

    // Create Greeting
    g := Greeting{&id, "Hello, World!"}

    // Marshal to json
    j, err := json.Marshal(g)
    if err != nil { log.Fatal(err) }

    // Print Json
    fmt.Printf("Json: %s", string(j))

}

Output:

Json: {"id":[91,20,221,32,246,65,140,132,67,165,255,236],"greeting":"Hello, World!"}

Whereas, what I'd like is:

Json: {"id":"5b14dd20f6418c8443a5ffec","greeting":"Hello, World!"}
Aaron N. Brock
  • 4,276
  • 2
  • 25
  • 43

2 Answers2

3

You should write your own (un)marshalling function in which you process your conversions/conditions and handle it as argument in calling the json (un)marshalling.

type Whatever struct {
   someField int
}

func (w Whatever) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct{
        SomeField int `json:"some_field"`
    }{
        SomeField: w.someField,
    })
}

func (w Whatever) MarshalJSON() ([]byte, error) {
   return json.Marshal(map[string]interface{}{
       "some_field": w.SomeField,
   })
}
Bert Verhees
  • 1,057
  • 3
  • 14
  • 25
3

You can declare a named type to objectid.ObjectId that would implement the json.Marshaler interface to override the standard marshaling and output the hex string instead:

type Id objectid.ObjectId

func (id Id) MarshalJSON() ([]byte, error) {
    return json.Marshal(objectid.ObjectId(id).Hex())
}

And use it instead of objectid.ObjectId in your structs:

type Greeting struct {
    Id Id
    Greeting string
}

Now, as you marshal a Greeting, the json package would marshal the Greeting field as expected, but the since the Id field implements json.Marshaler, it would be marshaled by Id.MarshalJSON which would output id.Hex().

You can do the same for unmarshaling by implementing the json.Unmarshaler interface for Id.

Zippo
  • 15,850
  • 10
  • 60
  • 58
  • I'm using the same struct to retrieve data from MongoDB, so I don't really have control over the types in the struct. (Or at least, I'd have to deal with this same issue from the other end) – Aaron N. Brock Jun 05 '18 at 02:44
  • This is **not** an alias! `Id`is just a named type with `objectid.ObjectId`being the underlying type. This is not aliasing! – Volker Jun 05 '18 at 04:00
  • @Volker Thanks, fixed. – Zippo Jun 05 '18 at 06:40