data

package module
v1.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2020 License: BSD-2-Clause Imports: 13 Imported by: 1

README

go-data:可方便稳定序列化的树状数据结构

go-data 旨在封装所有类似 JSON 结构数据的处理方法,可以用于处理数据或配置文件(JSON、TOML、YAML 等)。 其核心数据结构 Datadata.RawData 的包装,但不同于简单的 map,通过 MakeEncoder 获得的 Data 可以保证里面不包含任何无法序列化的结构,例如 funcchan 等,也不会包含任何指针、interface 等。通过这样的加工,可以保证 Data 使用任何序列化和反序列化工具,比如 json.Marshaljson.Unmarshal,得到稳定输出(除非遇到了工具的 bug,比如 JSON 无法表达超过 2^53 的整数)。

使用方法

构造 Data

MakeEncoder 可以将任意结构转换成 Data。需要注意,由于 Data 本质上是一个 map,所以只有 struct、结构的指针、map[string]T 可以成功转换成 Data

type T struct {
    Foo     int    `sample:"foo"`
    Bar     string `sample:"bar"`
    Empty   uint   `sample:"empty,omitempty"` // 设置 omitempty 后,如果 Empty 未设置值则不会被放入 Data
    Skipped bool   `sample:"-"`               // 名字为 -,则代表这个字段会被忽略
    *Embedded      `sample:",squash"`         // 设置 squash,这个结构会被展开(inline)到上层结构中去
}

type Embedded struct {
    Player bool `sample:"player"`
}

func main() {
    t := &T{
        Foo: 123,
        Bar: "player",
        Embedded: &Embedded{
            Player: true,
        },
    }
    enc := data.Encoder{
        TagName: "sample", // 自定义 field tag
    }
    d := enc.Encode(t)
    fmt.Println(d)

    // Output:
    // {
    //     "foo": 123,
    //     "bar": "player",
    //     "player": true,
    // }
}
读取数据

Data 提供一些方法来方便的读取里面的数据。

func main() {
    d := data.Make(data.RawData{
        "foo": data.RawData{
            "bar": 123,
        },
    })

    fmt.Println(d.Get("foo", "bar")) // 输出:123
    fmt.Println(d.Query("foo.bar"))  // 输出:123
}
解析数据

通过使用 Decoder 可以将 Data 解析到任意 Go 结构里面去。

当前支持以下类型的解析:

  • 布尔:bool
  • 所有的整型:int/int8/.../int64/uint/uint8/.../uint64
  • 所有的浮点:float32/float64
  • 所有的复数:complex64/complex128
  • 字符串:string
  • 各种 Go 内置类型:map/slice/array/struct
  • 时间类型:time.Time/time.Duration

其中,time.Duration 的源数据需要是符合 time.ParseDuration 规则的字符串,比如 "2m30s"

type T struct {
    Foo int           `sample:"foo"`
    Bar string        `sample:"bar"`
    Dur time.Duration `sample:"dur"`
}

func main() {
    d := data.Make(data.RawData{
        "foo": 123,
        "bar": "player",
        "dur": "2m30s",
    })
    dec := data.Decoder{
        TagName: "sample", // 自定义 field tag
    }

    var t T
    enc.Decode(d, &t)
    fmt.Println(t.Foo, t.Bar, t.Dur)
    fmt.Println(t.Dur == 2*time.Minute+30*time.Second)

    var foo int
    enc.DecodeQuery(d, "foo", &foo)
    fmt.Println("foo:", foo)

    // 输出:
    // 123  player  2m30s
    // true
    // foo:    123
}
序列化和反序列化

为了方便将 Data 进行持久化存储,特别提供了专用的格式来进行序列化和反序列化。

如果要将 Data 序列化,可以直接调用 Data#String 方法。如果要反序列化,则使用 Parse 函数。

func main() {
    d := data.Make(data.RawData{
        "foo": 123,
        "bar": "player",
    })
    str := d.String()
    fmt.Println(str)

    parsed, err := data.Parse(str)            // 输出:<json>{"bar":"player","foo":123}
    fmt.Println(err)                          // 输出:nil
    fmt.Println(reflect.DeepEqual(parsed), d) // 输出:true
}
通过 Patch 进行增量更新

由于 Data 底层数据结构相对复杂,手动更新数据会出现很多问题,比如难以跟踪变化,在持久化存储时会出现难以追查的并发冲突问题。 为了解决这个,推荐所有对 Data 的变更都采用 Patch 来实现。

