将null赋值给JSON字段而不是空字符串

80

由于空字符串是Go语言的string类型的零值/默认值,因此我决定将所有这样的字段定义为interface{}。例如

type student struct {
    FirstName  interface{} `json:"first_name"`
    MiddleName interface{} `json:"middle_name"`
    LastName   interface{} `json:"last_name"`
}

如果特定字段没有值,则我发送的数据应该期望一个null而不是一个空字符串。

这个方法正确吗?还是有更好的方法?请指点一下。


5
指针*string怎么样?还有相关的问题:如何在Go语言中表示可选字符串? - icza
@AryehArmon,你能提供一个标签被指定的文档链接吗?我似乎找不到它。 - thwd
5个回答

147
json包的文档中,指针值编码为所指向的值。空指针编码为null JSON对象。因此,您可以存储对字符串的指针,如果不为空,则将其编码为字符串,如果为空,则将其编码为"null"。
type student struct {
  FirstName  *string `json:"first_name"`
  MiddleName *string `json:"middle_name"`
  LastName   *string `json:"last_name"`
}

1
我尝试过这种方法。这种方法和接口方法是否有任何优势? - Prashant
7
我认为可读性和一致性很重要。显然,使用指针到字符串的方法可以清楚地了解变量的含义。使用接口会让编译器认为FirstName可以是任何东西,这可能会导致错误。 - Asdine
提出的方法不能处理数字等其他类型。有什么建议吗? - stanley_manley
2
@stanley_manley 这种方法对于我的float64非常有效。 - IpsRich

18
对于具有空字符串的JSON对象,最简单的方法是在字段上使用omitempty修饰符。
type student struct {
  FirstName  string `json:"first_name,omitempty"`
  MiddleName string `json:"middle_name,omitempty"`
  LastName   string `json:"last_name"`
}

使用上述声明,在只有指定了first_name的情况下才会在结果json中显示该键。另一方面,如果未指定last_name,则其将始终以“”值的形式显示在结果中。

现在,当您开始包含0可能是一个值的数字字段时,使用omitempty不会产生预期的效果。0值总是会删除该字段,我们需要能够区分0值和未指定值之间的差别。在这种情况下,建议使用诸如https://github.com/guregu/null这样的库。

更多讨论请参见:https://www.sohamkamani.com/blog/golang/2018-07-19-golang-omitempty/


11

可以使用 https://github.com/guregu/null

type student struct {
FirstName  null.String `json:"first_name"`
MiddleName null.String `json:"middle_name"`
LastName   null.String `json:"last_name"`}

9
@Dragonthoughts的离线资源?他正在链接到Github上的一个项目,这是非常合适的做法。 - Rob_vH
1
@Rob_vH 当那个特定的Github仓库被删除或变为私有时会发生什么?答案将失去重要内容。 - Dragonthoughts
3
没错,但如果 GitHub 存储库不再存在,Go 库也将随之消失。 - Atif

9

另一种方法是通过使用 golang 的 json 库提供的 MarshalJSON 和 UnmarshalJSON 接口方法来绕过这个问题。代码如下所示:

type MyType string
type MyStruct struct {
    A MyType `json:"my_type"`
}

func (c MyType) MarshalJSON() ([]byte, error) {
    var buf bytes.Buffer
    if len(string(c)) == 0 {
        buf.WriteString(`null`)
    } else {
        buf.WriteString(`"` + string(c) + `"`)   // add double quation mark as json format required
    }
    return buf.Bytes(), nil
}

func (c *MyType)UnmarshalJSON(in []byte) error {
    str := string(in)
    if str == `null` {
        *c = ""
        return nil
    }
    res := MyType(str)
    if len(res) >= 2 {
        res = res[1:len(res)-1]     // remove the wrapped qutation
    }
    *c = res
    return nil
}

使用json.Marshal时,MyType的值将被编组为null。

7
你可以使用类似sql.NullString的东西,使用'Valid'来检查它是否是空值。

NullString代表可能为空的字符串。NullString实现了Scanner接口,所以可以用作扫描目标:

type NullString struct {
    String string
    Valid  bool // Valid is true if String is not NULL
}

var s NullString
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
...
if s.Valid {
   // use s.String
} else {
   // NULL value
}

使用如下指针:
type student struct {
  FirstName  *string `json:"first_name"`
  MiddleName *string `json:"middle_name"`
  LastName   *string `json:"last_name"`
}

由于你需要到处检查nil值和解引用(如下所示),可能会导致意外崩溃:

if obj.FirstName != nil {
   fmt.Print("%s", *obj.FirstName)
}

我比较了两种解决方案,并在我的大量生产代码中选择了前一种方法……它运行良好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接