Golang中JSON转换时格式不正确的问题如何解决

Golang中JSON转换时格式不正确的问题如何解决 你好,

当我处理JSON时,会出现奇怪的字符。如果保持JSON原样,虽然不会得到乱码,但也得不到键名。请问我哪里做错了?

[{0 0 ACCEPT all -- * * 1.2.3.4/32 0.0.0.0/0 }
package main

import (
        "fmt"
        "encoding/json"
        "github.com/coreos/go-iptables/iptables"
)


func main() {
        ipt, _ := iptables.New()

        listTab, _ := ipt.StructuredStats("filter","FROM_API")
        jsonAPI, _ := json.MarshalIndent(listTab, "", "  ")

        fmt.Println(string(jsonAPI))


}

Mask字段的输出是乱码:

[
  {
    "pkts": 0,
    "bytes": 0,
    "target": "ACCEPT",
    "prot": "all",
    "opt": "--",
    "in": "*",
    "out": "*",
    "source": {
      "IP": "1.2.3.4",
      "Mask": "/////w=="
    },
    "destination": {
      "IP": "0.0.0.0",
      "Mask": "AAAAAA=="
    },
    "options": ""
  }]

更多关于Golang中JSON转换时格式不正确的问题如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

感谢您的帮助!

更多关于Golang中JSON转换时格式不正确的问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


那么我该如何动态地解码它呢?

这不是乱码,这是base64编码的数据。

原始数据很可能在一个 byte[] 或类似的类型中。

感谢您的回复!这两种格式(CIDR或点分十进制)都可以用,因为这里不使用IPv6。您知道如何实现这个吗?

感谢 @skillian 澄清了我为何会得到编码后的值。至于我的需求,是希望以人类可读的方式显示 Mask 的值,而不是 base64 编码。

“人类可读”是指像 255.255.255.0 这样的点分十进制表示法吗?还是指像 /24 这样的 CIDR 表示法?如果是点分十进制,那么 IPv6 地址该如何表示呢?

我唯一能想到的方法是复制结构体定义(以及任何子结构的定义;在本例中是 *net.IPNet),替换复制出的结构体中所有对其他复制类型的引用,然后手动复制所有字段:Go Playground - The Go Programming Language

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

嗨,@mistergo,欢迎来到论坛!

Go 的文档相当详尽,但我得承认,有时很难看出不同包之间是如何交互的,这正是 netencoding/json 包之间发生的情况:

解释

我查阅了该包的 IPTables.StructuredStats 函数的文档,看到它返回一个 Stat 类型的切片。

该类型的 SourceDestination 字段是 net.IPNet 类型,而 net.IPNet 本身包含你看到的 IPMask 字段。

json.Marshal 函数的文档是这样说的:

如果遇到的值实现了 Marshaler 接口并且不是 nil 指针,Marshal 会调用其 MarshalJSON 方法来生成 JSON。如果没有 MarshalJSON 方法,但该值实现了 encoding.TextMarshaler 接口,Marshal 会调用其 MarshalText 方法并将结果编码为 JSON 字符串

(强调部分是我加的)

IP 字段的类型是 net.IP,该类型有一个 MarshalText 方法,因此当你将其序列化为 JSON 时,会得到一个漂亮的字符串表示。Mask 字段的类型是 IPMask,它没有 MarshalText 方法,因此你会得到序列化 IPMask 底层类型 []byte 的默认行为。Marshal 文档关于 []byte 是这样说的:

数组和切片值编码为 JSON 数组,但 []byte 编码为 base64 编码的字符串,而 nil 切片编码为 null JSON 值。

(强调部分是我加的)

这就解释了为什么 Mask 字段显示为 base-64 编码:它没有 MarshalJSONMarshalText 方法,并且其底层类型是 []byte,默认情况下会编码为 base-64。

问题

所以我的问题是:你能澄清一下“动态解码”是什么意思吗?如果你想以不同的格式序列化为 JSON,你将需要定义自己的结构体,可能还需要定义你自己的 MarshalText/MarshalJSON 方法。如果你能澄清你希望看到什么,而不是你现在得到的结果,我们可以给你更具体的答案。

问题出在Mask字段的类型上。Mask字段是net.IPMask类型,它本质上是[]byte,而json.Marshal会将[]byte编码为base64字符串,所以出现了"/////w==""AAAAAA=="这样的输出。

要解决这个问题,你需要自定义JSON序列化。有两种方法:

方法一:使用自定义类型包装(推荐)

package main

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

// 自定义IPMask类型,实现json.Marshaler接口
type JSONIPMask net.IPMask

func (m JSONIPMask) MarshalJSON() ([]byte, error) {
    // 将IPMask转换为字符串表示
    if m == nil {
        return []byte(`null`), nil
    }
    // 获取掩码长度
    ones, bits := net.IPMask(m).Size()
    if bits == 0 {
        return []byte(`""`), nil
    }
    return json.Marshal(fmt.Sprintf("%d", ones))
}

// 或者如果你想显示为点分十进制格式
func (m JSONIPMask) MarshalJSON() ([]byte, error) {
    if m == nil {
        return []byte(`null`), nil
    }
    // 转换为net.IP再转换为字符串
    if len(m) == 0 {
        return []byte(`""`), nil
    }
    // 对于IPv4掩码
    if len(m) == net.IPv4len {
        return json.Marshal(net.IP(m).String())
    }
    // 对于IPv6掩码
    return json.Marshal(net.IP(m).String())
}

方法二:修改你的结构体定义

如果你能控制原始结构体,可以这样修改:

type Source struct {
    IP   net.IP   `json:"IP"`
    Mask string   `json:"Mask"` // 改为string类型
}

// 在序列化前转换
func convertMaskToString(mask net.IPMask) string {
    if mask == nil {
        return ""
    }
    ones, bits := mask.Size()
    if bits == 0 {
        return ""
    }
    return fmt.Sprintf("%d", ones)
    // 或者使用点分十进制:return net.IP(mask).String()
}

// 使用示例
listTab := getYourData() // 获取原始数据
for i := range listTab {
    listTab[i].Source.Mask = convertMaskToString(listTab[i].Source.Mask)
    listTab[i].Destination.Mask = convertMaskToString(listTab[i].Destination.Mask)
}
jsonAPI, _ := json.MarshalIndent(listTab, "", "  ")

方法三:使用结构体标签和自定义序列化

如果你不能修改原始结构体,可以创建一个新的结构体:

type RuleOutput struct {
    Pkts        int64  `json:"pkts"`
    Bytes       int64  `json:"bytes"`
    Target      string `json:"target"`
    Prot        string `json:"prot"`
    Opt         string `json:"opt"`
    In          string `json:"in"`
    Out         string `json:"out"`
    Source      struct {
        IP   string `json:"IP"`
        Mask string `json:"Mask"`
    } `json:"source"`
    Destination struct {
        IP   string `json:"IP"`
        Mask string `json:"Mask"`
    } `json:"destination"`
    Options string `json:"options"`
}

// 转换函数
func convertRule(originalRule YourOriginalRuleType) RuleOutput {
    var rule RuleOutput
    // 复制字段...
    rule.Source.IP = originalRule.Source.IP.String()
    
    // 处理Mask字段
    if originalRule.Source.Mask != nil {
        ones, bits := originalRule.Source.Mask.Size()
        if bits > 0 {
            rule.Source.Mask = fmt.Sprintf("%d", ones)
        }
    }
    
    // 同样处理Destination.Mask...
    return rule
}

选择哪种方法取决于你对代码的控制程度。第一种方法最通用,第二种方法最直接,第三种方法适合处理第三方库的结构体。

回到顶部