在Golang中解析带有日期的嵌套JSON对象

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

Unmarshalling nested JSON objects with dates in Golang

问题

我是一个对Golang不太熟悉的新手。我费了很大的努力才完成了一些任务。我正在处理包含嵌套日期的JSON文件。

我找到了一些解决方案,可以将JSON数据中的日期解组为time.Time,但是我在处理嵌套日期时遇到了困难。

下面的代码(在StackOverflow上找到)很容易理解,因为它创建了一个用户定义的函数,首先将时间对象解析为string,然后使用time.Parse将其解析为time.Time

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"time"
)

const dateFormat = "2006-01-02"

const data = `{
	"name": "Gopher",
	"join_date": "2007-09-20"
}`

type User struct {
	Name     string    `json:"name"`
	JoinDate time.Time `json:"join_date"`
}

func (u *User) UnmarshalJSON(p []byte) error {
	var aux struct {
		Name     string `json:"name"`
		JoinDate string `json:"join_date"`
	}

	err := json.Unmarshal(p, &aux)
	if err != nil {
		return err
	}

	t, err := time.Parse(dateFormat, aux.JoinDate)
	if err != nil {
		return err
	}

	u.Name = aux.Name
	u.JoinDate = t

	return nil
}

func main() {
	var u User
	err := json.Unmarshal([]byte(data), &u)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(u.JoinDate.Format(time.RFC3339))
} 

到目前为止,一切顺利。
现在,我想扩展它,以处理JSON中的嵌套日期字段,就像下面的示例一样:

[{
	"name": "Gopher",
	"join_date": "2007-09-20",
	"cashflow": [
		{"date": "2021-02-25", 
		"amount": 100},
		{"date": "2021-03-25",
		"amount": 105}
	]
}]

我想要的struct是:

type Record []struct {
	Name     string `json:"name"`
	JoinDate time.Time `json:"join_date"`
	Cashflow []struct {
		Date   time.Time `json:"date"`
		Amount int    `json:"amount"`
	} `json:"cashflow"`
}

谢谢帮助。

英文:

I'm a noob with Golang. I managed to get some things done with lots of effort.
I'm dealing with JSON files containing dates in a nested way.

I came across some workaround to unmarshal dates from JSON data into time.Time but I'm having a hard time dealing with nested ones.

The following code (obtained here in StackOverflow) is easy to understand since creates a user-defined function to parse the time objects first to a string and then to time.Time with time.Parse.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"time"
)

const dateFormat = "2006-01-02"

const data = `{
	"name": "Gopher",
	"join_date": "2007-09-20"
}`

type User struct {
	Name     string    `json:"name"`
	JoinDate time.Time `json:"join_date"`
}

func (u *User) UnmarshalJSON(p []byte) error {
	var aux struct {
		Name     string `json:"name"`
		JoinDate string `json:"join_date"`
	}

	err := json.Unmarshal(p, &aux)
	if err != nil {
		return err
	}

	t, err := time.Parse(dateFormat, aux.JoinDate)
	if err != nil {
		return err
	}

	u.Name = aux.Name
	u.JoinDate = t

	return nil
}

func main() {
	var u User
	err := json.Unmarshal([]byte(data), &u)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(u.JoinDate.Format(time.RFC3339))
} 

So far, so good.
Now I would like to extend it in order to handle the nested date fields in the JSON, like the example below:

[{
	"name": "Gopher",
	"join_date": "2007-09-20",
	"cashflow": [
		{"date": "2021-02-25", 
		"amount": 100},
		{"date": "2021-03-25",
		"amount": 105}
	]
}]

The struct that I would like to get is:

type Record []struct {
	Name     string `json:"name"`
	JoinDate time.Time `json:"join_date"`
	Cashflow []struct {
		Date   time.Time `json:"date"`
		Amount int    `json:"amount"`
	} `json:"cashflow"`
}

Thanks for the help.

答案1

得分: 7

要解决这个问题,你可以使用你已经拥有的模式,为内部结构编写一个单独的解组函数。你可以通过将内部结构提升为自己的命名结构,然后编写该函数来实现。

type CashflowRec struct {
    Date   time.Time `json:"date"`
    Amount int       `json:"amount"`
}

type Record struct {
    Name     string        `json:"name"`
    JoinDate time.Time     `json:"join_date"`
    Cashflow []CashflowRec `json:"cashflow"`
}

你已经展示了如何为CashflowRec编写解组函数,它看起来几乎与你的User函数相同。Record的解组函数将在调用时使用它:

func (u *Record) UnmarshalJSON(p []byte) error {
    var aux struct {
        Name     string        `json:"name"`
        JoinDate string        `json:"join_date"`
        Cashflow []CashflowRec `json:"cashflow"`
    }

    err := json.Unmarshal(p, &aux)
    // ...
}

工作示例:https://go.dev/play/p/1X7BJ4NETM0

附注1:在查看这个问题时,我学到了一些有趣的东西:因为你提供了自己的解组函数,所以你实际上不需要在原始结构中使用json标签。这些标签是json包提供的解组器的提示。为了以防万一以后需要对结构进行编组,你可能仍然应该保留它们。这是一个没有这些标签的工作示例:https://go.dev/play/p/G2VWopO_A3t

附注2:你可能会发现不使用time.Time而是创建自己的新类型,然后为该类型编写自己的解组器更简单。这使你只需编写一个解组器,但是否这样做取决于以后对结构的其他操作。这是一个仍然使用你的嵌套匿名结构的工作示例:https://go.dev/play/p/bJUcaw3_r41

type dateType time.Time

type Record struct {
    Name     string   `json:"name"`
    JoinDate dateType `json:"join_date"`
    Cashflow []struct {
        Date   dateType `json:"date"`
        Amount int      `json:"amount"`
    } `json:"cashflow"`
}

func (c *dateType) UnmarshalJSON(p []byte) error {
    var s string
    if err := json.Unmarshal(p, &s); err != nil {
        return err
    }
    t, err := time.Parse(dateFormat, s)
    if err != nil {
        return err
    }
    *c = dateType(t)
    return nil
}
英文:

To solve this using the patterns you've already got, you can write a separate unmarshalling function for the inner struct. You can do that by hoisting the inner struct to its own named struct, and then writing the function.

type CashflowRec struct {
    Date   time.Time `json:"date"`
    Amount int       `json:"amount"`
}

type Record struct {
    Name     string        `json:"name"`
    JoinDate time.Time     `json:"join_date"`
    Cashflow []CashflowRec `json:"cashflow"`
}

You've already shown how to write the unmarshalling function for CashflowRec, it looks almost the same as your User function. The unmarshalling function for Record will make use of that when it calls

func (u *Record) UnmarshalJSON(p []byte) error {
    var aux struct {
    	Name     string        `json:"name"`
	    JoinDate string        `json:"join_date"`
	    Cashflow []CashflowRec `json:"cashflow"`
    }

    err := json.Unmarshal(p, &aux)

Working example: https://go.dev/play/p/1X7BJ4NETM0

aside 1 Something amusing I learned while looking at this: because you've provided your own unmarshalling function, you don't actually need the json tags in your original structs. Those are hints for the unmarshaller that the json package provides. You should probably still leave them in, in case you have to marshal the struct later. Here's it working without those tags: https://go.dev/play/p/G2VWopO_A3t

aside 2 You might find it simpler not to use time.Time, but instead create a new type of your own, and then give that type its own unmarshaller. This gives you the interesting choice for writing only that one unmarshaller, but whether or not this is a win depends on what else you do with the struct later on. Working example that still uses your nested anonymous structs: https://go.dev/play/p/bJUcaw3_r41

type dateType time.Time

type Record struct {
    Name     string   `json:"name"`
    JoinDate dateType `json:"join_date"`
    Cashflow []struct {
    	Date   dateType `json:"date"`
	    Amount int      `json:"amount"`
    } `json:"cashflow"`
}

func (c *dateType) UnmarshalJSON(p []byte) error {
    var s string
    if err := json.Unmarshal(p, &s); err != nil {
    	return err
    }
   t, err := time.Parse(dateFormat, s)
     if err != nil {
    	return err
    }
    *c = dateType(t)
    return nil
}

huangapple
  • 本文由 发表于 2022年3月11日 02:21:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/71428995.html
匿名

发表评论

匿名网友

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

确定