Map(映射)

Map (映射)表示一种“键值对”(Key-Value Pair)的数据结构,键(Key)必须唯一,用于快速检索。

水平映射(Horizontal map)

[!IMPORTANT] 水平映射的列名中,map 元素名必须带有从 1 开始的数字后缀。

例如:Item1IDItem1NameItem2IDItem2Name(struct map,元素名:Item)。

水平 map 有以下几种:

  1. 水平 scalar map,map value 类型为 scalar。例如:map<int32, int32>
  2. 水平 struct map,map value 类型为 struct。例如:map<int32, Item>
  3. 水平 predefined-struct map,map value 类型为 predefined struct。例如:map<int32, .Item>

水平标量映射

无需单独支持,请使用 map<int32, Item> 代替。

水平结构体映射

HelloWorld.xlsx 中的 worksheet ItemConf

Item1IDItem1NameItem2IDItem2NameItem3IDItem3Name
map<uint32, Item>stringuint32stringuint32string
Item1’s IDItem1’s nameItem2’s IDItem2’s nameItem3’s IDItem3’s name
1Apple2Orange3Banana

生成结果:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<uint32, Item> item_map = 1 [(tableau.field) = {name:"Item" key:"ID" layout:LAYOUT_HORIZONTAL}];
  message Item {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    string name = 2 [(tableau.field) = {name:"Name"}];
  }
}
ItemConf.json
{
    "itemMap": {
        "1": {
            "id": 1,
            "name": "Apple"
        },
        "2": {
            "id": 2,
            "name": "Orange"
        },
        "3": {
            "id": 3,
            "name": "Banana"
        }
    }
}

水平预定义结构体映射

common.proto 中预定义的 Item

message Item {
    int32 id = 1 [(tableau.field) = {name:"ID"}];
    int32 num = 2 [(tableau.field) = {name:"Num"}];
}

HelloWorld.xlsx 中的 worksheet ItemConf

Item1IDItem1NumItem2IDItem2NumItem3IDItem3Num
map<int32, .Item>int32int32int32int32int32
Item1’s IDItem1’s numItem2’s IDItem3’s numItem3’s IDItem3’s num
110022003300

生成结果:

hello_world.proto
// --snip--
import "common.proto";
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<int32, protoconf.Item> item_map = 1 [(tableau.field) = {name:"Item" key:"ID" layout:LAYOUT_HORIZONTAL}];
}
ItemConf.json
{
    "itemMap": {
        "1": {
            "id": 1,
            "num": 100
        },
        "2": {
            "id": 2,
            "num": 200
        },
        "3": {
            "id": 3,
            "num": 300
        }
    }
}

垂直映射(Vertical map)

垂直映射有以下几种:

  1. 垂直 scalar map,map value 类型为 scalar。例如:map<int32, int32>
  2. 垂直 struct map,map value 类型为 struct。例如:map<int32, Item>
  3. 垂直 predefined-struct map,map value 类型为 predefined struct。例如:map<int32, .Item>

垂直标量映射

无需单独支持,请使用 map<int32, Item> 代替。

垂直结构体映射

HelloWorld.xlsx 中的 worksheet ItemConf

IDNameDesc
map<uint32, Item>stringstring
Item’s IDItem’s nameItem’s desc
1AppleA kind of delicious fruit.
2OrangeA kind of sour fruit.
3BananaA kind of calorie-rich fruit.

生成结果:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<uint32, Item> item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}];
  message Item {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    string name = 2 [(tableau.field) = {name:"Name"}];
    string desc = 3 [(tableau.field) = {name:"Desc"}];
  }
}
ItemConf.json
{
    "itemMap": {
        "1": {
            "id": 1,
            "name": "Apple",
            "desc": "A kind of delicious fruit."
        },
        "2": {
            "id": 2,
            "name": "Orange",
            "desc": "A kind of sour fruit."
        },
        "3": {
            "id": 3,
            "name": "Banana",
            "desc": "A kind of calorie-rich fruit."
        }
    }
}

