golang type conversion in unmarshaljson - go

Could someone help me please what's going wrong here? For some reason the output are not the same and I don't get why.
type rTime time.Time
func (rt *rTime) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
t, err := time.Parse("2006-01-02", s)
if err != nil {
return err
}
log.Println(t)
*rt = rTime(t)
log.Println(*rt)
return nil
}
Log looks like this:
2014/09/18 04:31:35 1999-10-15 00:00:00 +0000 UTC
2014/09/18 04:31:35 {63075542400 0 0x933ea0}
Why's the conversion not working? The input string is 1995-10-15 btw.

The conversion is working, but fmt.Println() looks for a String() method, and that exists on time.Time but not on your type. You should need nothing more than func (rt rTime) String() string { return time.Time(rt).String() } to direct String() calls back to time.Time's implementation.
Here's an example:
package main
import (
"log"
"time"
)
type rTime time.Time
func (rt rTime) String() string { return time.Time(rt).String() }
func main() {
s := "1999-10-15"
t, err := time.Parse("2006-01-02", s)
if err != nil {
panic(err)
}
log.Println(t)
rt := rTime(t)
log.Println(rt)
}
Note that I treated both time types as values because the standard library does, per the canonical advice to avoid pointers for tiny structs with value semantics.
Maybe more interesting, you can use type embedding to automagically pick up all of the methods of time.Time except any you override. The syntax changes slightly (see on Playground):
package main
import (
"log"
"time"
)
type rTime struct { time.Time }
func main() {
s := "1999-10-15"
t, err := time.Parse("2006-01-02", s)
if err != nil {
panic(err)
}
log.Println(t)
rt := rTime{t}
log.Println(rt)
}
If you've used embedding and want to write your own custom methods that "proxy through" to the embedded type's, you use a syntax like obj.EmbeddedTypeName.Method, which could be like, for instance, rt.Time.String():
// a custom String method that adds smiley faces
func (rt rTime) String() string { return "😁 " + rt.Time.String() + " 😁" }
obj.EmbeddedTypeName is also how you (for example) access operators on non-struct types that you've embedded.

Related

How do I bind a date string to a struct?

type TestModel struct {
Date time.Time `json:"date" form:"date" gorm:"index"`
gorm.Model
}
i'm using echo framwork, and
I have a struct like the one above, and I get string data like '2021-09-27' , how can I bind it to the struct?
func CreateDiary(c echo.Context) error {
var getData model.TestModel
if err := (&echo.DefaultBinder{}).BindBody(c, &getData); err != nil {
fmt.Print(err.Error())
}
return c.JSON(200, getData)
}
When I code like this, I get the following error:
code=400, message=parsing time "2021-09-27" as "2006-01-02T15:04:05Z07:00": cannot parse "" as "T", internal=parsing time "2021-09-27" as "2006-01-02T15:04:05Z07:00": cannot parse "" as "T"
I'm a golang beginner, can you show me a simple example??, please.
i'm using echo framwork
Type CustomTime time.Time
func (ct *CustomTime) UnmarshalParam(param string) error {
t, err := time.Parse(`2006-01-02`, param)
if err != nil {
return err
}
*ct = CustomTime(t)
return nil
}
ref: https://github.com/labstack/echo/issues/1571
Here is list of available tags used in echo. If you want to parse from body, then use json
query - source is request query parameters.
param - source is route path parameter.
header - source is header parameter.
form - source is form. Values are taken from query and request body. Uses Go standard library form parsing.
json - source is request body. Uses Go json package for unmarshalling.
xml - source is request body. Uses Go xml package for unmarshalling.
You need to wrap time.Time into custom struct and then implement json.Marshaler and json.Unmarshaler interfaces
Example
package main
import (
"fmt"
"strings"
"time"
"github.com/labstack/echo/v4"
)
type CustomTime struct {
time.Time
}
type TestModel struct {
Date CustomTime `json:"date"`
}
func (t CustomTime) MarshalJSON() ([]byte, error) {
date := t.Time.Format("2006-01-02")
date = fmt.Sprintf(`"%s"`, date)
return []byte(date), nil
}
func (t *CustomTime) UnmarshalJSON(b []byte) (err error) {
s := strings.Trim(string(b), "\"")
date, err := time.Parse("2006-01-02", s)
if err != nil {
return err
}
t.Time = date
return
}
func main() {
e := echo.New()
e.POST("/test", CreateDiary)
e.Logger.Fatal(e.Start(":1323"))
}
func CreateDiary(c echo.Context) error {
var getData TestModel
if err := (&echo.DefaultBinder{}).BindBody(c, &getData); err != nil {
fmt.Print(err.Error())
}
return c.JSON(200, getData)
}
test
curl -X POST http://localhost:1323/test -H 'Content-Type: application/json' -d '{"date":"2021-09-27"}'

