Golang中解析JSON日期值的最佳方法
Golang中解析JSON日期值的最佳方法 大家好,我想反序列化这种类型的数据:
"ranks": {
"2021-12-22T00:00:00Z": "Over page 5",
"2021-12-23T00:00:00Z": "Over page 5",
"2021-12-24T00:00:00Z": "Over page 5",
"2021-12-25T00:00:00Z": "112",
"2021-12-26T00:00:00Z": "152",
"2021-12-27T00:00:00Z": "138",
"2021-12-28T00:00:00Z": "105",
"2021-12-29T00:00:00Z": "Pending data refresh",
"2021-12-30T00:00:00Z": "No data"
},
我这样声明了我的结构体:
Ranks map[string]string `json:"ranks"`
所以我打算先将其反序列化为字符串,之后再处理这个映射,将 ISO8601 格式的键转换为日期,以得到类似这样的结构体:
Ranks map[Time.time]string `json:"ranks"`
有更好的想法吗?
更多关于Golang中解析JSON日期值的最佳方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html
这取决于你在反序列化后想要如何处理数据。
更多关于Golang中解析JSON日期值的最佳方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
eudore:
UnmarshalJSON
非常感谢,您能否在字段标签中指定您的 unmarshalJSON 函数?或者这将自动应用于我应用程序中的所有 Time 类型?
你需要一个 []Rank,但你的 JSON 中的 ranks 是一个对象。可以尝试:
type RanksObject struct {
Ranks []Rank
}
type RankReport struct {
...
Ranks *RanksObject `json:"ranks"`
....
}
并为 *RanksObject 定义 UnmarshalJSON。
// Ranks map[Time]string `json:"ranks"`
type Time time.Time
func (d *Time ) UnmarshalJSON(b []byte) error {
str := string(b)
if str != "" && str[0] == '"' && str[len(str)-1] == '"' {
str = str[1 : len(str)-1]
}
// parse string
t, err := time.Parse(format,str)
if err == nil {
*d = Time(t)
return nil
}
return fmt.Errorf("invalid duration type %T, value: '%s'", b, b)
}
我的观点是,当你问“有没有更好的主意?”时,答案可能是“没有,这看起来是符合惯用法的做法”,也可能是“你可能需要考虑做 x”。我们唯一能给你一个好答案的方式,就是了解你在反序列化这些数据之后打算用它做什么。如果时间戳是稀疏的,并且你需要检查精确的时间(例如,你会检查 2021-12-26T00:00:00Z 这个值,但不会检查 2021-12-25T19:00:00+500,这两个时间点是同一时刻,但处于不同时区)。如果你打算遍历这些时间,和/或基于最接近的时间做某些操作,那么就有不同的解决方案。如果时间总是 UTC 并且恰好按天递增,你可以将第一个日期存储在某个数据结构中,然后根据自第一天以来的天数作为索引去访问一个值切片,等等……只有当我们知道你在做什么,我们才能(可能)给你更好的建议。
谢谢,你说得对。
之后,我需要做的是将这些数据显示在 React 图表上。图表需要接收如下格式的输入:
[
{
"Date": "2010-01",
"scales": 1998
},
{
"Date": "2010-02",
"scales": 1850
},
{
"Date": "2010-03",
"scales": 1720
},
{
"Date": "2010-04",
"scales": 1818
},
{
"Date": "2010-05",
"scales": 1920
},
{
"Date": "2010-06",
"scales": 1802
},
所以我的想法是将其作为 Ranks map[time.Time]string 返回,并在 JavaScript 端将其转换为具有日期和刻度属性的对象。
我也需要将其存储到 MongoDB 中。
代码大致如下:
type Rank struct {
Date time.Time `json:"date"`
Rank int `json:"rank"`
}
func (b *Rank) UnmarshalJSON(data []byte) error {
var v []byte
if err := json.Unmarshal(data, &v); err != nil {
return err
}
str := string(v[0])
if str != "" && str[0] == '"' && str[len(str)-1] == '"' {
str = str[1 : len(str)-1]
}
t, err := time.Parse("2006-01-02T15:04:05-0700", str)
if err == nil {
return nil
}
b.Date = t
rankAsStr := string(v[1])
switch rankAsStr {
case NO_DATA:
b.Rank = 0
case PENDING:
b.Rank = -1
case RANK_REPORT_OUT_OF_RANGE:
b.Rank = 150
default:
b.Rank, err = strconv.Atoi(string(v[1]))
if err == nil {
return nil
}
}
return nil
}
我也尝试了:
type RankReport struct {
ProjectName interface{} `json:"projectName"`
Asin string `json:"asin"`
Marketplace string `json:"marketplace"`
Keyword string `json:"keyword"`
SearchVolume int `json:"searchVolume"`
Ranks Ranks `json:"ranks"`
SponsoredRanks Ranks `json:"sponsoredRanks"`
}
type Ranks = []Rank
type Rank struct {
Date time.Time `json:"date"`
Rank int `json:"rank"`
}
func (ranks *Ranks) UnmarshalJSON(data []byte) error {
var mapOfValues map[string]interface{}
if err := json.Unmarshal(data, &mapOfValues); err != nil {
return err
}
for k, v := range mapOfValues {
if k != "" && k[0] == '"' && k[len(k)-1] == '"' {
k = k[1 : len(k)-1]
}
t, err := time.Parse("2006-01-02T15:04:05-0700", k)
if err == nil {
return errors.InternalServerError.NewWithSecret("Unable to read DataHawk date", err)
}
rankAsStr, ok := v.(string)
if !ok {
return errors.InternalServerError.NewWithDetails("Unable to read DataHawk value", "")
}
rankValue := 0
switch rankAsStr {
case NO_DATA:
rankValue = 0
case PENDING:
rankValue = -1
case RANK_REPORT_OUT_OF_RANGE:
rankValue = 150
default:
rankValue, err = strconv.Atoi(rankAsStr)
if err == nil {
return nil
}
}
rank := Rank{
Date: t,
Rank: rankValue,
}
*ranks = append(*ranks, rank)
}
return nil
}
但这在数组上似乎是不可能的。
这似乎没有被调用,可能是因为
{
"projectName": null,
"asin": "B07Z842WBF",
"marketplace": "France",
"keyword": "macbook",
"searchVolume": 135000,
"ranks": {
"2021-12-22T00:00:00Z": "9",
"2021-12-23T00:00:00Z": "10",
"2021-12-24T00:00:00Z": "8",
"2021-12-25T00:00:00Z": "7",
"2021-12-26T00:00:00Z": "7",
"2021-12-27T00:00:00Z": "6",
"2021-12-28T00:00:00Z": "7",
"2021-12-29T00:00:00Z": "Pending data refresh",
"2021-12-30T00:00:00Z": "No data"
},
"sponsoredRanks": {
"2021-12-22T00:00:00Z": "Over page 5",
"2021-12-23T00:00:00Z": "Over page 5",
"2021-12-24T00:00:00Z": "Over page 5",
"2021-12-25T00:00:00Z": "Over page 5",
"2021-12-26T00:00:00Z": "Over page 5",
"2021-12-27T00:00:00Z": "Over page 5",
"2021-12-28T00:00:00Z": "Over page 5",
"2021-12-29T00:00:00Z": "Pending data refresh",
"2021-12-30T00:00:00Z": "No data"
}
},
它是一个对象,而我试图将其解码为数组:
type RankReport struct {
ProjectName interface{} `json:"projectName"`
Asin string `json:"asin"`
Marketplace string `json:"marketplace"`
Keyword string `json:"keyword"`
SearchVolume int `json:"searchVolume"`
Ranks []Rank `json:"ranks"`
SponsoredRanks []Rank `json:"sponsoredRanks"`
}
type Rank struct {
Date time.Time `json:"date"`
Rank int `json:"rank"`
}
我最终完成了这个实现:
type RankReport struct {
ASIN string `json:"asin"`
Marketplace string `json:"marketplace"`
Keyword string `json:"keyword"`
SearchVolume float64 `json:"searchVolume"`
Ranks []Rank `json:"ranks"`
SponsoredRanks []Rank `json:"sponsoredRanks"`
}
type Rank struct {
Date time.Time `json:"date"`
Rank int `json:"rank"`
}
func (rankReport *RankReport) UnmarshalJSON(data []byte) error {
var mapOfValues map[string]interface{}
if err := json.Unmarshal(data, &mapOfValues); err != nil {
return err
}
rankReport.ASIN = mapOfValues["asin"].(string)
rankReport.Marketplace = mapOfValues["marketplace"].(string)
rankReport.Keyword = mapOfValues["keyword"].(string)
rankReport.SearchVolume = mapOfValues["searchVolume"].(float64)
rankReport.Ranks = []Rank{}
ranks, ok := mapOfValues["ranks"].(map[string]interface{})
if !ok {
return errors.InternalServerError.NewWithDetails("Unable to read DataHawk ranks", "mapOfValues[\"ranks\"].")
}
for k, v := range ranks {
rank, err := rankReport.extractRank(k, v)
if err != nil {
return errors.InternalServerError.NewWithSecret("Unable to read DataHawk ranks", err)
}
if rank != nil {
rankReport.Ranks = append(rankReport.Ranks, *rank)
}
}
sponsoredRanks, ok := mapOfValues["sponsoredRanks"].(map[string]interface{})
if !ok {
return errors.InternalServerError.NewWithDetails("Unable to read DataHawk sponsoredRanks", "mapOfValues[\"sponsoredRanks\"]")
}
for k, v := range sponsoredRanks {
rank, err := rankReport.extractRank(k, v)
if err != nil {
return errors.InternalServerError.NewWithSecret("Unable to read DataHawk sponsoredRanks", err)
}
if rank != nil {
rankReport.SponsoredRanks = append(rankReport.SponsoredRanks, *rank)
}
}
return nil
}
func (rankReport *RankReport) extractRank(k string, v interface{}) (rank *Rank, err error) {
if k != "" && k[0] == '"' && k[len(k)-1] == '"' {
k = k[1 : len(k)-1]
}
t, err := time.Parse("2006-01-02T15:04:05Z", k)
if err != nil {
return nil, err
}
rankAsStr, ok := v.(string)
if !ok {
return nil, err
}
rankValue := 0
switch rankAsStr {
case NO_DATA:
rankValue = 0
case PENDING:
rankValue = -1
case RANK_REPORT_OUT_OF_RANGE:
rankValue = 150
default:
rankValue, err = strconv.Atoi(rankAsStr)
if err != nil {
return nil, err
}
}
return &Rank{
Date: t,
Rank: rankValue,
}, nil
}
我不太满意的是,这个实现会忽略所有的JSON标签…
在Go中解析JSON日期键的最佳方法是使用自定义的UnmarshalJSON方法。以下是实现方案:
package main
import (
"encoding/json"
"time"
"strings"
)
type CustomTime time.Time
func (ct *CustomTime) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), "\"")
t, err := time.Parse(time.RFC3339, s)
if err != nil {
return err
}
*ct = CustomTime(t)
return nil
}
func (ct CustomTime) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(ct).Format(time.RFC3339))
}
type RanksMap map[CustomTime]string
func (rm *RanksMap) UnmarshalJSON(data []byte) error {
var tempMap map[string]string
if err := json.Unmarshal(data, &tempMap); err != nil {
return err
}
*rm = make(RanksMap)
for k, v := range tempMap {
var ct CustomTime
if err := ct.UnmarshalJSON([]byte(`"` + k + `"`)); err != nil {
return err
}
(*rm)[ct] = v
}
return nil
}
func (rm RanksMap) MarshalJSON() ([]byte, error) {
tempMap := make(map[string]string)
for k, v := range rm {
keyBytes, err := json.Marshal(k)
if err != nil {
return nil, err
}
tempMap[string(keyBytes[1:len(keyBytes)-1])] = v
}
return json.Marshal(tempMap)
}
type YourStruct struct {
Ranks RanksMap `json:"ranks"`
}
func main() {
jsonData := `{
"ranks": {
"2021-12-22T00:00:00Z": "Over page 5",
"2021-12-23T00:00:00Z": "Over page 5",
"2021-12-24T00:00:00Z": "Over page 5",
"2021-12-25T00:00:00Z": "112",
"2021-12-26T00:00:00Z": "152",
"2021-12-27T00:00:00Z": "138",
"2021-12-28T00:00:00Z": "105",
"2021-12-29T00:00:00Z": "Pending data refresh",
"2021-12-30T00:00:00Z": "No data"
}
}`
var data YourStruct
if err := json.Unmarshal([]byte(jsonData), &data); err != nil {
panic(err)
}
// 使用示例
for date, value := range data.Ranks {
fmt.Printf("Date: %v, Value: %s\n", time.Time(date), value)
}
}
另一种更简洁的方法是使用json.RawMessage进行两步解析:
package main
import (
"encoding/json"
"time"
)
type YourStruct struct {
Ranks map[time.Time]string `json:"-"`
}
func (s *YourStruct) UnmarshalJSON(data []byte) error {
type Alias YourStruct
aux := &struct {
Ranks map[string]string `json:"ranks"`
*Alias
}{
Alias: (*Alias)(s),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
s.Ranks = make(map[time.Time]string)
for dateStr, value := range aux.Ranks {
t, err := time.Parse(time.RFC3339, dateStr)
if err != nil {
return err
}
s.Ranks[t] = value
}
return nil
}
func (s YourStruct) MarshalJSON() ([]byte, error) {
ranksMap := make(map[string]string)
for t, value := range s.Ranks {
ranksMap[t.Format(time.RFC3339)] = value
}
return json.Marshal(struct {
Ranks map[string]string `json:"ranks"`
}{
Ranks: ranksMap,
})
}
这两种方法都能正确处理ISO8601格式的日期键,并提供双向的JSON序列化/反序列化支持。