func main() {
    patch := data.NewPatch()

    // 删除 d["v2"]、d["v3"][1]、d["v4"]["v4-4"]。
    patch.Add([]string{"v2", "v3.1", "v4.v4-1"}, nil)

    // 添加数据。
    patch.Add(nil, map[string]Data{
        // 在根添加数据。
        "": data.Make(data.RawData{
            "v1": []int{2, 3},
            "v2": 456,
        }),

        // 在 d["v4"] 里添加数据。
        "v4": data.Make(data.RawData{
            "v4-1": "new",
        }),
    })

    // 同时删除并添加数据。
    patch.Add([]string{"v4.v4-2"}, map[string]Data{
        "v4": data.Make(data.RawData{
            "v4-2": data.RawData{
                "new": true,
            },
        }),
    })

    d := data.Make(data.RawData{
        "v1": []int{1},
        "v2": 123,
        "v3": []string{"first", "second", "third"},
        "v4": data.RawData{
            "v4-1": "old",
            "v4-2": data.RawData{
                "old": true,
            },
        },
    })
    patch.ApplyTo(&d)

    fmt.Println(d)

    // Output:
    // <json>{"v1":[1,2,3],"v2":456,"v3":["first","third"],"v4":{"v4-1":"new","v4-2":{"new":true}}}
}

工作原理

将数据编码成 Data 或者将 Data 数据提取到任意 Go 结构,这个的工作原理与 json.Marshaljson.Unmarshal 类似,可以查阅相关文章了解实现原理,这里不赘述。

为了保证 Data 序列化/反序列化结果能够稳定,这个库做了几件重要的事情:

  • 将所有结构、指针、map、interface 等变成标准的 Data 类型;
  • 将所有具有宽度的类型,比如 int/int8/int16/int32 等,都转化成最大尺寸的类型,比如 int64,这样保证同样的数据通过 MakeEncoder 得到 Data 时能够稳定;
  • 将所有 slice、array 也标准化成 slice,并且如果 slice 的元素类型是 int/int8/int16/int32 等,也都转化成最大尺寸的类型,保证 slice 类型也能稳定;
  • 消除所有类型别名,比如如果有一个类型是 type MyInt int,则会变成普通的 int64

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func MergeTo

func MergeTo(target *Data, data ...Data)

MergeTo 将多个 data 从左至右合并到 target 里面,如果有同名的 key 会进行深度遍历进行合并。 如果 target 为 nil,则直接返回,不做任何操作。

具体的合并规则是参考 `Merge` 的文档。

Types

type Data

type Data struct {
	// contains filtered or unexported fields
}

Data 是一种通用的数据存储结构,可以用于表达各种以 JSON、TOML、YAML 等为介质的数据, 也可以用于序列化/反序列化 Go struct。

Data 里面的数据不允许随意修改,只能通过 `MergeTo`、`Patch#ApplyTo` 等方法修改。

func Make

func Make(m map[string]interface{}) Data

Make 将一个普通 m 转化成 Data。 虽然 m 和 Data 类型一样,但是要成为 Data 必须得过滤掉不合法的数据, 也需要将 struct 转化成 Data 格式。

Make 适合用于将 JSON、YAML 等反序列化工具获得的 map[string]interface{} 数据转化成 Data, 假设需要将任意数据转化成 Data,应该使用 Encoder。

func Merge

func Merge(data ...Data) (d Data)

Merge 将多个 data 从左至右合并到 d 里面,如果有同名的 key 会进行深度遍历进行合并。 返回的 d 是一个全新的 Data,修改 d 的内容不会影响参数中任何的 data。

具体的合并规则是:

  • 对于 Data 类型的数值,将相同的 key 进行深度合并;
  • 如果出现同名 key 且 value 类型相同,都是 Data 或者 slice,深度合并 value 值;
  • 如果出现同名 key 且 value 类型不同,后面出现的 value 覆盖前面的 value。
  • 对于 slice 类型的数值,如果两个 slice 类型相同,后面出现的 slice 的值会被 append 进去。

func Parse

func Parse(str string) (d Data, err error)

Parse 从 str 中解析 Data,这个 str 应该是符合 Data 序列化格式的字符串。 如果 str 格式不合法,返回错误。

Data 序列化格式定义如下:

'<' type '>' raw