In golang, implement method on custom type so cast in Println isn't necessary

I'm very new to golang. The code below is modified from:
https://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go
Why is the time.Time() required in the Println call in the last line?
Why does printing the val["created_at"] not produce the same string result? It produces a pointer instead.
You'll see I made a few attempts to create a Println function that works with the custom Timestamp type. Is it possible to define a function on the Timestamp custom type such that the Println functions at the end of the code output the string instead of a pointer?
I think this probably answers my question also:
https://stackoverflow.com/a/6485229/4005067
But is there a way to define some function of the Timestamp type so that the cast is not necessary?
package main
import (
"encoding/json"
"fmt"
"reflect"
"time"
)
// start with a string representation of our JSON data
var input = `
{
"created_at": "Thu May 31 00:00:01 +0000 2012"
}
`
type Timestamp time.Time
func (t *Timestamp) UnmarshalJSON(b []byte) error {
v, err := time.Parse(time.RubyDate, string(b[1:len(b)-1]))
if err != nil {
return err
}
*t = Timestamp(v)
return nil
}
//func (t *Timestamp) Println(a ...interface{}) (n int, err error) {
// return fmt.Println(time.Time(*t))
//}
//func (t Timestamp) String() string {
// return string(t)
//}
func main() {
// our target will be of type map[string]interface{}, which is a pretty generic type
// that will give us a hashtable whose keys are strings, and whose values are of
// type interface{}
var val map[string]Timestamp
if err := json.Unmarshal([]byte(input), &val); err != nil {
panic(err)
}
fmt.Println(val)
for k, v := range val {
fmt.Println(k, reflect.TypeOf(v))
}
fmt.Println(val["created_at"])
fmt.Println(reflect.TypeOf(val["created_at"]))
fmt.Println(Timestamp(val["created_at"]))
fmt.Println(time.Time(val["created_at"]))
}
Output on the go playground is:
map[created_at:{0 63474019201 0x5b0580}]
created_at main.Timestamp
{0 63474019201 0x5b0580}
main.Timestamp
{0 63474019201 0x5b0580}
2012-05-31 00:00:01 +0000 UTC
Defining a String method is one way to do it, just like you tried above.
With some small modification we can make it work.
func (t Timestamp) String() string {
return time.Time(t).String()
}

How to omit empty json fields using json.decoder

I try to understand why both functions return the same output.
As far as I understood, the point of omit empty is to not add that key to the result struct.
I wrote this example, I was expecting the first output not to have the "Empty" key, but for some reason its value still shows as 0.
package main
import (
"encoding/json"
"fmt"
"strings"
)
type agentOmitEmpty struct {
Alias string `json:"Alias,omitempty"`
Skilled bool `json:"Skilled,omitempty"`
FinID int32 `json:"FinId,omitempty"`
Empty int `json:"Empty,omitempty"`
}
type agent struct {
Alias string `json:"Alias"`
Skilled bool `json:"Skilled"`
FinID int32 `json:"FinId"`
Empty int `json:"Empty"`
}
func main() {
jsonString := `{
"Alias":"Robert",
"Skilled":true,
"FinId":12345
}`
fmt.Printf("output with omit emtpy: %v\n", withEmpty(strings.NewReader(jsonString)))
// output with omit emtpy: {Robert true 12345 0}
fmt.Printf("output regular: %v\n", withoutEmpty(strings.NewReader(jsonString)))
// output without omit: {Robert true 12345 0}
}
func withEmpty(r *strings.Reader) agentOmitEmpty {
dec := json.NewDecoder(r)
body := agentOmitEmpty{}
err := dec.Decode(&body)
if err != nil {
panic(err)
}
return body
}
func withoutEmpty(r *strings.Reader) agent {
dec := json.NewDecoder(r)
body := agent{}
err := dec.Decode(&body)
if err != nil {
panic(err)
}
return body
}
You need to define Empty as *int so it will be replaced with nil when there is no value. Then it will not be saved in the database.

