Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with using custom JSON Marshal() for Embedded Structs

Tags:

json

struct

go

I am attempting to define a custom JSON marshaler to display some time information in specific formats. Ideally, I'd like to have a struct that stores created/modified values and then embed them into structs that need to keep track of that information. In addition, I want to define a custom date format in the JSON marshaler to use from a client-side application.

I currently have two structs

type Timestamp struct {
    Created time.Time
    Modified time.Time
}

type Company struct {
    Id string
    Name string
    Timestamp    
}

I want to embed the Timestamp struct into objects that will need to record when items are updated/created. Nothing insane there.

My issues comes when I define

func (t Timestamp) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        CreatedFormatted string
    }{
        CreatedFormatted: t.Created.Format("Monday Jan _2 15:04:05 2006"),
    })
}

When I go to marshal Company, I only see the json for the Timestamp but nothing for the Company. I would have thought that the contents of the Company struct and the Timestamp struct would have been displayed. Am I doing something wrong here?

like image 683
John F. Avatar asked Nov 13 '15 22:11

John F.


2 Answers

When a type embedded in a struct provides a method, that method becomes a part of the embedding struct. Since Company embeds Timestamp, Timestamp's MarshalJSON is available for Company as well. When json is looking to marshal a Company it sees that it has a MarshalJSON method and calls it — and the method it finds only marshals the timestamp field. The default behavior of structs (to marshal each field into its own key in a JSON object) is overridden.

What you can do:

  1. Don't use struct embedding, just make Timestamp a regular field (even if you declare it as Timestamp Timestamp). Then Company won't inherit its methods and JSON will work as expected (but other parts of your code that expect embedding might change). Or:
  2. Give Company its own MarshalJSON method that marshalls all of the fields including the timestamp. You could do this by
    a. Copying the fields into a different type that is identical to Company but without the embedding, and marshalling that.
    b. Copying the fields into a map and marshalling that.
    c. Marshalling each of the fields independently and string-pasting fmt.Sprintf({"key1":%s,"key2":%s,...}, m1, m2, ...) yourself.
like image 106
hobbs Avatar answered Nov 11 '22 20:11

hobbs


By embedding Timestamp in Company you not only shared member variables, but also the methods. This means you provided Company.MarshalJSON method, which is then used by json package for marshalling the whole structure. In order to see all the fields you would need to implement an explicit marshaler for Company structure as well.

If you only want to format the timestamp in a specific way, the other solution would be to provide your own time.Time and providing JSON marshaller there.

For example:

type AccessTime time.Time

func (t AccessTime) MarshalJSON() ([]byte, error) {
    return json.Marshal(time.Time(t).Format("Monday Jan _2 15:04:05 2006"))
}

type Timestamp struct {
    Created  AccessTime
    Modified AccessTime
}

https://play.golang.org/p/PhZXPauSyz

like image 42
tomasz Avatar answered Nov 11 '22 19:11

tomasz