Golang中JSON标签无效的问题如何解决

Golang中JSON标签无效的问题如何解决 假设我们有一个结构体:

type DailyExchangeDetails struct {
	Date     string `json:"date"`
	Currency map[string]float64 `json:"btc"`
}

但是,JSON属性(字段名)可能不同——它可能是“btc”,但也可能是“eur”或“pln”。如何创建一个通用的结构体,其JSON标签能够覆盖这种情况?问题在于,如果没有正确的标签(例如:`json:"pln"`),JSON编码就无法正常工作。此外,如果我省略JSON标签,它仍然无法正常工作。

这是用于编码的代码:

func fromJSON[T any](res *http.Response, target *T) error {
	//
	buf := new(bytes.Buffer)
	buf.ReadFrom(res.Body)

	err := json.Unmarshal(buf.Bytes(), target)

	if err != nil {
		log.Print(err)
		log.Fatalf("Error unmarshal")
	}
	return err
}

这是JSON文件的一个片段:

{
	"date": "2024-05-15",
	"btc": {
		"$myro": 388814.57395719,
		"$wen": 399391074.4242139,
		"00": 855345.08008341,
		"0x0": 205498.96296352,
		"1000sats": 237075658.30367133,
		"10set": 62400.24253448,
		"1inch": 173185.53022438,
		"aave": 765.26611448,
		"abt": 15467.94403195
         }
}

我注意到问题出在map上。这是解组后返回的结构体: {2024-05-16 map[]}。如你所见,map不知为何是空的。


更多关于Golang中JSON标签无效的问题如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

好的。感谢您花时间解释。

更多关于Golang中JSON标签无效的问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


是的,我现在明白了。在这种情况下,你可以寻找支持多个标签定义的第三方 JSON 库。使用标准库的解决方案将是编写你自己的反序列化方法,或者解码到 map[string]any 而不是结构体。

func main() {
    fmt.Println("hello world")
}

如果不为结构体字段使用显式标签,那么 json 会默认字段名等于结构体字段名。在你的示例中,如果从结构体中移除标签,它会期望 json 文件中的字段是 datecurrency

如果你添加JSON标签,它就能正常工作。如果不添加,它就无法工作。正如我所说,JSON字段名称可以不同,但结构是相同的。我们有一个字符串和一个映射。

{
	"date": "2024-05-15",
	"pln": {
		"$myro": 388814.57395719,
		"$wen": 399391074.4242139,
		"00": 855345.08008341,
		"0x0": 205498.96296352,
		"1000sats": 237075658.30367133,
		"10set": 62400.24253448,
		"1inch": 173185.53022438,
		"aave": 765.26611448,
		"abt": 15467.94403195
         }
}

如果你把货币改为PLN,它就无法工作。

你好。你是如何将你的 T 传递给函数的?我写了一个简单的测试,一切都按预期解析了。

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
)

type DailyExchangeDetails struct {
	Date     string             `json:"date"`
	Currency map[string]float64 `json:"btc"`
}

func main() {
	data, err := os.ReadFile("test.json")
	if err != nil {
		log.Println(err)
		return
	}

	var t DailyExchangeDetails

	if err := fromJSON(data, &t); err != nil {
		log.Println(err)
		return
	}

	fmt.Printf("%#v\n", t)
}

func fromJSON[T any](data []byte, target T) error {
	buf := bytes.NewBuffer(data)

	return json.Unmarshal(buf.Bytes(), &target)
}

输出:

% go run main.go
main.DailyExchangeDetails{Date:"2024-05-15", Currency:map[string]float64{"$myro":388814.57395719, "$wen":3.993910744242139e+08, "00":855345.08008341, "0x0":205498.96296352, "1000sats":2.3707565830367133e+08, "10set":62400.24253448, "1inch":173185.53022438, "aave":765.26611448, "abt":15467.94403195}}

更新:或者你的意思只是无法用额外的标签解析它?

假设我们有一个结构体:

type DailyExchangeDetails struct {
	Date     string `json:"date"`
	Currency map[string]float64 `json:"btc"`
}

但是 JSON 属性、字段名称可能不同——可能是 “btc”,但也可能是 “eur” 或 “pln”。如何创建一个通用的结构体,其 json 标签能够覆盖这种情况?问题在于,如果没有正确的标签,例如 json:"pln",JSON 的编码就无法正常工作。此外,如果我省略 JSON 标签,它仍然无法正常工作。

这是用于编码的代码:

func fromJSON[T any](res *http.Response, target *T) error {
	//
	buf := new(bytes.Buffer)
	buf.ReadFrom(res.Body)

	err := json.Unmarshal(buf.Bytes(), target)

	if err != nil {
		log.Print(err)
		log.Fatalf("Error unmarshal")
	}
	return err
}

这是一个 JSON 文件的片段:

{
	"date": "2024-05-15",
	"btc": {
		"$myro": 388814.57395719,
		"$wen": 399391074.4242139,
		"00": 855345.08008341,
		"0x0": 205498.96296352,
		"1000sats": 237075658.30367133,
		"10set": 62400.24253448,
		"1inch": 173185.53022438,
		"aave": 765.26611448,
		"abt": 15467.94403195
         }
}

我注意到问题出在 map 上。这是从 unmarshal 返回的结构体: {2024-05-16 map[]}。如你所见,map 不知为何是空的。

感谢你帮助我,我之前很困惑。你让我很开心!

问题出在结构体定义与JSON数据结构不匹配。JSON中的btc字段是一个对象(键值对),但你的结构体将其定义为map[string]float64,这本身是正确的。然而,实际问题是JSON中的btc对象包含嵌套的键值对,而你的结构体期望的是直接映射。

根据你提供的JSON片段,btc字段的值是一个对象,其键是货币代码(如$myro$wen等),值是浮点数。因此,结构体定义是正确的。问题可能在于JSON数据的实际结构与示例不符,或者存在其他解析问题。

以下是一个修正后的示例,确保结构体与JSON匹配,并添加了错误处理:

package main

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

type DailyExchangeDetails struct {
    Date     string             `json:"date"`
    Currency map[string]float64 `json:"btc"` // JSON字段名固定为"btc"
}

func main() {
    // 示例JSON数据(与你的片段一致)
    jsonData := `{
        "date": "2024-05-15",
        "btc": {
            "$myro": 388814.57395719,
            "$wen": 399391074.4242139,
            "00": 855345.08008341
        }
    }`

    var result DailyExchangeDetails
    err := json.Unmarshal([]byte(jsonData), &result)
    if err != nil {
        log.Fatalf("Error unmarshaling JSON: %v", err)
    }

    fmt.Printf("Date: %s\n", result.Date)
    fmt.Printf("Currency map: %v\n", result.Currency)
    // 输出示例:Currency map: map[$myro:388814.57395719 $wen:3.993910744242139e+08 00:855345.08008341]
}

如果你的JSON中btc字段名是动态的(例如可能是eurpln),则不能使用固定标签。此时,你需要使用map[string]interface{}进行通用解析,或者使用自定义解组逻辑。以下是使用map[string]interface{}的解决方案:

package main

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

func main() {
    // 示例JSON,其中货币字段名可能是btc、eur或pln
    jsonData := `{
        "date": "2024-05-15",
        "pln": {
            "$myro": 388814.57395719,
            "$wen": 399391074.4242139
        }
    }`

    var rawData map[string]interface{}
    err := json.Unmarshal([]byte(jsonData), &rawData)
    if err != nil {
        log.Fatalf("Error unmarshaling JSON: %v", err)
    }

    date := rawData["date"].(string)
    fmt.Printf("Date: %s\n", date)

    // 遍历所有键,找到货币字段(假设除"date"外都是货币字段)
    for key, value := range rawData {
        if key != "date" {
            currencyMap := value.(map[string]interface{})
            fmt.Printf("Currency field '%s': %v\n", key, currencyMap)
        }
    }
}

如果必须使用结构体,且货币字段名动态变化,可以结合使用json.RawMessage和自定义解组:

package main

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

type DailyExchangeDetails struct {
    Date     string          `json:"date"`
    Currency json.RawMessage // 原始JSON数据,用于动态字段
}

func main() {
    jsonData := `{
        "date": "2024-05-15",
        "eur": {
            "$myro": 388814.57395719,
            "$wen": 399391074.4242139
        }
    }`

    var result DailyExchangeDetails
    err := json.Unmarshal([]byte(jsonData), &result)
    if err != nil {
        log.Fatalf("Error unmarshaling JSON: %v", err)
    }

    fmt.Printf("Date: %s\n", result.Date)

    // 动态解析Currency字段
    var currencyMap map[string]float64
    err = json.Unmarshal(result.Currency, &currencyMap)
    if err != nil {
        log.Fatalf("Error unmarshaling currency: %v", err)
    }
    fmt.Printf("Currency map: %v\n", currencyMap)
}

对于你的fromJSON函数,确保传入的target参数是正确类型的指针。例如:

func fromJSON[T any](res *http.Response, target *T) error {
    defer res.Body.Close()
    decoder := json.NewDecoder(res.Body)
    err := decoder.Decode(target)
    if err != nil {
        return fmt.Errorf("error unmarshaling JSON: %w", err)
    }
    return nil
}

// 使用示例
var details DailyExchangeDetails
err := fromJSON(response, &details)

检查你的实际JSON数据是否与结构体匹配。如果问题仍然存在,请验证HTTP响应体中的JSON是否与示例一致,或者是否有额外的嵌套层级。

回到顶部