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

11 回复

这取决于你在反序列化后想要如何处理数据。

更多关于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序列化/反序列化支持。

回到顶部