Create custom type that will seem like the another when checking types Golang

I am a experienced python programmer but I am still new to Golang so my apologies if this is an obvious or silly question. But I am trying to create my own type that I want to act exactly like the base type with the exception of several methods being overridden. The reason for this is because several libraries I am using are checking the type against time.Time and I want it to match.
type PythonTime struct {
time.Time
}
var pythonTimeFormatStr = "2006-01-02 15:04:05-0700"
func (self *PythonTime) UnmarshalJSON(b []byte) (err error) {
// removes prepending/trailing " in the string
if b[0] == '"' && b[len(b)-1] == '"' {
b = b[1 : len(b)-1]
}
self.Time, err = time.Parse(pythonTimeFormatStr, string(b))
return
}
func (self *PythonTime) MarshalJSON() ([]byte, error) {
return []byte(self.Time.Format(pythonTimeFormatStr)), nil
}
type OtherType struct {
Uuid string `json:"uuid`
Second PythonTime `json:"second"`
Location string `json:"location"`
Action string `json:"action"`
Duration int `json:"duration"`
Value string `json:"value"`
}
So the the above works fine for marshalling and unmarshalling JSON. However, for my library that I am using (gocql and cqlr) they are checking if the type is a time.Time type so they can make some other modifications before putting it in C*. How do I get my PythonTime type to equate to either show as time.Time or override the default marshalling/unmarshalling for a time.Time object just for the use of my OtherType objects?
My temporary solution has been to modify their code and add a special case for the PythonTime type that does the same thing as the time.Time type. However, this is causing me circular imports and is not the best solution. Here is their code with my modifications.
func marshalTimestamp(info TypeInfo, value interface{}) ([]byte, error) {
switch v := value.(type) {
case Marshaler:
return v.MarshalCQL(info)
case int64:
return encBigInt(v), nil
case time.Time:
if v.IsZero() {
return []byte{}, nil
}
x := int64(v.UTC().Unix()*1e3) + int64(v.UTC().Nanosecond()/1e6)
return encBigInt(x), nil
case models.PythonTime:
x := int64(v.UTC().Unix()*1e3) + int64(v.UTC().Nanosecond()/1e6)
return encBigInt(x), nil
}
if value == nil {
return nil, nil
}
rv := reflect.ValueOf(value)
switch rv.Type().Kind() {
case reflect.Int64:
return encBigInt(rv.Int()), nil
}
return nil, marshalErrorf("can not marshal %T into %s", value, info)
}
Don't do this. You're checking for a time.Time object when you should be checking that it satisfies an interface.
type TimeLike interface {
Day() int
Format(string) string
... // whatever makes a "time" object to your code!
// looks like in this case it's
UTC() time.Time
IsZero() bool
}
then any code that expects a time.Time that can be substituted with a PythonTime, expect a TimeLike instead.
function Foo(value interface{}) int {
switch v := value.(type) {
case TimeLike:
return v.Day() // works for either time.Time or models.PythonTime
}
return 0
}
Just like you have done with the json.Marshaler and json.Unamrshaler, you can also implement the gocql.Marshaler gocql.Unamrshaler interfaces.
func (t *PythonTime) MarshalCQL(info gocql.TypeInfo) ([]byte, error) {
b := make([]byte, 8)
x := t.UnixNano() / int64(time.Millisecond)
binary.BigEndian.PutUint64(b, uint64(x))
return b, nil
}
func (t *PythonTime) UnmarshalCQL(info gocql.TypeInfo, data []byte) error {
x := int64(binary.BigEndian.Uint64(data)) * int64(time.Millisecond)
t.Time = time.Unix(0, x)
return nil
}
(note, untested in the context of CQL, but this does round-trip with itself)
Unfortunately, that will not work in Go. Your best option would be to create some import and export methods, so that you can cast your PythonTime to a time.Time and vice versa. That will give you flexibility you desire along with compatibility with other libraries.
package main
import (
"fmt"
"reflect"
"time"
)
func main() {
p, e := NewFromTime(time.Now())
if e != nil {
panic(e)
}
v, e := p.MarshalJSON()
if e != nil {
panic(e)
}
fmt.Println(string(v), reflect.TypeOf(p))
t, e := p.GetTime()
if e != nil {
panic(e)
}
fmt.Println(t.String(), reflect.TypeOf(t))
}
type PythonTime struct {
time.Time
}
var pythonTimeFormatStr = "2006-01-02 15:04:05-0700"
func NewFromTime(t time.Time) (*PythonTime, error) {
b, e := t.GobEncode()
if e != nil {
return nil, e
}
p := new(PythonTime)
e = p.GobDecode(b)
if e != nil {
return nil, e
}
return p, nil
}
func (self *PythonTime) GetTime() (time.Time, error) {
return time.Parse(pythonTimeFormatStr, self.Format(pythonTimeFormatStr))
}
func (self *PythonTime) UnmarshalJSON(b []byte) (err error) {
// removes prepending/trailing " in the string
if b[0] == '"' && b[len(b)-1] == '"' {
b = b[1 : len(b)-1]
}
self.Time, err = time.Parse(pythonTimeFormatStr, string(b))
return
}
func (self *PythonTime) MarshalJSON() ([]byte, error) {
return []byte(self.Time.Format(pythonTimeFormatStr)), nil
}
That should give output like this:
2016-02-04 14:32:17-0700 *main.PythonTime
2016-02-04 14:32:17 -0700 MST time.Time