当前 type 仅支持 JSON,值为 `json`,对应的 raw 是 JSON 字符串。 例如:

<json>{"hello":"world!"}

func ParseJSON

func ParseJSON(str string) (d Data, err error)

ParseJSON 解析 JSON 字符串并且生成 Data,如果解析过程出现任何错误则返回错误。 由于 Data 是一个 map,所以 JSON 必须是一个 object,如果不是则返回错误。

func (Data) Clone

func (d Data) Clone() Data

Clone 复制一份 d 的内容。

func (Data) Get

func (d Data) Get(fields ...string) interface{}

Get 通过 fields 找到对应的值并且返回,如果找不到则返回 nil。

其中,field 是一个数组,例如 []string{"a", "b", "c"} 代表访问 d["a"]["b"]["c"]。 如果希望访问数组元素,可以直接写数组下标数字,比如 []string{"a", "0", "c"} 代表访问 d["a"][0]["c"]。

func (Data) JSON

func (d Data) JSON(pretty bool) string

JSON 返回 d 对应的 JSON 字符串。 如果 pretty 为 true,会为打印优化输出格式。

func (Data) Len

func (d Data) Len() int

Len 返回 d 的数据个数。

func (Data) MarshalJSON

func (d Data) MarshalJSON() ([]byte, error)

MarshalJSON 将 d 序列化成 JSON。

func (Data) PrettyString

func (d Data) PrettyString() string

PrettyString 输出用于打印输出的存储格式。

func (Data) Query

func (d Data) Query(query string) interface{}

Query 解析 query 找到对应的值并且返回,如果找不到则返回 nil。

其中,query 的格式是以“.”分隔的字段,例如 a.b.c 代表访问 d["a"]["b"]["c"]。 如果希望访问数组元素,可以直接写数组下标数字,比如 a.0.c 代表访问 d["a"][0]["c"]。

func (Data) String

func (d Data) String() string

String 返回 d 的可存储格式,这个格式可以用 Parse 解析并还原成 Data 结构。

func (*Data) UnmarshalJSON

func (d *Data) UnmarshalJSON(src []byte) error

UnmarshalJSON 解析 JSON 字符串并设置 d 的值。 这里不直接使用 `json.Unmarshal` 来反序列化的原因是,`Data` 内部要求统一所有的数据类型, 但 `json.Marshal` 无法满足这个要求。

type Decoder

type Decoder struct {
	TagName string // 在解析 struct 时候使用的 field tag,默认是 data。
}

Decoder 用来将 Data 设置到指定值里面去。

func (*Decoder) Decode

func (dec *Decoder) Decode(d Data, v interface{}) error

Decode 将 d 解析到 v 中。

func (*Decoder) DecodeField

func (dec *Decoder) DecodeField(d Data, field []string, v interface{}) error

DecodeField 通过 field 找到对应的值并且解析到 v 中。 其中,field 的格式详见 `Data#Get` 文档。

func (*Decoder) DecodeQuery

func (dec *Decoder) DecodeQuery(d Data, query string, v interface{}) error

DecodeQuery 解析 query 找到对应的值并且解析到 v 中。 其中,query 的格式详见 `Data#Qeury` 文档。

type Encoder

type Encoder struct {
	TagName   string // 在解析 struct 时候使用的 field tag,默认是 data。
	OmitEmpty bool   // 如果为 true,则默认所有字段都会忽略空值。
}

Encoder 用来将数据转化成 Data。

func (*Encoder) Encode

func (enc *Encoder) Encode(v interface{}) Data

Encode 将任意的 Go 类型转化成 Data。

需要注意,只有以下类型可以成功转化成 Data,如果 v 不是这些类型,Encode 会返回 nil。

  • Go struct 和 struct 指针;
  • 任意的 map[string]T 类型,T 可以是任意的类型。

type FieldTag

type FieldTag struct {
	Alias     string // 字段别名。
	Skipped   bool   // 字段别名为“-”时,跳过这个字段。
	OmitEmpty bool   // 忽略空值。
	Squash    bool   // 是否展开。
}

FieldTag 是一个解析完成的字段 tag。

格式是:

data:"alias,opt1,opt2,..."

当前支持以下选项:

  • omitempty:忽略空值
  • squash:将一个字段的内容展开到当前 struct

当 alias 为“-”时,当前字段会被跳过。

func ParseFieldTag

func ParseFieldTag(tag string) *FieldTag

