I'm unmarshalling a GET request from a JSON API. Soem of the fields are a Unix timestamp (in ms). For those fields, I wrote a method to handle unmarshalling. For that to work, I needed to create a new local type Datetime.
type Datetime time.Time
type Response struct {
Status string `json:"status"`
CreatedAt Datetime `json:"created_at"`
}
// Handles unmarshalling of Time from the API
func (dt *Datetime) UnmarshalJSON(b []byte) error {
var unixTimeMs int64
if err := json.Unmarshal(b, &unixTimeMs); err != nil {
return err
}
*dt = Datetime(time.Unix(0, unixTimeMs*1000000))
return nil
}
Now that I have this new Response object, I'd like to call time.Time methods on the Datetime. I've tried casting the object (i.e. response.CreatedAt.(time.Time)), but that leads to a panic.
How can I call methods like time.Time#string on Datetime?
Convert the Datetime to time.Time to call a Time method. Example:
fmt.Println(time.Time(dt).String())
Another approach is use a struct with an embedded time field:
type Datetime struct {
time.Time
}
All time.Time methods are promoted to the Datetime type. Modify the UnmarshalJSON method to set dt.Time instead of *dt.
Related
Given the code below, is it possible to transform first name while it's being unmarshaled? Say I want to always have it be lowercase whether it is in the actual json or not.
type Person struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}
func main() {
jsonText := GetJsonFromSomewhere()
var v Person
json.Unmarshal(jsonText, &v)
}
One way to do this is to create a custom type that implements the Unmarshaler interface from the encoding/json package. Here's a link to this interface. Any type which implements Unmarshaler can be used as the type of a struct field when doing JSON unmarshalling. When doing the unmarshalling, encoding/json will use your implementation of the interface's UnmarshalJSON function to convert the JSON bytes to the field type.
So you could write an UnmarshalJSON function which includes changing the string values to lowercase.
Here's an example of what that could look like:
type LowerCaseString string
func (l *LowerCaseString) UnmarshalJSON(bytes []byte) error {
lowerCasedString := strings.ToLower(string(bytes))
*l = LowerCaseString(lowerCasedString)
return nil
}
Then, in your struct for JSON mapping, you can use your custom type instead of string:
type Person struct {
FirstName LowerCaseString `json:"first_name"`
LastName LowerCaseString `json:"last_name"`
}
If you unmarshal into this struct, the values of FirstName and LastName will be lowercased (also note that you'll need to type convert them back to string to use them as strings).
testJSON := `{"first_name" : "TestFirstNAME", "last_name": "TestLastNAME"}`
var result Person
err := json.Unmarshal([]byte(testJSON), &result)
if err != nil { /*handle the error*/ }
fmt.Println(result.FirstName) // prints "testfirstname"
var stringLastName string
stringLastName = string(result.LastName) // need to type convert from LowerCaseString to string
fmt.Println(stringLastName) // prints "testlastname"
Here is the above code running in Go Playground.
I am trying to unmarshall multidimensional JSON. My JSON is contains dynamic key therefore I can't do it.
JSON
{
"id":"3",
"datetime":"2019-06-08",
"metadata":[{"a":"A"},{"b":"B"}]
}
Go file
type Chats struct {
Id string json:"id"
Datetime string json:"date"
Metadata string json:"metadata"
}
chat := models.Chats{}
err := c.BindJSON(&chat)
if err != nil {
c.Error(err)
return
}
fmt.Println(chat)
If metadata is dynamic then treat is as an interface{}. If you know it's always going to be a JSON container then you could do map[string]interface{} for convenience. There is also json.RawMessage if you don't necessarily want to use type assertions to see what's inside of it, but just want to preserve the JSON (I'm guessing this is what you were hoping to do by setting it to string).
type Chats struct {
Id string `json:"id"`
Datetime string `json:"date"`
Metadata interface{} `json:"metadata"`
}
type Chats struct {
Id string `json:"id"`
Datetime string `json:"date"`
Metadata map[string]interface{} `json:"metadata"`
}
type Chats struct {
Id string `json:"id"`
Datetime string `json:"date"`
Metadata json.RawMessage `json:"metadata"`
}
I have a code:
values, err := redis.Values(c.Do("hgetall", value))
if err != nil {
fmt.Println("HGETALL", err)
}
/*
type UD struct {
created_at string
B time.Time
ended_at string
data string
status string
}
*/
if err := redis.ScanStruct(values, &UD); err != nil {
fmt.Println(err)
}
The error I got is
redigo.ScanStruct: cannot assign field B: cannot convert from Redis
bulk string to time.Time
How do I resolve this? Any examples of ScanStruct in detail for a variety of field types for Struct for reference?
The documentation for ScanStruct is quite clear:
Integer, float, boolean, string and []byte fields are supported.
Other field types are not supported time.Time included.
To resolve this, I'd go and make my own version of ScanStruct that can deal with the conversion between Redis' and whatever types I need to throw at it.
You can simply add an ignore tag to make time field avoid being marshaled.
type UD struct {
created_at string
B time.Time `redis:"-"`
ended_at string
data string
status string
}
In a if condition I'm trying to know if my data's type is time.Time.
What is the best way to get the res.Datas[i]'s data type and check it in a if loop ?
Assuming type of res.Datas[i] is not a concrete type but an interface type (e.g. interface{}), simply use a type assertion for this:
if t, ok := res.Datas[i].(time.Time); ok {
// it is of type time.Time
// t is of type time.Time, you can use it so
} else {
// not of type time.Time, or it is nil
}
If you don't need the time.Time value, you just want to tell if the interface value wraps a time.Time:
if _, ok := res.Datas[i].(time.Time); ok {
// it is of type time.Time
} else {
// not of type time.Time, or it is nil
}
Also note that the types time.Time and *time.Time are different. If a pointer to time.Time is wrapped, you need to check that as a different type.
I am trying to use Google Datastore to store data by Go. Since the EndDate is optional field, and don't want to store zero value in that field. If I make a pointer for time field, Google Datastore will send an error message - datastore: unsupported struct field type: *time.Time
How can I ignore zero value field in struct?
type Event struct {
StartDate time.Time `datastore:"start_date,noindex" json:"startDate"`
EndDate time.Time `datastore:"end_date,noindex" json:"endDate"`
}
The default saving mechanism does not handle optional fields. A field is either saved all the time, or never. There is no such thing as "only save if it's value does not equal to something".
The "optionally saved property" is considered a custom behavior, a custom saving mechanism, and as such, it has to be implemented manually. Go's way to do this is to implement the PropertyLoadSaver interface on your struct. Here I present 2 different methods to achieve that:
Manually saving fields
Here is an example how to do it by manually saving the fields (and excluding EndDate if it is the zero value):
type Event struct {
StartDate time.Time `datastore:"start_date,noindex" json:"startDate"`
EndDate time.Time `datastore:"end_date,noindex" json:"endDate"`
}
func (e *Event) Save(c chan<- datastore.Property) error {
defer close(c)
// Always save StartDate:
c <- datastore.Property{Name:"start_date", Value:e.StartDate, NoIndex: true}
// Only save EndDate if not zero value:
if !e.EndDate.IsZero() {
c <- datastore.Property{Name:"end_date", Value:e.EndDate, NoIndex: true}
}
return nil
}
func (e *Event) Load(c chan<- datastore.Property) error {
// No change required in loading, call default implementation:
return datastore.LoadStruct(e, c)
}
With another struct
Here's another way using another struct. The Load() implementation is always the same, only Save() differs:
func (e *Event) Save(c chan<- datastore.Property) error {
if !e.EndDate.IsZero() {
// If EndDate is not zero, save as usual:
return datastore.SaveStruct(e, c)
}
// Else we need a struct without the EndDate field:
s := struct{ StartDate time.Time `datastore:"start_date,noindex"` }{e.StartDate}
// Which now can be saved using the default saving mechanism:
return datastore.SaveStruct(&s, c)
}
Use omitempty in the field tags.
From the docs: https://golang.org/pkg/encoding/json/
Struct values encode as JSON objects. Each exported struct field
becomes a member of the object unless
the field's tag is "-", or
the field is empty and its tag specifies the "omitempty" option.
Field int json:"myName,omitempty"