Map(映射)

本文说明 Excel map 类型的各种特性。

横向 map(Horizontal map)

横向 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>

横向 scalar map

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

横向 struct map

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"}
    }
}

横向 predefined-struct map

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}
    }
}

纵向 map(Vertical map)

纵向 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>

纵向 scalar map

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

纵向 struct map

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."}
    }
}

纵向 predefined-struct map

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

Incell map 有以下几种:

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

Incell scalar map

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

⚠️ 注意:如果希望使用显式模式 [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 enum map

对于 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"}
    }
}

空 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 条目应连续存在,否则如果中间存在空条目会报错。

固定大小

隐式固定大小(Implicit fixed size)

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

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

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 文件中看到。

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"}
    }
}

高级特性

横向跳列 map(Horizontal column-skipped map)

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}
            }
        }
    }
}

Ordered-map

在 metasheet @TABLEAU 中将 OrderedMap 选项设置为 true,则会生成 ordered map 访问器。此功能由 tableauio/loader 提供支持。目前支持的编程语言:

  • C++
  • Go
  • C#
  • JS/TS

示例

如果希望 ItemConf 生成 ordered map 访问器,则在 metasheet @TABLEAU 中将 OrderedMap 选项设置为 true

IDName
map<uint32, Item>string
Item’s IDItem’s Name
1Apple
2Orange
3Banana
SheetOrderedMap
ItemConftrue

更多有用的选项请参考 metasheet 章节。Metasheet @TABLEAU →