垂直预定义结构体映射

common.proto 中预定义的 Item

message Item {
    int32 id = 1 [(tableau.field) = {name:"ID"}];
    int32 num = 2 [(tableau.field) = {name:"Num"}];
}

HelloWorld.xlsx 中的 worksheet ItemConf

IDNum
map<int32, .Item>int32
Item’s IDItem’s num
1100
2200
3300

生成结果:

hello_world.proto
// --snip--
import "common.proto";
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<int32, protoconf.Item> item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}];
}
ItemConf.json
{
    "itemMap": {
        "1": {
            "id": 1,
            "num": 100
        },
        "2": {
            "id": 2,
            "num": 200
        },
        "3": {
            "id": 3,
            "num": 300
        }
    }
}

单元格内映射(Incell map)

单元格内映射有以下几种:

  1. Incell scalar map,map value 类型为 scalar。例如:map<int32, int32>
  2. Incell struct map,map value 类型为 struct。例如:map<int32, Item>

单元格内标量映射

HelloWorld.xlsx 中的 worksheet ItemConf

Item
map<uint32, string>
Item key-value pairs
1:Apple,2:Orange,3:Banana,4,:Peach

Item 列的类型为 incell map map<uint32, string>,map value 为 scalar 类型 string

[!IMPORTANT] 如果希望使用显式模式 [Key:Value]...,请将 field property present 设置为 true。 参见 选项 present

生成结果:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<uint32, string> item_map = 1 [(tableau.field) = {name:"Item" layout:LAYOUT_INCELL}];
}
ItemConf.json
{
    "itemMap": {
        "0": "Peach",
        "1": "Apple",
        "2": "Orange",
        "3": "Banana",
        "4": ""
    }
}

单元格内枚举映射

对于 incell map,key 和 value 都可以是枚举类型。

例如,common.proto 中预定义的枚举类型 FruitTypeFruitFlavor

enum FruitType {
  FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"];
  FRUIT_TYPE_APPLE   = 1 [(tableau.evalue).name = "Apple"];
  FRUIT_TYPE_ORANGE  = 2 [(tableau.evalue).name = "Orange"];
  FRUIT_TYPE_BANANA  = 3 [(tableau.evalue).name = "Banana"];
}

enum FruitFlavor {
  FRUIT_FLAVOR_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"];
  FRUIT_FLAVOR_FRAGRANT = 1 [(tableau.evalue).name = "Fragrant"];
  FRUIT_FLAVOR_SOUR = 2 [(tableau.evalue).name = "Sour"];
  FRUIT_FLAVOR_SWEET = 3 [(tableau.evalue).name = "Sweet"];
}

HelloWorld.xlsx 中的 worksheet ItemConf

FruitFlavorItem
map<enum<.FruitType>, int64>map<int64, enum<.FruitFlavor»map<enum<.FruitType>, enum<.FruitFlavor»
FruitsFlavorsItems
Apple:1,Orange:21:Fragrant,2:SweetApple:Fragrant,Orange:Sour

生成结果:

hello_world.proto
// --snip--
import "common.proto";
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<int32, Fruit> fruit_map = 1 [(tableau.field) = {name:"Fruit" key:"Key" layout:LAYOUT_INCELL}];
  message Fruit {
    protoconf.FruitType key = 1 [(tableau.field) = {name:"Key"}];
    int64 value = 2 [(tableau.field) = {name:"Value"}];
  }
  map<int64, protoconf.FruitFlavor> flavor_map = 2 [(tableau.field) = {name:"Flavor" layout:LAYOUT_INCELL}];
  map<int32, Item> item_map = 3 [(tableau.field) = {name:"Item" key:"Key" layout:LAYOUT_INCELL}];
  message Item {
    protoconf.FruitType key = 1 [(tableau.field) = {name:"Key"}];
    protoconf.FruitFlavor value = 2 [(tableau.field) = {name:"Value"}];
  }
}
ItemConf.json
{
    "fruitMap": {
        "1": {
            "key": "FRUIT_TYPE_APPLE",
            "value": "1"
        },
        "3": {
            "key": "FRUIT_TYPE_ORANGE",
            "value": "2"
        }
    },
    "flavorMap": {
        "1": "FRUIT_FLAVOR_FRAGRANT",
        "2": "FRUIT_FLAVOR_SWEET"
    },
    "itemMap": {
        "1": {
            "key": "FRUIT_TYPE_APPLE",
            "value": "FRUIT_FLAVOR_FRAGRANT"
        },
        "3": {
            "key": "FRUIT_TYPE_ORANGE",
            "value": "FRUIT_FLAVOR_SOUR"
        }
    }
}

