Map(映射)
Map (映射)表示一种“键值对”(Key-Value Pair)的数据结构,键(Key)必须唯一,用于快速检索。
水平映射(Horizontal map)
[!IMPORTANT] 水平映射的列名中,map 元素名必须带有从
1开始的数字后缀。例如:
Item1ID、Item1Name、Item2ID、Item2Name(struct map,元素名:Item)。
水平 map 有以下几种:
- 水平 scalar map,map value 类型为 scalar。例如:
map<int32, int32>。 - 水平 struct map,map value 类型为 struct。例如:
map<int32, Item>。 - 水平 predefined-struct map,map value 类型为 predefined struct。例如:
map<int32, .Item>。
水平标量映射
无需单独支持,请使用 map<int32, Item> 代替。
水平结构体映射
HelloWorld.xlsx 中的 worksheet ItemConf:
| Item1ID | Item1Name | Item2ID | Item2Name | Item3ID | Item3Name |
|---|---|---|---|---|---|
| map<uint32, Item> | string | uint32 | string | uint32 | string |
| Item1’s ID | Item1’s name | Item2’s ID | Item2’s name | Item3’s ID | Item3’s name |
| 1 | Apple | 2 | Orange | 3 | Banana |
生成结果:
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:
| Item1ID | Item1Num | Item2ID | Item2Num | Item3ID | Item3Num |
|---|---|---|---|---|---|
| map<int32, .Item> | int32 | int32 | int32 | int32 | int32 |
| Item1’s ID | Item1’s num | Item2’s ID | Item3’s num | Item3’s ID | Item3’s num |
| 1 | 100 | 2 | 200 | 3 | 300 |
生成结果:
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)
垂直映射有以下几种:
- 垂直 scalar map,map value 类型为 scalar。例如:
map<int32, int32>。 - 垂直 struct map,map value 类型为 struct。例如:
map<int32, Item>。 - 垂直 predefined-struct map,map value 类型为 predefined struct。例如:
map<int32, .Item>。
垂直标量映射
无需单独支持,请使用 map<int32, Item> 代替。
垂直结构体映射
HelloWorld.xlsx 中的 worksheet ItemConf:
| ID | Name | Desc |
|---|---|---|
| map<uint32, Item> | string | string |
| Item’s ID | Item’s name | Item’s desc |
| 1 | Apple | A kind of delicious fruit. |
| 2 | Orange | A kind of sour fruit. |
| 3 | Banana | A 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:
| ID | Num |
|---|---|
| map<int32, .Item> | int32 |
| Item’s ID | Item’s num |
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
生成结果:
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)
单元格内映射有以下几种:
- Incell scalar map,map value 类型为 scalar。例如:
map<int32, int32>。 - 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 propertypresent设置为 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 中预定义的枚举类型 FruitType 和 FruitFlavor:
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:
| Fruit | Flavor | Item |
|---|---|---|
| map<enum<.FruitType>, int64> | map<int64, enum<.FruitFlavor» | map<enum<.FruitType>, enum<.FruitFlavor» |
| Fruits | Flavors | Items |
| Apple:1,Orange:2 | 1:Fragrant,2:Sweet | Apple: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:
| ID | Desc |
|---|---|
| map<uint32, Item> | string |
| Item’s ID | Item’s name |
| 1 | Apple |
| Orange | |
| 3 | Banana |
生成结果:
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:
| Type | Price |
|---|---|
| map<enum<.FruitType>, Item> | int32 |
| Item’s type | Item’s price |
| Apple | 100 |
| Orange | 200 |
| Banana | 300 |
生成结果:
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:
| Item1ID | Item1Name | Item2ID | Item2Name | Item3ID | Item3Name |
|---|---|---|---|---|---|
| map<uint32, Item>|{fixed:true} | string | uint32 | string | uint32 | string |
| Item1’s ID | Item1’s name | Item2’s ID | Item2’s name | Item3’s ID | Item3’s name |
| 1 | Apple | 3 | Banana |
生成结果:
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:
| Item1ID | Item1Name | Item2ID | Item2Name | Item3ID | Item3Name |
|---|---|---|---|---|---|
| map<uint32, Item>|{size:2} | string | uint32 | string | uint32 | string |
| Item1’s ID | Item1’s name | Item2’s ID | Item2’s name | Item3’s ID | Item3’s name |
| 1 | Apple | 3 | Banana |
生成结果:
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:
| D | Prop1ID | Prop1Value | Prop2ID | Prop2Value | ||
|---|---|---|---|---|---|---|
| map<uint32, Item> | map<int32, Prop> | int32 | int32 | int32 | ||
| Item’s ID | Prop1’s ID | Prop1’s name | Prop1’s value | Prop2’s ID | Prop2’s name | Prop2’s value |
| 1 | 1 | Apple | 100 | 2 | Orange | 200 |
| 2 | 3 | Banana | 300 | 4 | Pomelo | 400 |
| 3 | 5 | Watermelon | 500 |
生成结果:
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
}
}
}
}
}