Timestamps in Golang

Trying to get this approach to timestamps working in my application: https://gist.github.com/bsphere/8369aca6dde3e7b4392c#file-timestamp-go
Here it is:
package timestamp
import (
"fmt"
"labix.org/v2/mgo/bson"
"strconv"
"time"
)
type Timestamp time.Time
func (t *Timestamp) MarshalJSON() ([]byte, error) {
ts := time.Time(*t).Unix()
stamp := fmt.Sprint(ts)
return []byte(stamp), nil
}
func (t *Timestamp) UnmarshalJSON(b []byte) error {
ts, err := strconv.Atoi(string(b))
if err != nil {
return err
}
*t = Timestamp(time.Unix(int64(ts), 0))
return nil
}
func (t Timestamp) GetBSON() (interface{}, error) {
if time.Time(*t).IsZero() {
return nil, nil
}
return time.Time(*t), nil
}
func (t *Timestamp) SetBSON(raw bson.Raw) error {
var tm time.Time
if err := raw.Unmarshal(&tm); err != nil {
return err
}
*t = Timestamp(tm)
return nil
}
func (t *Timestamp) String() string {
return time.Time(*t).String()
}
and the article that goes with it: https://medium.com/coding-and-deploying-in-the-cloud/time-stamps-in-golang-abcaf581b72f
However, I'm getting the following error:
core/timestamp/timestamp.go:31: invalid indirect of t (type Timestamp)
core/timestamp/timestamp.go:35: invalid indirect of t (type Timestamp)
My relevant code looks like this:
import (
"github.com/path/to/timestamp"
)
type User struct {
Name string
Created_at *timestamp.Timestamp `bson:"created_at,omitempty" json:"created_at,omitempty"`
}
Can anyone see what I'm doing wrong?
Related question
I can't see how to implement this package either. Do I create a new User model something like this?
u := User{Name: "Joe Bloggs", Created_at: timestamp.Timestamp(time.Now())}
Your code has a typo. You can't dereference a non-pointer, so you need to make GetBSON a pointer receiver (or you could remove the indirects to t, since the value of t isn't changed by the method).
func (t *Timestamp) GetBSON() (interface{}, error) {
To set a *Timestamp value inline, you need to have a *time.Time to convert.
now := time.Now()
u := User{
Name: "Bob",
CreatedAt: (*Timestamp)(&now),
}
Constructor and a helper functions like New() and Now() may come in handy for this as well.
You cannot refer to an indirection of something that is not a pointer variable.
var a int = 3 // a = 3
var A *int = &a // A = 0x10436184
fmt.Println(*A == a) // true, both equals 3
fmt.Println(*&a == a) // true, both equals 3
fmt.Println(*a) // invalid indirect of a (type int)
Thus, you can not reference the address of a with *a.
Looking at where the error happens:
func (t Timestamp) GetBSON() (interface{}, error) {
// t is a variable type Timestamp, not type *Timestamp (pointer)
// so this is not possible at all, unless t is a pointer variable
// and you're trying to dereference it to get the Timestamp value
if time.Time(*t).IsZero() {
return nil, nil
}
// so is this
return time.Time(*t), nil
}

Resources