空键映射(Empty key map)

如果 map key 未配置,则视为 map key 类型的默认值。默认值参见 Scalar types

HelloWorld.xlsx 中的 worksheet ItemConf

IDDesc
map<uint32, Item>string
Item’s IDItem’s name
1Apple
Orange
3Banana

生成结果:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<uint32, Item> item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}];
  message Item {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    string desc = 2 [(tableau.field) = {name:"Desc"}];
  }
}
ItemConf.json
{
    "itemMap": {
        "0": {
            "id": 0,
            "desc": "Orange"
        },
        "1": {
            "id": 1,
            "desc": "Apple"
        },
        "3": {
            "id": 3,
            "desc": "Banana"
        }
    }
}

枚举键映射(Enum key map)

根据 protobuf 文档对 map key type 的限制:

key_type 可以是任意整数或字符串类型(即除浮点类型和 bytes 之外的任意 scalar 类型)。注意,enum 不是有效的 key_type

然而,在某些场景下,以枚举作为 key 类型非常有用。因此我们以一种简单的方式支持它:

  • 枚举类型作为 map key 类型时,视为 int32
  • 枚举类型保留在 map value 类型(struct)中。

例如,common.proto 中预定义的枚举类型 FruitType

enum FruitType {
  FRUIT_TYPE_UNKNOWN = 0 [(tableau.evalue).name = "Unknown"];
  FRUIT_TYPE_APPLE   = 1 [(tableau.evalue).name = "Apple"];
  FRUIT_TYPE_ORANGE  = 3 [(tableau.evalue).name = "Orange"];
  FRUIT_TYPE_BANANA  = 4 [(tableau.evalue).name = "Banana"];
}

map<enum<.FruitType>, ValueType> 会被转换为 map<int32, ValueType>, 且 FruitType 保留在 ValueType 中:

message ValueType {
  FruitType key = 1;
  ...
}

HelloWorld.xlsx 中的 worksheet ItemConf

TypePrice
map<enum<.FruitType>, Item>int32
Item’s typeItem’s price
Apple100
Orange200
Banana300

生成结果:

hello_world.proto
// --snip--
import "common.proto";
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<int32, Item> item_map = 1 [(tableau.field) = {key:"Type" layout:LAYOUT_VERTICAL}];
  message Item {
    protoconf.FruitType type = 1 [(tableau.field) = {name:"Type"}];
    int32 price = 2 [(tableau.field) = {name:"Price"}];
  }
}
ItemConf.json
{
    "itemMap": {
        "1": {
            "type": "FRUIT_TYPE_APPLE",
            "price": 100
        },
        "3": {
            "type": "FRUIT_TYPE_ORANGE",
            "price": 200
        },
        "4": {
            "type": "FRUIT_TYPE_BANANA",
            "price": 300
        }
    }
}

水平映射大小

动态大小

默认情况下,所有 map 都是动态大小类型。Map 条目应连续存在,否则如果中间存在空条目会报错。

固定大小

隐式固定大小

Map 大小由名称行中最大存在的 map 条目数量自动确定。

在下面的示例中,虽然第二个 map 条目 Item2 为空,但由于 field property fixed 设置为 true,这是合法的。此外,Item2 也会作为空 map 条目生成,可以在生成的 ItemConf.json 文件中看到。