ParseFieldTag 解析 field tag 的 alias 和选项。 详细的格式见 FieldTag 文档。

type Patch

type Patch struct {
	// contains filtered or unexported fields
}

Patch 代表一系列的对 Data 的修改操作。

Example
patch := NewPatch()

// 删除 d["v2"]、d["v3"][1]、d["v4"]["v4-4"]。
patch.Add([]string{"v2", "v3.1", "v4.v4-1"}, nil)

// 添加数据。
patch.Add(nil, map[string]Data{
	// 在根添加数据。
	"": Make(RawData{
		"v1": []int{2, 3},
		"v2": 456,
	}),

	// 在 d["v4"] 里添加数据。
	"v4": Make(RawData{
		"v4-1": "new",
	}),
})

// 同时删除并添加数据。
patch.Add([]string{"v4.v4-2"}, map[string]Data{
	"v4": Make(RawData{
		"v4-2": RawData{
			"new": true,
		},
	}),
})

d := Make(RawData{
	"v1": []int{1},
	"v2": 123,
	"v3": []string{"first", "second", "third"},
	"v4": RawData{
		"v4-1": "old",
		"v4-2": RawData{
			"old": true,
		},
	},
})
patch.ApplyTo(&d)
fmt.Println(d)
Output:

<json>{"v1":[1,2,3],"v2":456,"v3":["first","third"],"v4":{"v4-1":"new","v4-2":{"new":true}}}

func NewPatch

func NewPatch() *Patch

NewPatch 创建一个新 Patch 对象。

func (*Patch) Actions

func (patch *Patch) Actions() []*PatchAction

Actions 返回所有的 action。

func (*Patch) Add

func (patch *Patch) Add(deletes []string, updates map[string]Data)

Add 增加一个新的 patch 操作。 每个 patch 操作都由 deletes 和 updates 组成,实际 apply 的时候 会先删除所有 deletes 字段,然后再使用 updates 进行更新。 updates 的 key 是 Data 的 query,apply 时候会按照字典顺序从小到大排序之后合并更新到 Data。

需要注意,`Patch#Apply`/`Patch#ApplyTo` 使用 `Merge`/`MergeTo` 来更新 Data, merge 系列函数会深度遍历 map/slice,这导致新值无法简单覆盖老值。 如果希望新值覆盖老值,而不是合并,那么得先用 deletes 删除老值再合并。

func (*Patch) Apply

func (patch *Patch) Apply(d Data) (applied Data, err error)

Apply 将 d 复制一份出来,并将所有变更应用在 d 的副本上, d 本身不会受到任何影响。

Apply 在如下情况下报错:

  • updates 的某个 query 无法找到对应元素;
  • updates 的某个 query 查询出的结果并不是一个 RawData。

func (*Patch) ApplyTo

func (patch *Patch) ApplyTo(target *Data) error

ApplyTo 将变更直接应用于 target 上,将会修改 target 内部值。

ApplyTo 的出错条件与 Apply 相同。

type PatchAction

type PatchAction struct {
	Deletes []string        `data:"deletes"`
	Updates map[string]Data `data:"updates"`
}

PatchAction 代表一个 patch 操作。

func (*PatchAction) ApplyTo

func (action *PatchAction) ApplyTo(target *Data) error

ApplyTo 将一个 action 应用到 target。

type RawData

type RawData map[string]interface{}

RawData 是一种未经加工的 map[string]interface{}。

func (*RawData) Delete

func (d *RawData) Delete(queries ...string)

Delete 将 query 中查询到的值删除。 如果 query 为空字符串,则清空 d。

func (RawData) Get

func (d RawData) Get(fields ...string) interface{}

Get 通过 fields 找到对应的值并且返回,如果找不到则返回 nil。

其中,field 是一个数组,例如 []string{"a", "b", "c"} 代表访问 d["a"]["b"]["c"]。 如果希望访问数组元素,可以直接写数组下标数字,比如 []string{"a", "0", "c"} 代表访问 d["a"][0]["c"]。

func (RawData) Query

func (d RawData) Query(query string) interface{}

Query 解析 query 找到对应的值并且返回,如果找不到则返回 nil。

其中,query 的格式是以“.”分隔的字段,例如 a.b.c 代表访问 d["a"]["b"]["c"]。 如果希望访问数组元素,可以直接写数组下标数字,比如 a.0.c 代表访问 d["a"][0]["c"]。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL