基本介绍

Toml是一种易读、mini语言,由github前CEO Tom创建。Tom's Obvious, Minimal Language

TOML致力于配置文件的小型化和易读性。 

  1. WIKI介绍: https://github.com/toml-lang/toml/wiki
  2. 官方地址: https://github.com/toml-lang/toml
  3. 汉化版:https://github.com/LongTengDao/TOML/blob/%E9%BE%99%E8%85%BE%E9%81%93-%E8%AF%91/toml-v1.0.0.md

与其他格式比较

TOML与用于应用程序配置和数据序列化的其他文件格式(如YAMLJSON)具有相同的特性。TOMLJSON都很简单,并且使用普遍存在的数据类型,这使得它们易于编写代码或使用机器进行解析。TOMLYAML都强调人的可读性,比如注释,它使理解给定行的目的变得更容易。TOML的不同之处在于,它支持注释(不像JSON),但保持了简单性(不像YAML)。

由于TOML被显式地设计为一种配置文件格式,所以解析它很容易,但并不打算序列化任意的数据结构。TOML的文件顶层是一个哈希表,它很容易在键中嵌套数据,但是它不允许顶级数组或浮点数,所以它不能直接序列化一些数据。也没有标准来标识TOML文件的开始或结束,这会使通过流发送文件变得复杂。这些细节必须在应用层进行协商。

INI文件经常与TOML进行比较,因为它们在语法和用作配置文件方面具有相似性。然而,INI没有标准化的格式,它们不能优雅地处理超过一两个层次的嵌套。

基础语法

title = "TOML 例子"

