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.
Related
Say I create a custom type in Go:
type CustomTime time.Time
Using reflection, I'm comparing types, e.g.
var foo CustomTime = CustomTime(time.Now())
customType := reflect.TypeOf(foo)
timeType := reflect.TypeOf(time.Now())
if customType == timeType {
fmt.Println("Is timeType")
} else {
fmt.Println("Is not timeType")
}
This prints "Is not timeType". What I'm trying to do is find a way to see if the base type that the custom type uses (i.e. the time.Time in type CustomType time.Time) is of type time.Time.
I've tried using reflect's Kind() function, but that returns struct for both since time.Time is a struct.
Here's a playground with this code.
You can't exactly, because that's not how types work in Go. In your example, CustomTime's underlying type isn't Time; both CustomTime and time.Time share the same underlying type, which is a struct type. From the docs:
Each type T has an underlying type: If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself. Otherwise, T's underlying type is the underlying type of the type to which T refers in its type declaration.
That means that CustomTimes underlying type isn't time.Time, it's time.Time's underlying type.
You can use reflect.Type's ConvertibleTo() method to see if one type is convertible to another, which is as close as you're likely to get to what you're describing.
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 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.
In Go, I get the json marshalling/unmarshalling. If a struct or type has a MarshalJSON method, when calling json.Marshal on another struct which has the former as a field, that structs's MarshalJSON method will be called. So from what I gather and have seen in practice...
type MyType struct has a MarshalJSON method to marshal itself a string.
type MyDocument struct has MyType as a field.
When calling json.Marshal() on MyDocument, the MyType field will be marshalled as a string due to it implementing json.Marshaller.
I'm trying to make my system database-agnostic and am implementing a service for MongoDB using the mgo driver, which means implementing bson.Getter and bson.Setter on all structs and things I want marshalled in a specific way. This is where it gets confusing.
Because Go doesn't have native fixed-point arithmetic, I'm using Shopspring's decimal package (found here) to deal with currency values. Decimal marshals to JSON perfectly but I have a named type type Currency decimal.Decimal which I just can't get to marshal down to BSON.
These are my implementations which convert the decimal to a float64 and try marshalling that in the same way I've done for json:
/*
Implements the bson.Getter interface.
*/
func (c Currency) GetBSON() (interface{}, error) {
f, _ := decimal.Decimal(c).Float64()
return f, nil
}
/*
Implements the bson.Setter interface.
*/
func (c *Currency) SetBSON(raw bson.Raw) error {
var f float64
e := raw.Unmarshal(&f)
if e == nil {
*c = Currency(decimal.NewFromFloat(f))
}
return e
}
Only problem is in the documentation for the bson package:
Marshal serializes the in value, which may be a map or a struct value.
Because it's not a struct or a map it just produces an empty document.
I'm just trying to marshal one bit of data which will only need to be marshalled as part of larger structs, but the package will only let me do it on whole documents. What should I do to get the results I need?
Here is a basic go program
package main
import (
"fmt"
"time"
)
type myTime time.Time
func main() {
my := myTime(time.Now())
fmt.Println(my)
normal := time.Now()
fmt.Println(normal)
}
And the corresponding output
{63547112172 291468455 0x545980}
2014-09-23 23:36:12.292132305 +0000 UTC
I would like to know why myTime prints diffrently than time.Time. They basically are supposed to be from the same type... Also, if I try to access any method of time.Time, let's say, Day, it's available for "normal" but not for "my".
Thanks!
Your new type does not inherit methods from time.Time. To quote the spec:
The declared type does not inherit any methods bound to the existing type
Since there is no String method, it won't print a meaningful value. You need to implement that yourself.
Your other option is to embed time.Time in your own type. That way you can include the functionality of time.Time but also add your own functionality.
Playground link: http://play.golang.org/p/PY6LIBoP6H
type myTime struct {
time.Time
}
func (t myTime) String() string {
return "<Custom format here>"
}
func main() {
my := myTime{time.Now()}
fmt.Println(my)
normal := time.Now()
fmt.Println(normal)
}
fmt.Println uses the String() method (or rather the fmt.Stringer interface) when formatting a type as a string, if it is available. When you create a new type using an underlying type (in your case time.Time):
type myTime time.Time
You will not inherit the methodset of the underlying type. Therefore, myTime has no String() method, so fmt will use the default format for a struct.