[!CAUTION] 若插入多个空 map 条目,实际上只会生成一个——因为所有空 map 条目的 key 相同。这与 list 的行为不同,请特别注意。

HelloWorld.xlsx 中的 worksheet ItemConf

Item1IDItem1NameItem2IDItem2NameItem3IDItem3Name
map<uint32, Item>|{fixed:true}stringuint32stringuint32string
Item1’s IDItem1’s nameItem2’s IDItem2’s nameItem3’s IDItem3’s name
1Apple3Banana

生成结果:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<uint32, Item> item_map = 1 [(tableau.field) = {name:"Item" key:"ID" layout:LAYOUT_HORIZONTAL prop:{fixed:true}}];
  message Item {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    string name = 2 [(tableau.field) = {name:"Name"}];
  }
}
ItemConf.json
{
    "itemMap": {
        "0": {
            "id": 0,
            "name": ""
        },
        "1": {
            "id": 1,
            "name": "Apple"
        },
        "3": {
            "id": 3,
            "name": "Banana"
        }
    }
}

显式固定大小(Explicit fixed size)

Map 大小由 field property size 显式设置。

在下面的示例中,field property size 设置为 2,则第二个 map 条目 Item2 之后的所有 map 条目都会被截断。此外,Item2 也会作为空 map 条目生成,可以在生成的 ItemConf.json 文件中看到。

[!CAUTION] 若插入多个空 map 条目,实际上只会生成一个——因为所有空 map 条目的 key 相同。这与 list 的行为不同,请特别注意。

HelloWorld.xlsx 中的 worksheet ItemConf

Item1IDItem1NameItem2IDItem2NameItem3IDItem3Name
map<uint32, Item>|{size:2}stringuint32stringuint32string
Item1’s IDItem1’s nameItem2’s IDItem2’s nameItem3’s IDItem3’s name
1Apple3Banana

生成结果:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<uint32, Item> item_map = 1 [(tableau.field) = {name:"Item" key:"ID" layout:LAYOUT_HORIZONTAL prop:{size:2}}];
  message Item {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    string name = 2 [(tableau.field) = {name:"Name"}];
  }
}
ItemConf.json
{
    "itemMap": {
        "0": {
            "id": 0,
            "name": ""
        },
        "1": {
            "id": 1,
            "name": "Apple"
        }
    }
}

高级特性

水平跳列映射

HelloWorld.xlsx 中的 worksheet ItemConf

DProp1IDProp1ValueProp2IDProp2Value
map<uint32, Item>map<int32, Prop>int32int32int32
Item’s IDProp1’s IDProp1’s nameProp1’s valueProp2’s IDProp2’s nameProp2’s value
11Apple1002Orange200
23Banana3004Pomelo400
35Watermelon500

生成结果:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx" namerow:1 typerow:2 noterow:3 datarow:4};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf"};

  map<uint32, Item> item_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}];
  message Item {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    map<int32, Prop> prop_map = 2 [(tableau.field) = {name:"Prop" key:"ID" layout:LAYOUT_HORIZONTAL}];
    message Prop {
      int32 id = 1 [(tableau.field) = {name:"ID"}];
      int32 value = 2 [(tableau.field) = {name:"Value"}];
    }
  }
}
ItemConf.json
{
    "itemMap": {
        "1": {
            "id": 1,
            "propMap": {
                "1": {
                    "id": 1,
                    "value": 100
                },
                "2": {
                    "id": 2,
                    "value": 200
                }
            }
        },
        "2": {
            "id": 2,
            "propMap": {
                "3": {
                    "id": 3,
                    "value": 300
                },
                "4": {
                    "id": 4,
                    "value": 400
                }
            }
        },
        "3": {
            "id": 3,
            "propMap": {
                "5": {
                    "id": 5,
                    "value": 500
                }
            }
        }
    }
}