I want to encode my time.Time fields as numeric Unix time and I would prefer not to implement custom MarshalJSON functions for each and every struct, since I have lots and lots of structs.
So, I tried defining a type alias as such:
type Timestamp time.Time
And implementing MarshalJSON on it like so:
func (t Timestamp) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatInt(t.Unix(), 10)), nil
}
But that gives me a t.Unix undefined (type Timestamp has no field or method Unix), which doesn't make sense to me. Shouldn't Timestamp 'inherit' (I know that's probably the wrong term) all functions of time.Time?
I also tried using a type assertion like so:
strconv.FormatInt(t.(time.Time).Unix(), 10)
But that also fails, complaining about an invalid type assertion: invalid type assertion: t.(time.Time) (non-interface type Timestamp on left)
You need to convert your type back to a time.Time to have access to its methods. Named types do not "inherit" the methods of their underlying types (to do that, you need embedding).
func (t Timestamp) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatInt(time.Time(t).Unix(), 10)), nil
}
Also, just as a matter of personal preference, I tend to preferred fmt.Sprintf("%v", i) over strconv.FormatInt(i, 10) or even strconv.Itoa(i). Honestly not sure which is faster, but the fmt version seems easier to read, personally.
Related
I need to read dates from a db, convert it to a certain timestamp and convert it to JSON.
I have the following code:
package usages
import (
"fmt"
"time"
)
type SpecialOffer struct {
PublishedDate jsonTime `gorm:"column:publishing_date" json:"published_date"`
ExpirationDate jsonTime `gorm:"column:expiration_date" json:"expiration_date"`
}
type jsonTime struct {
time.Time
}
func (tt jsonTime) MarshalJSON() ([]byte, error) {
jsonTime := fmt.Sprintf("\"%s\"", tt.Format("20060102"))
return []byte(jsonTime), nil
}
When I run it like this I get the following error:
sql: Scan error on column index 8, name "publishing_date": unsupported Scan, storing driver.Value type time.Time into type *usages.trvTime
And the data is wrong:
{"published_date":"00010101","expiration_date":"00010101"}
If I change the SpecialOffer struct to use time.Time, it return correct, but obviously the format is wrong:
{"published_date":"2020-03-12T00:00:00Z","expiration_date":"2020-06-12T00:00:00Z"}
What am I doing wrong?
You should implement the sql.Scanner and driver.Valuer interfaces.
Something like this:
func (j *jsonTime) Scan(src interface{}) error {
if t, ok := src.(time.Time); ok {
j.Time = t
}
return nil
}
func (j jsonTime) Value() (driver.Value, error) {
return j.Time, nil
}
This is necessary because the database/sql package which is used by gorm and some other go ORMs, if not all of them, provides out-of-the-box support for only a handful of types.
Most of the supported types are the language's basic builtin types like string, int, bool, etc. by extension it also supports any custom user defined type whose underlying type is one of the aforementioned basic types, then there's supports for the []byte type and the related sql.RawBytes type, and lastly the time.Time type is also supported ootb.
Any other type that you may want to write to or read from the database will need to implement the two interfaces above. The sql.Scanner's Scan method is invoked automatically after a column's value is decoded into one of the supported types (that's why you need to type assert against time.Time rather than against, say []byte). The driver.Valuer's Value method is invoked automatically before the driver encodes it into a format that's valid for the target column (that's why you can return time.Time directly rather than having the do the encoding yourself).
And keep in mind that
type jsonTime struct {
time.Time
}
or even
type jsonTime time.Time
declares a new type that is not equal to time.Time and that is why it's not picked up by the database/sql package.
I have a struct that consists of a custom time.Time defined for the sake of it having a custom MarshalJSON() interface, following this answer's suggestion:
type MyTime time.Time
func (s myTime) MarshalJSON() ([]byte, error) {
t := time.Time(s)
return []byte(t.Format(`"20060102T150405Z"`)), nil
}
I define a MyStruct type with ThisDate and ThatDate fields of type *MyTime:
type MyStruct struct {
ThisDate *MyTime `json:"thisdate,omitempty"`
ThatDate *MyTime `json:"thatdate,omitempty"`
}
As far as I understand, I need to use *MyTime and not MyTime so the omitempty tag will have an effect when I'll MarshalJSON a variable of this type, following this answer's suggestion.
I use a library that has a function that returns me a struct with some fields of type *time.Time:
someVar := Lib.GetVar()
I tried to define a variable of type MyStruct like this:
myVar := &MyStruct{
ThisDate: someVar.ThisDate
ThatDate: someVar.ThatDate
}
Naturally, it gives me a compilation error:
cannot use someVar.ThisDate (variable of type *time.Time) as *MyTime value in struct literal ...
I tried typecasting someVar.ThisDate with */& and without these without luck. I thought the following would work:
myVar := &MyStruct{
ThisDate: *MyTime(*someVar.ThisDate)
ThatDate: *MyTime(*someVar.ThatDate)
}
But it gives me a different compilation error:
invalid operation: cannot indirect MyTime(*someVar.ThisDate) (value of type MyTime) ...
It seems I probably lack basic understanding of pointers and dereferences in Go. Never the less, I would like to avoid finding a specific solution for my issue which comes down to the combination of the need to make omitempty have an effect and a custom MarshalJSON.
The problem is the ambiguous syntax of *T(v) or whatever else you tried there. The Golang's spec gives useful examples for type conversions as this, quoting:
*Point(p) // same as *(Point(p))
(*Point)(p) // p is converted to *Point
Therefor, since *Point is needed, *T(v) should be used.
I am trying to write a function which could end up taking any kind of struct... let's say it is like this :
func setDate(s timestamp, data interface{}){
data.Date = timestamp
}
I realize that I wouldn't need a function to set this value in real life, I am trying to learn more about how interfaces work, etc.
You could approach it that way, but then inside setDate() you would need to use reflection to set the Date field. Go is a statically typed language, so if the (static) type of data is interface{} (which says nothing about it), you can't really do anything useful with it (you can't refer to its Date field, because there is no guarantee that its value has a Date field).
Instead you should define a HasDate interface which contains a single method:
type HasDate interface {
SetDate(s time.Time)
}
The ability to set the date. And your function should expect a value of this interface type:
func setDate(s time.Time, data HasDate) {
data.SetDate(s)
}
Anyone who implements this HasDate interface can be passed to your setDate() function. Note that in Go implementing interfaces is implicit: there is no declaration of intent. This means any type that has a SetDate(time.Time) method implements this HasDate interface without even knowing this interface exists.
This is an example type that implements it (more precisely its pointer *MyType):
type MyType struct {
date time.Time
}
func (mt *MyType) SetDate(s time.Time) {
mt.date = s
}
Example testing it (try it on the Go Playground):
mt := &MyType{}
setDate(time.Now(), mt)
fmt.Println(mt.date)
I have been trying to work out how to get this to work, and I am stuck.
I have an object that looks like this:
type PropSet map[string]*Prop
type Prop struct {
val reflect.Value
}
and I need to generate a JSON representation of all the key value pairs that it holds. I have been reading posts on SO talking about how to marshal more mundane types, but I have not been able to figure out how to deal with the reflect.Value type. I think I should be able to do something simple like this:
func (p Prop) MarshalJSON() ([]byte, error) {
return json.Marshal(p.val.Value().Interface())
}
... but it just isn't working. Any suggestions?
Additional note: I didn't write the data structure, but the reason that I think it is using the reflect.Value for the map value is that the values that we are expecting can be ints, floats, strings etc. So this is essentially needs to do some sort of type inference with base interface to figure out the return result.
You're almost there: reflect.Value doesn't itself have a Value receiver method, nor does it need one. Changing your MarshalJSON implementation to the following works:
func (p Prop) MarshalJSON() ([]byte, error) {
return json.Marshal(p.val.Interface())
}
(i.e. dropping .Value() from the chain of function calls).
Playground link
(I don't like the use of reflect here – solutions relying on reflection are rarely clear and understandable, but it seems you can't change the upstream data structure, besides choosing not to use it.)
How to convert value type by another value's reflect.Type in Golang
maybe like this:
func Scan(value interface{}, b string) error {
converted := value.(reflect.TypeOf(b)) // do as "value.(string)"
return nil
}
How can do this properly in golang?
The only way to get a typed value out of an interface is to use a type assertion, and the syntax is value.(T) where T is a type. There's a good reason for this, because it makes the type of the type assertion expression computable: value.(T) has type T. If instead, you allowed value.(E) where E is some expression that evaluates to a reflect.Type (which I think is the gist of your question), then the compiler has no way to (in general) statically determine the type of value.(E) since it depends on the result of an arbitrary computation.