如何解析非标准时间格式的 JSON 数据?

huangapple go评论184阅读模式
英文:

How to parse non standard time format from json

问题

假设我有以下的 JSON 数据:

  1. {
  2. "name": "John",
  3. "birth_date": "1996-10-07"
  4. }

我想将其解码为以下的结构体:

  1. type Person struct {
  2. Name string `json:"name"`
  3. BirthDate time.Time `json:"birth_date"`
  4. }

可以按照以下方式实现:

  1. person := Person{}
  2. decoder := json.NewDecoder(req.Body)
  3. if err := decoder.Decode(&person); err != nil {
  4. log.Println(err)
  5. }

但是这样会报错 parsing time ""1996-10-07"" as ""2006-01-02T15:04:05Z07:00"": cannot parse """" as "T"

如果我要手动解析时间,我会这样做:

  1. t, err := time.Parse("2006-01-02", "1996-10-07")

但是当时间值来自 JSON 字符串时,如何让解码器按照上述格式进行解析呢?

英文:

lets say i have the following json

  1. {
  2. name: "John",
  3. birth_date: "1996-10-07"
  4. }

and i want to decode it into the following structure

  1. type Person struct {
  2. Name string `json:"name"`
  3. BirthDate time.Time `json:"birth_date"`
  4. }

like this

  1. person := Person{}
  2. decoder := json.NewDecoder(req.Body);
  3. if err := decoder.Decode(&person); err != nil {
  4. log.Println(err)
  5. }

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

  1. 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文档中的示例,你可以得到以下代码:

  1. // 首先创建一个类型别名
  2. type JsonBirthDate time.Time
  3. // 将其添加到你的结构体中
  4. type Person struct {
  5. Name string `json:"name"`
  6. BirthDate JsonBirthDate `json:"birth_date"`
  7. }
  8. // 实现编组和解组接口
  9. func (j *JsonBirthDate) UnmarshalJSON(b []byte) error {
  10. s := strings.Trim(string(b), "\"")
  11. t, err := time.Parse("2006-01-02", s)
  12. if err != nil {
  13. return err
  14. }
  15. *j = JsonBirthDate(t)
  16. return nil
  17. }
  18. func (j JsonBirthDate) MarshalJSON() ([]byte, error) {
  19. return json.Marshal(time.Time(j))
  20. }
  21. // 可能还需要一个格式化日期的函数
  22. func (j JsonBirthDate) Format(s string) string {
  23. t := time.Time(j)
  24. return t.Format(s)
  25. }

你可以在Golang的json包文档中找到更多相关信息。

英文:

That's a case when you need to implement custom marshal and unmarshal functions.

  1. UnmarshalJSON(b []byte) error { ... }
  2. MarshalJSON() ([]byte, error) { ... }

By following the example in the Golang documentation of json package you get something like:

  1. // First create a type alias
  2. type JsonBirthDate time.Time
  3. // Add that to your struct
  4. type Person struct {
  5. Name string `json:"name"`
  6. BirthDate JsonBirthDate `json:"birth_date"`
  7. }
  8. // Implement Marshaler and Unmarshaler interface
  9. func (j *JsonBirthDate) UnmarshalJSON(b []byte) error {
  10. s := strings.Trim(string(b), "\"")
  11. t, err := time.Parse("2006-01-02", s)
  12. if err != nil {
  13. return err
  14. }
  15. *j = JsonBirthDate(t)
  16. return nil
  17. }
  18. func (j JsonBirthDate) MarshalJSON() ([]byte, error) {
  19. return json.Marshal(time.Time(j))
  20. }
  21. // Maybe a Format function for printing your date
  22. func (j JsonBirthDate) Format(s string) string {
  23. t := time.Time(j)
  24. return t.Format(s)
  25. }

答案2

得分: 6

如果有很多结构体,并且你只是实现了自定义的编组和解组函数,那么这将是很多工作。你可以使用另一个库,比如一个名为jsontime的json-iterator扩展库:

  1. import "github.com/liamylian/jsontime"
  2. var json = jsontime.ConfigWithCustomTimeFormat
  3. type Book struct {
  4. Id int `json:"id"`
  5. UpdatedAt *time.Time `json:"updated_at" time_format:"sql_date" time_utc:"true"`
  6. CreatedAt time.Time `json:"created_at" time_format:"sql_datetime" time_location:"UTC"`
  7. }
英文:

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:

  1. import "github.com/liamylian/jsontime"
  2. var json = jsontime.ConfigWithCustomTimeFormat
  3. type Book struct {
  4. Id int `json:"id"`
  5. UpdatedAt *time.Time `json:"updated_at" time_format:"sql_date" time_utc:"true"`
  6. CreatedAt time.Time `json:"created_at" time_format:"sql_datetime" time_location:"UTC"`
  7. }

答案3

得分: 2

我为处理yyyy-MM-ddyyyy-MM-ddThh:mm:ss日期编写了一个包,可以在https://github.com/a-h/date找到。

它使用了上面回答中的类型别名方法,然后通过一些修改实现了MarshalJSONUnmarshalJSON函数。

  1. // MarshalJSON 输出 JSON。
  2. func (d YYYYMMDD) MarshalJSON() ([]byte, error) {
  3. return []byte("\"" + time.Time(d).Format(formatStringYYYYMMDD) + "\""), nil
  4. }
  5. // UnmarshalJSON 处理传入的 JSON。
  6. func (d *YYYYMMDD) UnmarshalJSON(b []byte) (err error) {
  7. if err = checkJSONYYYYMMDD(string(b)); err != nil {
  8. return
  9. }
  10. t, err := time.ParseInLocation(parseJSONYYYYMMDD, string(b), time.UTC)
  11. if err != nil {
  12. return
  13. }
  14. *d = YYYYMMDD(t)
  15. return
  16. }

在正确的时区解析很重要。我的代码假设使用的是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.

  1. // MarshalJSON outputs JSON.
  2. func (d YYYYMMDD) MarshalJSON() ([]byte, error) {
  3. return []byte("\"" + time.Time(d).Format(formatStringYYYYMMDD) + "\""), nil
  4. }
  5. // UnmarshalJSON handles incoming JSON.
  6. func (d *YYYYMMDD) UnmarshalJSON(b []byte) (err error) {
  7. if err = checkJSONYYYYMMDD(string(b)); err != nil {
  8. return
  9. }
  10. t, err := time.ParseInLocation(parseJSONYYYYMMDD, string(b), time.UTC)
  11. if err != nil {
  12. return
  13. }
  14. *d = YYYYMMDD(t)
  15. return
  16. }

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方法。

  1. package json
  2. import (
  3. "fmt"
  4. "strings"
  5. "time"
  6. )
  7. const rfc3339 string = "2006-01-02"
  8. // Date represents a date without a time component, encoded as a string
  9. // in the "YYYY-MM-DD" format.
  10. type Date struct {
  11. Year int
  12. Month time.Month
  13. Day int
  14. }
  15. // UnmarshalJSON implements json.Unmarshaler inferface.
  16. func (d *Date) UnmarshalJSON(b []byte) error {
  17. t, err := time.Parse(rfc3339, strings.Trim(string(b), `"`))
  18. if err != nil {
  19. return err
  20. }
  21. d.Year, d.Month, d.Day = t.Date()
  22. return nil
  23. }
  24. // MarshalJSON implements json.Marshaler interface.
  25. func (d Date) MarshalJSON() ([]byte, error) {
  26. s := fmt.Sprintf(`"%04d-%02d-%02d"`, d.Year, d.Month, d.Day)
  27. return []byte(s), nil
  28. }
  29. // String defines a string representation.
  30. // It will be called automatically when you try to convert struct instance
  31. // to a string.
  32. func (d Date) String() string {
  33. return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
  34. }

以及对它们的测试。

  1. package json
  2. import (
  3. "encoding/json"
  4. "testing"
  5. "time"
  6. )
  7. func TestDate_UnmarshalJSON(t *testing.T) {
  8. in := `"2022-12-31"`
  9. want := time.Date(2022, time.December, 31, 0, 0, 0, 0, time.UTC)
  10. var got Date
  11. if err := got.UnmarshalJSON([]byte(in)); err != nil {
  12. t.Fatalf("unexpected error: %v", err)
  13. }
  14. if !(got.Year == want.Year() && got.Month == want.Month() && got.Day == want.Day()) {
  15. t.Errorf("got date = %s, want %s", got, want)
  16. }
  17. }
  18. func TestDate_UnmarshalJSON_badFormat(t *testing.T) {
  19. in := `"31 Dec 22"`
  20. var got Date
  21. err := got.UnmarshalJSON([]byte(in))
  22. if err, ok := err.(*time.ParseError); !ok {
  23. t.Errorf("expected a time parse error, got: %v", err)
  24. }
  25. }
  26. func TestDate_MarshalJSON(t *testing.T) {
  27. testcases := map[string]struct {
  28. in Date
  29. want string
  30. }{
  31. "without zero padding": {
  32. in: Date{2022, time.December, 31},
  33. want: `"2022-12-31"`,
  34. },
  35. "with zero padding": {
  36. in: Date{2022, time.July, 1},
  37. want: `"2022-07-01"`,
  38. },
  39. "initial value": {
  40. in: Date{},
  41. want: `"0000-00-00"`,
  42. },
  43. }
  44. for name, tc := range testcases {
  45. t.Run(name, func(t *testing.T) {
  46. got, err := json.Marshal(tc.in)
  47. if err != nil {
  48. t.Fatalf("unexpected error: %v", err)
  49. }
  50. if string(got) != tc.want {
  51. t.Errorf("got date = %s, want %s", got, tc.want)
  52. }
  53. })
  54. }
  55. }
  56. func TestDate_String(t *testing.T) {
  57. testcases := map[string]struct {
  58. in Date
  59. want string
  60. }{
  61. "without zero padding": {
  62. in: Date{2022, time.December, 31},
  63. want: "2022-12-31",
  64. },
  65. "with zero padding": {
  66. in: Date{2022, time.July, 1},
  67. want: "2022-07-01",
  68. },
  69. "initial value": {
  70. in: Date{},
  71. want: "0000-00-00",
  72. },
  73. }
  74. for name, tc := range testcases {
  75. t.Run(name, func(t *testing.T) {
  76. if got := tc.in.String(); got != tc.want {
  77. t.Errorf("got %q, want %q", got, tc.want)
  78. }
  79. })
  80. }
  81. }

希望对你有帮助!

英文:

Custom implementation of marshal, unmarshal and string methods.

  1. package json
  2. import (
  3. "fmt"
  4. "strings"
  5. "time"
  6. )
  7. const rfc3339 string = "2006-01-02"
  8. // Date represents a date without a time component, encoded as a string
  9. // in the "YYYY-MM-DD" format.
  10. type Date struct {
  11. Year int
  12. Month time.Month
  13. Day int
  14. }
  15. // UnmarshalJSON implements json.Unmarshaler inferface.
  16. func (d *Date) UnmarshalJSON(b []byte) error {
  17. t, err := time.Parse(rfc3339, strings.Trim(string(b), `"`))
  18. if err != nil {
  19. return err
  20. }
  21. d.Year, d.Month, d.Day = t.Date()
  22. return nil
  23. }
  24. // MarshalJSON implements json.Marshaler interface.
  25. func (d Date) MarshalJSON() ([]byte, error) {
  26. s := fmt.Sprintf(`"%04d-%02d-%02d"`, d.Year, d.Month, d.Day)
  27. return []byte(s), nil
  28. }
  29. // String defines a string representation.
  30. // It will be called automatically when you try to convert struct instance
  31. // to a string.
  32. func (d Date) String() string {
  33. return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
  34. }

And tests for them.

  1. package json
  2. import (
  3. "encoding/json"
  4. "testing"
  5. "time"
  6. )
  7. func TestDate_UnmarshalJSON(t *testing.T) {
  8. in := `"2022-12-31"`
  9. want := time.Date(2022, time.December, 31, 0, 0, 0, 0, time.UTC)
  10. var got Date
  11. if err := got.UnmarshalJSON([]byte(in)); err != nil {
  12. t.Fatalf("unexpected error: %v", err)
  13. }
  14. if !(got.Year == want.Year() && got.Month == want.Month() && got.Day == want.Day()) {
  15. t.Errorf("got date = %s, want %s", got, want)
  16. }
  17. }
  18. func TestDate_UnmarshalJSON_badFormat(t *testing.T) {
  19. in := `"31 Dec 22"`
  20. var got Date
  21. err := got.UnmarshalJSON([]byte(in))
  22. if err, ok := err.(*time.ParseError); !ok {
  23. t.Errorf("expected a time parse error, got: %v", err)
  24. }
  25. }
  26. func TestDate_MarshalJSON(t *testing.T) {
  27. testcases := map[string]struct {
  28. in Date
  29. want string
  30. }{
  31. "without zero padding": {
  32. in: Date{2022, time.December, 31},
  33. want: `"2022-12-31"`,
  34. },
  35. "with zero padding": {
  36. in: Date{2022, time.July, 1},
  37. want: `"2022-07-01"`,
  38. },
  39. "initial value": {
  40. in: Date{},
  41. want: `"0000-00-00"`,
  42. },
  43. }
  44. for name, tc := range testcases {
  45. t.Run(name, func(t *testing.T) {
  46. got, err := json.Marshal(tc.in)
  47. if err != nil {
  48. t.Fatalf("unexpected error: %v", err)
  49. }
  50. if string(got) != tc.want {
  51. t.Errorf("got date = %s, want %s", got, tc.want)
  52. }
  53. })
  54. }
  55. }
  56. func TestDate_String(t *testing.T) {
  57. testcases := map[string]struct {
  58. in Date
  59. want string
  60. }{
  61. "without zero padding": {
  62. in: Date{2022, time.December, 31},
  63. want: "2022-12-31",
  64. },
  65. "with zero padding": {
  66. in: Date{2022, time.July, 1},
  67. want: "2022-07-01",
  68. },
  69. "initial value": {
  70. in: Date{},
  71. want: "0000-00-00",
  72. },
  73. }
  74. for name, tc := range testcases {
  75. t.Run(name, func(t *testing.T) {
  76. if got := tc.in.String(); got != tc.want {
  77. t.Errorf("got %q, want %q", got, tc.want)
  78. }
  79. })
  80. }
  81. }

huangapple
  • 本文由 发表于 2017年7月25日 20:28:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/45303326.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定