解析 Revel 中的 JSON 日期时间

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

Parsing a json datetime in revel

问题

我正在尝试从JSON请求中解析一个Datetime。请求的格式如下所示:

{
  "startedAt": "2017-06-01 10:39",
  ...
}

被解码为以下结构体:

type MyStruct struct {
  StartedAt time.Time `json:"startedAt" bson:"startedAt"`
  ...
}

解码行如下所示:

json.NewDecoder(c.Request.Body).Decode(&MyStruct)

Revel返回以下错误:

interface conversion: error is *time.ParseError, not *errors.Error

根据revel的文档(https://revel.github.io/manual/parameters.html),SQL标准的日期时间格式2006-01-02、2006-01-02 15:04是内置的。

在同一文档中,他们还说可以追加格式,如下所示:

func init() {
    revel.TimeFormats = append(revel.TimeFormats, "01/02/2006")
}

为了验证格式是否在数组中,我尝试了以下代码:

func init() {
    revel.TimeFormats = append(revel.TimeFormats, "2016-06-01 12:12")
}

在绝望之下,我尝试以revel返回JSON时间的相同格式提交:

{
  "startedAt": "2017-06-22T12:22:16.265618819-05:00",
  ...
}

目前我不确定该怎么办。有人能够成功解析Datetime吗?


更新:我尝试了RickyA的解决方案,但现在出现了解析错误。

parsing time ""Mon, 02 Jan 2006 15:04:05 -0700"" as "Mon, 02 Jan 2006 15:04:05 -0700": cannot parse ""Mon, 02 Jan 2006 15:04:05 -0700"" as "Mon"

更奇怪的是,我实现了一个小技巧来解决这个问题。我将请求时间字段更改为字符串,并给它一个ToX函数来进行转换。该函数有效,但当它移动到UnmarshalJSON函数中时却失败了。

此外,我无法确定这是否是一个错误:

func (t *AnyTime) UnmarshalJSON(b []byte) error {
  fmt.Println(time.RFC1123Z)
  fmt.Println(string(b))
  fmt.Println(reflect.TypeOf(time.RFC1123Z))
  fmt.Println(reflect.TypeOf(string(b)))
  ...

这将输出:

Mon, 02 Jan 2006 15:04:05 -0700
"Mon, 02 Jan 2006 15:04:05 -0700"
string
string

请注意,只有一个字符串带有双引号。这让我相信,传递给UnmarshalJSON的字节数组中包含双引号。


最终更新:我相当确定双引号是有意的,从某种意义上讲,这是有道理的。UnmarshalJSON将从':'到','之间的所有内容传递,包括双引号。我猜这样做是为了支持整数和布尔值。我不喜欢这个解决方案,但这是我修复它的方法:

func (t *AnyTime) UnmarshalJSON(b []byte) error {
  var err error
  sTime := string(b)
  sTime = strings.TrimSuffix(sTime, "\"")
  sTime = strings.TrimPrefix(sTime, "\"")
  t.Time, err = time.Parse(time.RFC1123Z, sTime)
  ...
英文:

I'm attempting to parse a Datetime in revel from a json request. The request looks something like this:

{
  "startedAt": "2017-06-01 10:39",
  ...
}

The struct it's being decoded into looks like this:

type MyStruct struct {
  StartedAt time.Time `json:"startedAt" bson:"startedAt"`
  ...
}

The decode line looks like this:

json.NewDecoder(c.Request.Body).Decode(&MyStruct)

Revel returns this error:

interface conversion: error is *time.ParseError, not *errors.Error

According to revel's documentation here, https://revel.github.io/manual/parameters.html

The SQL standard date time formats of 2006-01-02, 2006-01-02 15:04 are built in.

In the same document, they also say you can append formats like this:

func init() {
    revel.TimeFormats = append(revel.TimeFormats, "01/02/2006")
}

To verify the format was in the array, I tried this:

func init() {
    revel.TimeFormats = append(revel.TimeFormats, "2016-06-01 12:12")
}

In a final act of desperation I tried submitting it in the same format that revel will return json times in:

{
  "startedAt": "2017-06-22T12:22:16.265618819-05:00",
  ...
}

At this point I'm not sure where to go with this. Has anyone been able to get revel to parse a Datetime?


Update: I tried RickyA's solution below, but now there is a parsing error.

parsing time ""Mon, 02 Jan 2006 15:04:05 -0700"" as "Mon, 02 Jan 2006 15:04:05 -0700": cannot parse ""Mon, 02 Jan 2006 15:04:05 -0700"" as "Mon"

What's even stranger is that I implemented a bit of a hack to get this working in the interrum. I changed the request time field to a string, and gave it a ToX function which converted it. That function works, but when it's moved into the UnmarshalJSON function it fails.

Also, I can't tell if this is a bug or not:

func (t *AnyTime) UnmarshalJSON(b []byte) error {
  fmt.Println(time.RFC1123Z)
  fmt.Println(string(b))
  fmt.Println(reflect.TypeOf(time.RFC1123Z))
  fmt.Println(reflect.TypeOf(string(b)))
  ...

This outputs this:

Mon, 02 Jan 2006 15:04:05 -0700
"Mon, 02 Jan 2006 15:04:05 -0700"
string
string

Notice that only 1 of the string has double quotes. This is leading me to believe that some how the byte array passed to UnmarshalJSON has double quotes within its string.


Final Update: So I'm pretty sure the double quotes are intentional, and in a way it makes sense. The UnmarshalJSON is passing everything from the ':' through the ',' which includes the double quotes. I'm guessing this is done so that it can also support integers and booleans. I don't like the solution, but this is how I fixed it:

func (t *AnyTime) UnmarshalJSON(b []byte) error {
  var err error
  sTime := string(b)
  sTime = strings.TrimSuffix(sTime, "\"")
  sTime = strings.TrimPrefix(sTime, "\"")
  t.Time, err = time.Parse(time.RFC1123Z, sTime)
  ...

答案1

得分: 3

我遇到了同样的问题,最后创建了一个自定义类型来处理time.Time

package genericfields

import (
	"encoding/json"
	"time"
	"strings"

	"gopkg.in/mgo.v2/bson"
)

// ANYTIME //

// AnyTime接受任何时间格式进行反序列化//
type AnyTime struct{ time.Time }

func (t AnyTime) MarshalJSON() ([]byte, error) {
	return json.Marshal(t.Time)
}

func (t *AnyTime) UnmarshalJSON(b []byte) error {
	err := json.Unmarshal(b, &t.Time)
	if err != nil { //假设输入的时间没有时区信息
		bstr := strings.Trim(string(b), `"`)
		t.Time, err = time.Parse("2006-01-02T15:04:05", bstr)
		if err != nil {
			return err //TODO 添加更多尝试的时间格式
		}
	}
	return nil
}

func (t AnyTime) GetBSON() (interface{}, error) {
	return t.Time, nil
}

func (t *AnyTime) SetBSON(raw bson.Raw) error {
	var tm time.Time
	err := raw.Unmarshal(&tm)
	if err != nil {
		return err
	}
	t.Time = tm.UTC()
	return nil
}

func (t AnyTime) ToTime() time.Time {
	return t.Time
}

用法:

type MyStruct struct {
  StartedAt genericfields.AnyTime `json:"startedAt" bson:"startedAt"`
  ...
}

你可能需要调整time.Parse的输入格式。

英文:

I had the same problem and ended up creating a custom type for time.Time

package genericfields

import (
	"encoding/json"
	"time"
    "strings"

	"gopkg.in/mgo.v2/bson"
)

// ANYTIME //

// AnyTime accepts any time format for its unmarshaling //
type AnyTime struct{ time.Time }

func (t AnyTime) MarshalJSON() ([]byte, error) {
	return json.Marshal(t.Time)
}

func (t *AnyTime) UnmarshalJSON(b []byte) error {
	err := json.Unmarshal(b, &t.Time)
	if err != nil { //assume non tz time input
		bstr := strings.Trim(string(b), `"`)
	    t.Time, err = time.Parse("2006-01-02T15:04:05", bstr)
		if err != nil {
			return err //TODO add more formats to try
		}
	}
	return nil
}

func (t AnyTime) GetBSON() (interface{}, error) {
	return t.Time, nil
}

func (t *AnyTime) SetBSON(raw bson.Raw) error {
	var tm time.Time
	err := raw.Unmarshal(&tm)
	if err != nil {
		return err
	}
	t.Time = tm.UTC()
	return nil
}

func (t AnyTime) ToTime() time.Time {
	return t.Time
}

Usage:

type MyStruct struct {
  StartedAt genericfields.AnyTime `json:"startedAt" bson:"startedAt"`
  ...
}

You might need to tweak the input for time.Parse somewhat.

huangapple
  • 本文由 发表于 2017年6月23日 01:24:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/44705817.html
匿名

发表评论

匿名网友

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

确定