go-ozzo / ozzo-routing

An extremely fast Go (golang) HTTP router that supports regular expression route matching. Comes with full support for building RESTful APIs.
MIT License
455 stars 51 forks source link

Routing.Context.Write() not marshaling JSON as expected #50

Closed byanjiong closed 6 years ago

byanjiong commented 6 years ago

I have a struct Person with custom JSON MarshalJSON and UnmarshalJSON methods. When I use the the Write() in a routing handler, it skips these custom methods and use the default JSON marshal/unmarshal.

func GetPerson (c *routing.Context) error {
    // ...
    return c.Write(p) // print JSON with default JSON method, expect custom marshal to be used
}

However, if I wrap the struct in another struct or slice, e.g. :

type User struct {
    UserID  int    `json:"userID"`
    Details Person `json:"details"`
}

Or

[]Person{}

and c.Write(...) these values, the custom marshal methods is used.

E.g.

func GetPerson (c *routing.Context) error {
    u := User{}
    // ... more code
    return c.Write(u) // the custom marshal methods in Person is used (e.g. Birthday in Person have the correct time format)
}

func GetPeople (c *routing.Context) error {
    people := []Person{}
    // ... more code
    return c.Write(people) // the custom marshal methods is being used too (e.g. time format is converted as expected)
}

Is it a bug? I guess it is something related to http Writter... Hmmm... may please have a look? :)

Note: the Person struct is included below

type Person struct {
    Name     string    `json:"name"`
    Birthday time.Time `json:"birthday"`
    Hobbies  []Sharing `json:"hobbies"`
}

// MarshalJSON marshal struct.
// - Convert empty slice to "[]" instead of "null"
// - Custom manipulation to certain fields
// - Output time.Time in specific time format used by the app
func (m *Person) MarshalJSON() ([]byte, error) {
    type Alias Person
    if m.Hobbies == nil {
        m.Hobbies = make([]Sharing, 0) //convert to empty array if slice is nil
    }

    return json.Marshal(&struct {
        Name     string `json:"name"`
        Birthday string `json:"birthday"`
        *Alias
    }{
        Name:     "Oh, you need to guess it!",  //custom manipulation
        Birthday: app.TimeToString(m.Birthday), //change time format
        Alias:    (*Alias)(m),
    })
}

// UnmarshalJSON unmarshal JSON to struct data
func (m *Person) UnmarshalJSON(data []byte) error {
    type Alias Person
    aux := &struct {
        Birthday string `json:"birthday"`
        *Alias
    }{
        Alias: (*Alias)(m),
    }
    if err := json.Unmarshal(data, &aux); err != nil {
        return err
    }

    t, err = app.StringToTime(aux.Birthday)
    if err != nil {
        return err
    }
    m.Birthday = t

    return nil
}
qiangxue commented 6 years ago

Declare your MarshalJSON as this: func (m Person) MarshalJSON() ([]byte, error) (the method is bound to the struct, not struct pointer)

byanjiong commented 6 years ago

OK, it works... many thanks... ^_^