将JSON字符串解组成常量

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

Unmarshalling JSON string into a constant

问题

在我的Go代码中,我有以下情况:

type Operator int
const (
    UNKNOWN  Operator = iota
    EQUALS
    CONTAINS
    BETWEEN
    DISTANCE
)

type Filter struct {
    Field string      `json:"field"`
    Operator Operator `json:"operator"`
    Values []string   `json:"values"`
}

我期望的JSON应该如下所示:

{
    "operator": "EQUALS",
    "field": "name",
    "values": [ "John", "Doe" ]
}

我能否创建一个映射,以便 json.Unmarshal 可以在 Filter 结构体中设置正确的操作符常量?
我知道 Unmarshaler 接口,但我不认为它可以真正用于常量值..

我真的很想保留我的Go代码中的常量,因为这样可以很好地强制进行类型检查和一致性,当我传递它们时。

英文:

In my Go code I have the following situation:

type Operator int
const (
    UNKNOWN  Operator = iota
    EQUALS
    CONTAINS
    BETWEEN
    DISTANCE
)

type Filter struct {
    Field string      `json:"field"`
    Operator Operator `json:"operator"`
    Values []string   `json:"values"`
}

My expected JSON would look like the following:

{
    "operator": "EQUALS",
    "field": "name",
    "values": [ "John", "Doe" ]
}

Can I create a mapping such that json.Unmarshal will set the right operator constant in the Filter struct?
I am aware of the Unmarshaler interface but I don't think this can really be used on a constant value..

I would really like to keep the constants in my go code since this nicely enforces type checking and consistency when I pass them around.

答案1

得分: 7

encoding/json Unmarshaler接口将与Operator类型一起使用,但它必须具有指针作为其接收器:

func (o *Operator) UnmarshalJSON(b []byte) error {
    str := strings.Trim(string(b), `"`)

    switch {
        case str == "CONTAINS":
            *o = CONTAINS

        default:
            *o = UNKNOWN
            // or return an error...
    }

    return nil
}

JSON解码器将获取Filter结构体中Operator字段的地址,并在其上调用UnmarshalJSON方法。

请注意,您可以通过将UnmarshalJSON更改为上面的UnmarshalText来实现更通用的encoding/TextUnmarshaler

这是一个示例:http://play.golang.org/p/szcnC6L86u

可以说,对于Operator,使用字符串基本类型可能更简单:http://play.golang.org/p/FCCg1NOeYw

英文:

The encoding/json Unmarshaler interface will work with the Operator type, but it must have a pointer as its receiver:

func (o *Operator) UnmarshalJSON(b []byte) error {
    str := strings.Trim(string(b), `"`)

    switch {
        case str == "CONTAINS":
            *o = CONTAINS

        default:
            *o = UNKNOWN
            // or return an error...
    }

    return nil
}

The JSON decoder will take the address of the Operator field from the Filter struct and invoke the UnmarshalJSON method on it.

Note that you could implement the more generic encoding/TextUnmarshaler instead, by changing UnmarshalJSON to UnmarshalText above.

Here is a playground example: http://play.golang.org/p/szcnC6L86u

Arguably it might be simpler to use a string base type for Operator instead: http://play.golang.org/p/FCCg1NOeYw

答案2

得分: 3

你可以将对象包装在一个Unmarshaler中。

例如:

package main

import (
    "encoding/json"
    "fmt"
)

type Operator int

const (
    UNKNOWN Operator = iota
    EQUALS
    CONTAINS
    BETWEEN
    DISTANCE
)

type Filter struct {
    Field       string   `json:"field"`
    RawOperator string   `json:"operator"`
    Operator    Operator `json:"-"`
    Values      []string `json:"values"`
}

type FilterUnmarshaler struct {
    Filter
}

func (f *FilterUnmarshaler) UnmarshalJSON(data []byte) error {
    if err := json.Unmarshal(data, &f.Filter); err != nil {
        return err
    }
    switch f.RawOperator {
    case "UNKOWN":
        f.Operator = UNKNOWN
    case "EQUALS":
        f.Operator = EQUALS
    case "CONTAINS":
        f.Operator = CONTAINS
    case "BETWEEN":
        f.Operator = BETWEEN
    case "DISTANCE":
        f.Operator = DISTANCE
    default:
        return fmt.Errorf("Unknown operator %s", f.RawOperator)
    }
    return nil
}

func main() {
    rawJson := []byte(`
    {
        "operator": "BETWEEN",
        "field": "name",
        "values": [ "John", "Doe" ]
    }
    `)
    val := &FilterUnmarshaler{}
    if err := json.Unmarshal(rawJson, val); err != nil {
        panic(err)
    }
    fmt.Printf("%#v\n", val)
}

在不使用包装器的情况下,我没有找到如何避免无限递归。

这个示例展示了如何解组,但你很可能也希望在编组时应用它,以便从带有整数操作符的对象生成正确的 JSON。

你可能还想创建一个映射而不是使用常量。或者两者都创建,创建一个映射并手动填充它,包括常量和它们的字符串等效项。

英文:

You can wrap your object in an Unmarshaler.
ex:

package main
import (
"encoding/json"
"fmt"
)
type Operator int
const (
UNKNOWN Operator = iota
EQUALS
CONTAINS
BETWEEN
DISTANCE
)
type Filter struct {
Field       string   `json:"field"`
RawOperator string   `json:"operator"`
Operator    Operator `json:"-"`
Values      []string `json:"values"`
}
type FilterUnmarshaler struct {
Filter
}
func (f *FilterUnmarshaler) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &f.Filter); err != nil {
return err
}
switch f.RawOperator {
case "UNKOWN":
f.Operator = UNKNOWN
case "EQUALS":
f.Operator = EQUALS
case "CONTAINS":
f.Operator = CONTAINS
case "BETWEEN":
f.Operator = BETWEEN
case "DISTANCE":
f.Operator = DISTANCE
default:
return fmt.Errorf("Unkown operator %s", f.RawOperator)
}
return nil
}
func main() {
rawJson := []byte(`
{
"operator": "BETWEEN",
"field": "name",
"values": [ "John", "Doe" ]
}
`)
val := &FilterUnmarshaler{}
if err := json.Unmarshal(rawJson, val); err != nil {
panic(err)
}
fmt.Printf("%#v\n", val)
}

Without using a wrapper, I didn't find how not to fall in an infinite recursion.

This example shows how to unmarshal but you most likely want to apply it to Marshal as well in order to have a correct json with Operator strings from your object with integer operator.

You might also want to create a map instead of const. Or both, create a map and manually populate it with your const and their string equivalent.

huangapple
  • 本文由 发表于 2014年2月7日 01:04:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/21609363.html
匿名

发表评论

匿名网友

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

确定