英文:
How to parse non standard time format from json
问题
假设我有以下的 JSON 数据:
{
"name": "John",
"birth_date": "1996-10-07"
}
我想将其解码为以下的结构体:
type Person struct {
Name string `json:"name"`
BirthDate time.Time `json:"birth_date"`
}
可以按照以下方式实现:
person := Person{}
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&person); err != nil {
log.Println(err)
}
但是这样会报错 parsing time ""1996-10-07"" as ""2006-01-02T15:04:05Z07:00"": cannot parse """" as "T"
。
如果我要手动解析时间,我会这样做:
t, err := time.Parse("2006-01-02", "1996-10-07")
但是当时间值来自 JSON 字符串时,如何让解码器按照上述格式进行解析呢?
英文:
lets say i have the following json
{
name: "John",
birth_date: "1996-10-07"
}
and i want to decode it into the following structure
type Person struct {
Name string `json:"name"`
BirthDate time.Time `json:"birth_date"`
}
like this
person := Person{}
decoder := json.NewDecoder(req.Body);
if err := decoder.Decode(&person); err != nil {
log.Println(err)
}
which gives me the error parsing time ""1996-10-07"" as ""2006-01-02T15:04:05Z07:00"": cannot parse """ as "T"
if i were to parse it manually i would do it like this
t, err := time.Parse("2006-01-02", "1996-10-07")
but when the time value is from a json string how do i get the decoder to parse it in the above format?
答案1
得分: 34
这是一个需要实现自定义编组和解组函数的情况。
按照Golang文档中的示例,你可以得到以下代码:
// 首先创建一个类型别名
type JsonBirthDate time.Time
// 将其添加到你的结构体中
type Person struct {
Name string `json:"name"`
BirthDate JsonBirthDate `json:"birth_date"`
}
// 实现编组和解组接口
func (j *JsonBirthDate) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), "\"")
t, err := time.Parse("2006-01-02", s)
if err != nil {
return err
}
*j = JsonBirthDate(t)
return nil
}
func (j JsonBirthDate) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(j))
}
// 可能还需要一个格式化日期的函数
func (j JsonBirthDate) Format(s string) string {
t := time.Time(j)
return t.Format(s)
}
你可以在Golang的json包文档中找到更多相关信息。
英文:
That's a case when you need to implement custom marshal and unmarshal functions.
UnmarshalJSON(b []byte) error { ... }
MarshalJSON() ([]byte, error) { ... }
By following the example in the Golang documentation of json package you get something like:
// First create a type alias
type JsonBirthDate time.Time
// Add that to your struct
type Person struct {
Name string `json:"name"`
BirthDate JsonBirthDate `json:"birth_date"`
}
// Implement Marshaler and Unmarshaler interface
func (j *JsonBirthDate) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), "\"")
t, err := time.Parse("2006-01-02", s)
if err != nil {
return err
}
*j = JsonBirthDate(t)
return nil
}
func (j JsonBirthDate) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(j))
}
// Maybe a Format function for printing your date
func (j JsonBirthDate) Format(s string) string {
t := time.Time(j)
return t.Format(s)
}
答案2
得分: 6
如果有很多结构体,并且你只是实现了自定义的编组和解组函数,那么这将是很多工作。你可以使用另一个库,比如一个名为jsontime的json-iterator扩展库:
import "github.com/liamylian/jsontime"
var json = jsontime.ConfigWithCustomTimeFormat
type Book struct {
Id int `json:"id"`
UpdatedAt *time.Time `json:"updated_at" time_format:"sql_date" time_utc:"true"`
CreatedAt time.Time `json:"created_at" time_format:"sql_datetime" time_location:"UTC"`
}
英文:
If there are lots of struct and you just implement custom marshal und unmarshal functions, that's a lot of work to do so. You can use another lib instead,such as a json-iterator extension jsontime:
import "github.com/liamylian/jsontime"
var json = jsontime.ConfigWithCustomTimeFormat
type Book struct {
Id int `json:"id"`
UpdatedAt *time.Time `json:"updated_at" time_format:"sql_date" time_utc:"true"`
CreatedAt time.Time `json:"created_at" time_format:"sql_datetime" time_location:"UTC"`
}
答案3
得分: 2
我为处理yyyy-MM-dd
和yyyy-MM-ddThh:mm:ss
日期编写了一个包,可以在https://github.com/a-h/date找到。
它使用了上面回答中的类型别名方法,然后通过一些修改实现了MarshalJSON
和UnmarshalJSON
函数。
// MarshalJSON 输出 JSON。
func (d YYYYMMDD) MarshalJSON() ([]byte, error) {
return []byte("\"" + time.Time(d).Format(formatStringYYYYMMDD) + "\""), nil
}
// UnmarshalJSON 处理传入的 JSON。
func (d *YYYYMMDD) UnmarshalJSON(b []byte) (err error) {
if err = checkJSONYYYYMMDD(string(b)); err != nil {
return
}
t, err := time.ParseInLocation(parseJSONYYYYMMDD, string(b), time.UTC)
if err != nil {
return
}
*d = YYYYMMDD(t)
return
}
在正确的时区解析很重要。我的代码假设使用的是UTC时区,但你可能出于某种原因希望使用计算机的时区。
我还发现,使用time.Parse
函数的解决方案会泄漏Go的内部机制作为错误消息,这对客户端来说并不有用,例如:cannot parse "sdfdf-01-01" as "2006"
。只有当你知道服务器是用Go编写的,并且2006
是示例日期格式时,这才有用,所以我放入了更易读的错误消息。
我还实现了Stringer
接口,以便在日志或调试消息中以漂亮的方式打印。
英文:
I wrote a package for handling yyyy-MM-dd
and yyyy-MM-ddThh:mm:ss
dates at https://github.com/a-h/date
It uses the type alias approach in the answer above, then implements the MarshalJSON
and UnmarshalJSON
functions with a few alterations.
// MarshalJSON outputs JSON.
func (d YYYYMMDD) MarshalJSON() ([]byte, error) {
return []byte("\"" + time.Time(d).Format(formatStringYYYYMMDD) + "\""), nil
}
// UnmarshalJSON handles incoming JSON.
func (d *YYYYMMDD) UnmarshalJSON(b []byte) (err error) {
if err = checkJSONYYYYMMDD(string(b)); err != nil {
return
}
t, err := time.ParseInLocation(parseJSONYYYYMMDD, string(b), time.UTC)
if err != nil {
return
}
*d = YYYYMMDD(t)
return
}
It's important to parse in the correct timezone. My code assumes UTC, but you may wish to use the computer's timezone for some reason.
I also found that solutions which involved using the time.Parse
function leaked Go's internal mechanisms as an error message which clients didn't find helpful, for example: cannot parse "sdfdf-01-01" as "2006"
. That's only useful if you know that the server is written in Go, and that 2006
is the example date format, so I put in more readable error messages.
I also implemented the Stringer
interface so that it gets pretty printed in log or debug messages.
答案4
得分: 0
自定义实现了marshal、unmarshal和string方法。
package json
import (
"fmt"
"strings"
"time"
)
const rfc3339 string = "2006-01-02"
// Date represents a date without a time component, encoded as a string
// in the "YYYY-MM-DD" format.
type Date struct {
Year int
Month time.Month
Day int
}
// UnmarshalJSON implements json.Unmarshaler inferface.
func (d *Date) UnmarshalJSON(b []byte) error {
t, err := time.Parse(rfc3339, strings.Trim(string(b), `"`))
if err != nil {
return err
}
d.Year, d.Month, d.Day = t.Date()
return nil
}
// MarshalJSON implements json.Marshaler interface.
func (d Date) MarshalJSON() ([]byte, error) {
s := fmt.Sprintf(`"%04d-%02d-%02d"`, d.Year, d.Month, d.Day)
return []byte(s), nil
}
// String defines a string representation.
// It will be called automatically when you try to convert struct instance
// to a string.
func (d Date) String() string {
return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
}
以及对它们的测试。
package json
import (
"encoding/json"
"testing"
"time"
)
func TestDate_UnmarshalJSON(t *testing.T) {
in := `"2022-12-31"`
want := time.Date(2022, time.December, 31, 0, 0, 0, 0, time.UTC)
var got Date
if err := got.UnmarshalJSON([]byte(in)); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !(got.Year == want.Year() && got.Month == want.Month() && got.Day == want.Day()) {
t.Errorf("got date = %s, want %s", got, want)
}
}
func TestDate_UnmarshalJSON_badFormat(t *testing.T) {
in := `"31 Dec 22"`
var got Date
err := got.UnmarshalJSON([]byte(in))
if err, ok := err.(*time.ParseError); !ok {
t.Errorf("expected a time parse error, got: %v", err)
}
}
func TestDate_MarshalJSON(t *testing.T) {
testcases := map[string]struct {
in Date
want string
}{
"without zero padding": {
in: Date{2022, time.December, 31},
want: `"2022-12-31"`,
},
"with zero padding": {
in: Date{2022, time.July, 1},
want: `"2022-07-01"`,
},
"initial value": {
in: Date{},
want: `"0000-00-00"`,
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
got, err := json.Marshal(tc.in)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(got) != tc.want {
t.Errorf("got date = %s, want %s", got, tc.want)
}
})
}
}
func TestDate_String(t *testing.T) {
testcases := map[string]struct {
in Date
want string
}{
"without zero padding": {
in: Date{2022, time.December, 31},
want: "2022-12-31",
},
"with zero padding": {
in: Date{2022, time.July, 1},
want: "2022-07-01",
},
"initial value": {
in: Date{},
want: "0000-00-00",
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
if got := tc.in.String(); got != tc.want {
t.Errorf("got %q, want %q", got, tc.want)
}
})
}
}
希望对你有帮助!
英文:
Custom implementation of marshal, unmarshal and string methods.
package json
import (
"fmt"
"strings"
"time"
)
const rfc3339 string = "2006-01-02"
// Date represents a date without a time component, encoded as a string
// in the "YYYY-MM-DD" format.
type Date struct {
Year int
Month time.Month
Day int
}
// UnmarshalJSON implements json.Unmarshaler inferface.
func (d *Date) UnmarshalJSON(b []byte) error {
t, err := time.Parse(rfc3339, strings.Trim(string(b), `"`))
if err != nil {
return err
}
d.Year, d.Month, d.Day = t.Date()
return nil
}
// MarshalJSON implements json.Marshaler interface.
func (d Date) MarshalJSON() ([]byte, error) {
s := fmt.Sprintf(`"%04d-%02d-%02d"`, d.Year, d.Month, d.Day)
return []byte(s), nil
}
// String defines a string representation.
// It will be called automatically when you try to convert struct instance
// to a string.
func (d Date) String() string {
return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
}
And tests for them.
package json
import (
"encoding/json"
"testing"
"time"
)
func TestDate_UnmarshalJSON(t *testing.T) {
in := `"2022-12-31"`
want := time.Date(2022, time.December, 31, 0, 0, 0, 0, time.UTC)
var got Date
if err := got.UnmarshalJSON([]byte(in)); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !(got.Year == want.Year() && got.Month == want.Month() && got.Day == want.Day()) {
t.Errorf("got date = %s, want %s", got, want)
}
}
func TestDate_UnmarshalJSON_badFormat(t *testing.T) {
in := `"31 Dec 22"`
var got Date
err := got.UnmarshalJSON([]byte(in))
if err, ok := err.(*time.ParseError); !ok {
t.Errorf("expected a time parse error, got: %v", err)
}
}
func TestDate_MarshalJSON(t *testing.T) {
testcases := map[string]struct {
in Date
want string
}{
"without zero padding": {
in: Date{2022, time.December, 31},
want: `"2022-12-31"`,
},
"with zero padding": {
in: Date{2022, time.July, 1},
want: `"2022-07-01"`,
},
"initial value": {
in: Date{},
want: `"0000-00-00"`,
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
got, err := json.Marshal(tc.in)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(got) != tc.want {
t.Errorf("got date = %s, want %s", got, tc.want)
}
})
}
}
func TestDate_String(t *testing.T) {
testcases := map[string]struct {
in Date
want string
}{
"without zero padding": {
in: Date{2022, time.December, 31},
want: "2022-12-31",
},
"with zero padding": {
in: Date{2022, time.July, 1},
want: "2022-07-01",
},
"initial value": {
in: Date{},
want: "0000-00-00",
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
if got := tc.in.String(); got != tc.want {
t.Errorf("got %q, want %q", got, tc.want)
}
})
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论