[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # 日期时间是一等公民。为什么不呢?

[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true

[servers]
  # 你可以依照你的意愿缩进。使用空格或Tab。TOML不会在意。
  [servers.alpha]
  ip = "10.0.0.1"
  dc = "eqdc10"

  [servers.beta]
  ip = "10.0.0.2"
  dc = "eqdc10"

[clients]
data = [ ["gamma", "delta"], [1, 2] ]

#在数组里换行没有关系。
hosts = [
  "alpha",
  "omega"
]

特点: 

  • 大小写敏感,必须是UTF-8编码
  • 注释:#
  • 空白符:tab(0x09)space(0x20)
  • 换行符:LF(0x0A)CRLF(0x0D 0x0A)
  • 键值对:同一行,无值的键不可用,每行只能保存一个键值对

TOML主要结构是键值对,与JSON类似。值必须是如下类型: String, Integer, Float, Boolean, Datetime, Array, Table

注释

使用 # 表示注释:

# I am a comment. Hear me roar. Roar.
key = "value" # Yeah, you can do this.

字符串

TOML中有4种字符串表示方法:基本、多行-基本、字面量、多行-字面量

1. 基本字符串

由双引号包裹,所有Unicode字符均可出现,除了双引号、反斜线、控制字符(U+0000 to U+001F)需要转义。

2. 多行-基本字符串

由三个双引号包裹,除了分隔符开始的换行外,字符串内的换行将被保留:

str1 = """
Roses are red
Violets are blue"""

3. 字面量字符串

由单引号包裹,其内不允许转义,因此可以方便的表示基本字符串中需要转义的内容:

winpath = 'C:\Users\nodejs\templates'

4. 多行-字面量字符串

与多行-基本字符串相似:

str1 = '''
Roses are red
Violets are blue'''

数值与BOOL值

int1  = +99
flt3  = -0.01
bool1 = true

日期时间

date = 1979-05-27T07:32:00Z

数组

数组使用方括号包裹。空格会被忽略。元素使用逗号分隔。

注意,同一个数组下不允许混用数据类型。

array1 = [ 1, 2, 3 ]
array2 = [ "red", "yellow", "green" ]
array3 = [ [ 1, 2 ], [3, 4, 5] ]
array4 = [ [ 1, 2 ], ["a", "b", "c"] ] # 这是可以的。
array5 = [ 1, 2.0 ] # 注意:这是不行的。

表格

表格(也叫哈希表或字典)是键值对的集合。它们在方括号内,自成一行。注意和数组相区分,数组只有值。

[table]

在此之下,直到下一个 table 或 EOF 之前,是这个表格的键值对。键在左,值在右,等号在中间。键以非空字符开始,以等号前的非空字符为结尾。键值对是无序的。

[table]
key = "value"

你可以随意缩进,使用 Tab空格。为什么要缩进呢?因为你可以嵌套表格。

嵌套表格的表格名称中使用.符号。你可以任意命名你的表格,只是不要用点,点是保留的。

[dog.tater]
type = "pug"

以上等价于如下的 JSON 结构:

{ "dog": { "tater": { "type": "pug" } } }

如果你不想的话,你不用声明所有的父表。TOML 知道该如何处理。

# [x] 你
# [x.y] 不需要
# [x.y.z] 这些
[x.y.z.w] # 可以直接写

空表是允许的,其中没有键值对。

只要父表没有被直接定义,而且没有定义一个特定的键,你可以继续写入:

[a.b]
c = 1

[a]
d = 2

然而你不能多次定义键和表格。这么做是不合法的。

# 别这么干!

[a]
b = 1

[a]
c = 2
# 也别这个干

[a]
b = 1

[a.b]
c = 2

表格数组

最后要介绍的类型是表格数组。表格数组可以通过包裹在双方括号内的表格名来表达。使用相同的双方括号名称的表格是同一个数组的元素。表格按照书写的顺序插入。双方括号表格如果没有键值对,会被当成空表。

[[products]]
name = "Hammer"
sku = 738594937

[[products]]

[[products]]
name = "Nail"
sku = 284758393
color = "gray"

等价于以下的 JSON 结构:

{
  "products": [
    { "name": "Hammer", "sku": 738594937 },
    { },
    { "name": "Nail", "sku": 284758393, "color": "gray" }
  ]
}

表格数组同样可以嵌套。只需在子表格上使用相同的双方括号语法。每一个双方括号子表格从属于最近定义的上层表格元素。

[[fruit]]
  name = "apple"

  [fruit.physical]
    color = "red"
    shape = "round"

  [[fruit.variety]]
    name = "red delicious"

  [[fruit.variety]]
    name = "granny smith"

[[fruit]]
  name = "banana"

  [[fruit.variety]]
    name = "plantain"

等价于如下的 JSON 结构:

{
  "fruit": [
    {
      "name": "apple",
      "physical": {
        "color": "red",
        "shape": "round"
      },
      "variety": [
        { "name": "red delicious" },
        { "name": "granny smith" }
      ]
    },
    {
      "name": "banana",
      "variety": [
        { "name": "plantain" }
      ]
    }
  ]
}

尝试定义一个普通的表格,使用已经定义的数组的名称,将抛出一个解析错误:

# 不合法的 TOML

[[fruit]]
  name = "apple"

  [[fruit.variety]]
    name = "red delicious"

  # 和上面冲突了
  [fruit.variety]
    name = "granny smith"
Content Menu

  • No labels

14 Comments

  1. 为什么不用yaml做默认格式

    1. 默认支持自动识别文件格式。

    2. 我一直忍受不支持注释的json格式,而不用yaml。仅仅因为它的空格和缩进很容易出错。

      现在发现了toml,如获至宝。感谢作者。文档写得太好了。

      1. toml/yaml/json/ini都支持,默认自动检索识别文件格式。

        1. 突然想到个问题,比如个人偏好不同。有些人只用ini, toml 或 json 其中的一种,但框架默认支持全部文件格式。会依赖所有文件格式的解析组件。这样会不会不大好?框架的其他地方也是这样的吗?

  2. 表格数组,刚开始用的时候,不知道怎么用,经过测试后找到用法,供参考,如果有更好的方法,希望能告知。

    config.toml里面的表格数组如下

    [[port.forward]]
    name = "goxunrao"
    addressPort = "192.168.9.172:8194"

    [[port.forward]]
    name = "godashi"
    addressPort = "192.168.9.172:8198"

    [[port.forward]]
    name = "gorouting"
    addressPort = "192.168.9.172:8195"

    [[port.forward]]
    name = "gomodbus"
    addressPort = "192.168.9.172:8196"


    在程序中读取的方法如下:
    type portForware struct {
    Name string `json:"name"`
    AddressPort string `json:"addressPort"`
    }

    func serverOnceJob() error {
    var portForwares []*portForware

    portForware := g.Cfg().GetInterfaces("port.forward")
    err := gconv.Structs(portForware, &portForwares)
    if err != nil {
    return err
    } else {
    for _,v := range portForwares{
    fmt.Println(fmt.Sprintf ("name:%s, port:%s",v.Name,v.AddressPort))
    }
    }
    return err
    }






  3. 配置文件在config目录下,需要怎么加载进来?v2版本的。

    1. manifest文件夹下面的config文件夹

  4. Hi , 郭强 

      我试了一下采用 V2  版本中针对toml 类型 config文件. 如果在设定中使用

    多行-字面量字符串 

    比如: 

    [tables] 

    val = ''' 

     {

        "name" :"abc" 

       xxxx

    }

    '''

    类似上面的设定.value为json 格式. 之前 v 1.16.x 的版本是可以用getjson 读取. 现在调整v2 之后用g.cfg().get (ctx,"tables.val") 返回error 

    提示类似以下信息

    load config file "/Users/wilson/GolandProjects/demo3/manifest/config/config.toml" failed: unsupported type "" for loading
    1. load config file "/Users/wilson/GolandProjects/demo3/manifest/config/config.toml" failed
       1).  github.com/gogf/gf/v2/os/gcfg.(*AdapterFile).getJson.func1
            /Users/wilson/go/pkg/mod/github.com/gogf/gf/v2@v2.0.0/os/gcfg/gcfg_adapter_file.go:258
       2).  github.com/gogf/gf/v2/container/gmap.(*StrAnyMap).doSetWithLockCheck
            /Users/wilson/go/pkg/mod/github.com/gogf/gf/v2@v2.0.0/container/gmap/gmap_hash_str_any_map.go:215
       3).  github.com/gogf/gf/v2/container/gmap.(*StrAnyMap).GetOrSetFuncLock
            /Users/wilson/go/pkg/mod/github.com/gogf/gf/v2@v2.0.0/container/gmap/gmap_hash_str_any_map.go:252
       4).  github.com/gogf/gf/v2/os/gcfg.(*AdapterFile).getJson
            /Users/wilson/go/pkg/mod/github.com/gogf/gf/v2@v2.0.0/os/gcfg/gcfg_adapter_file.go:227
       5).  github.com/gogf/gf/v2/os/gcfg.(*AdapterFile).Get
            /Users/wilson/go/pkg/mod/github.com/gogf/gf/v2@v2.0.0/os/gcfg/gcfg_adapter_file.go:139
       6).  github.com/gogf/gf/v2/os/gcfg.(*Config).Get

    1. 补充: 如果修改成 双引号是可以

      [tables]

      val ="""

       {

        "name":"abc" 

      }

      """

  5. 可以理解为,和tp6、laravel中的 .env 的内容格式一样

  6. 有没有可能支持可注释的json配置文件,多级配置在json里面看起来更加直观,但是不支持注释又是json最大的问题
    我在网上看过json5的支持,https://github.com/yosuke-furukawa/json5,我现在的做法是在gf现有的配置上套了一层,去读取 .json5 后缀的文件,然后用json5提供的方法读取文件

    例子:
    ggm := make(map[string]interface{})
    _ = json5.Unmarshal([]byte(`{
    key : "json5 key ", // 哈哈
    // json specific
    "of" : "course we can use json as json5",
    trailing : "trailing comma is ok",
    }`), &ggm)
    g.Dump(ggm)
    打印结果:
    {
        "key":      "json5 key ",
        "of":       "course we can use json as json5",
        "trailing": "trailing comma is ok",
    }
    再通过gf配置文件的setContent这种方式去设置配置。


  7. 尝试用 g.Cfg().Get() 读取嵌套配置项时,会找不到配置项,经过调试发现需要加上 .0 才能找到值。请大佬帮忙看看解答下。

    读取配置 Demo 代码如下:

    config.toml:

    [challenge]
    attachmentSaveDir = "./attachments"
    [[challenge.Container]]
    portMinimum = 30000

    main.go:

    func main() {
    ctx := gctx.New()

    get, err := g.Cfg().Get(ctx, "challenge.attachmentSaveDir")
    if err != nil {
    return
    }
    fmt.Println(get.String())
    //"./attachments"

    get, err = g.Cfg().Get(ctx, "challenge.Container.portMinimum")
    if err != nil {
    return
    }
    fmt.Println(get.String())
    //(nil)

    get, err = g.Cfg().Get(ctx, "challenge.Container.0.portMinimum")
    if err != nil {
    return
    }
    fmt.Println(get.String())
    //30000
    }


    在 gjson checkPatternByPointer() 里面有两个分支。当 pattern="challenge.Container.portMinimum" 、循环到 key=“portMinimum”时,(*pointer).(type)=[]interface{} 走下面的分支进行数组取值而导致找不到 portMinimum 。

    func (j *Json) checkPatternByPointer(key string, pointer *interface{}) *interface{} {
    switch (*pointer).(type) {
    case map[string]interface{}:
    if v, ok := (*pointer).(map[string]interface{})[key]; ok {
    return &v
    }
    case []interface{}:
    if gstr.IsNumeric(key) {
    n, err := strconv.Atoi(key)
    if err == nil && len((*pointer).([]interface{})) > n {
    return &(*pointer).([]interface{})[n]
    }
    }
    }
    return nil
    }

    配置大概变成:

    {
    "challenge": {
    "attachmentSaveDir": "./attachments",
    "Container": [
    {
    "portMinimum": 3000
    }
    ]
    }
    }