Map

This guide demonstrates different features of excel map type.

Horizontal map

There are some kinds of horizontal map:

  1. Horizontal scalar map, as map value type is scalar. E.g: map<int32, int32>.
  2. Horizontal struct map, as map value type is struct. E.g: map<int32, Item>.
  3. Horizontal predefined-struct map, as map value type is predefined struct. E.g: map<int32, .Item>.

Horizontal scalar map

No need to support, use this instead: map<int32, Item>.

Horizontal struct map

A worksheet ItemConf in HelloWorld.xlsx:

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

Generated:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

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

Horizontal predefined-struct map

Item in common.proto is predefined as:

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

A worksheet ItemConf in HelloWorld.xlsx:

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

Generated:

hello_world.proto
// --snip--
import "common.proto";
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

  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

There are some kinds of vertical map:

  1. Vertical scalar map, as map value type is scalar. E.g: map<int32, int32>.
  2. Vertical struct map, as map value type is struct. E.g: map<int32, Item>.
  3. Vertical predefined-struct map, as map value type is predefined struct. E.g: map<int32, .Item>.

Vertical scalar map

No need to support, use map<int32, Item> instead.

Vertical struct map

A worksheet ItemConf in HelloWorld.xlsx:

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.

Generated:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

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

Vertical predefined-struct map

Item in common.proto is predefined as:

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

A worksheet ItemConf in HelloWorld.xlsx:

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

Generated:

hello_world.proto
// --snip--
import "common.proto";
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

  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

There are some kinds of in-cell map:

  1. in-cell scalar map, as map value type is scalar. E.g: map<int32, int32>.
  2. in-cell struct map, as map value type is struct. E.g: map<int32, Item>.

Incell scalar map

A worksheet ItemConf in HelloWorld.xlsx:

Items
map<uint32, string>
Items
1:Apple,2:Orange,3:Banana,4,:Peach

The Items column’s type is in-cell map map<uint32, string>, as the map value is scalar type string.

⚠️ NOTE: If you want explicit pattern like: [Key:Value]..., then set the field property present as true. See Option present.

Generated:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

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

Incell enum map

For incell map, both the key and value can be enum types.

For example, predefined enum types FruitType and FruitFlavor in common.proto are:

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

A worksheet ItemConf in HelloWorld.xlsx:

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

Generated:

hello_world.proto
// --snip--
import "common.proto";
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

  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

If map key is not configured, then it will be treated as default value of map key type. Default value is illustrated at Scalar types →.

A worksheet ItemConf in HelloWorld.xlsx:

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

Generated:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

  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

As the protobuf documents the restrictions of map key type:

… the key_type can be any integral or string type (so, any scalar type except for floating point types and bytes). Note that enum is not a valid key_type.

However, key type as enum is very useful in some situations. So we support it in a simple way:

  • enum type is treated as int32 as map key type,
  • enum type is reserved in map value type (struct).

For example, FruitType in common.proto is predefined as:

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

then map<enum<.FruitType>, ValueType> will be converted to map<int32, ValueType>, and FruitType is reserved in ValueType:

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

A worksheet ItemConf in HelloWorld.xlsx:

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

Generated:

hello_world.proto
// --snip--
import "common.proto";
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

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

Horizontal map size

Dynamic size

By default, all maps are Dynamically Sized Types. Map items should be present continuously, otherwise an error is reported if an empty item is existed in between.

Fixed size

Implicit fixed size

The map size is auto resolved by the max map items present in name row.

In this example below, though the second map item Item2 is empty, it is legal as the field property fixed is set true. Besides, Item2 will also be generated as an empty map item. You can see it in the generated file ItemConf.json.

A worksheet ItemConf in HelloWorld.xlsx:

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

Generated:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

  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

The map size is explicitly set by field property size.

In this example below, field property size is set as 2, then map items after the second item Item2 will all be truncated. Besides, Item2 will also be generated as an empty map item. You can see it in the generated file ItemConf.json.

A worksheet ItemConf in HelloWorld.xlsx:

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

Generated:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

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

Advanced features

Horizontal column-skipped map

A worksheet ItemConf in HelloWorld.xlsx:

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

Generated:

hello_world.proto
// --snip--
option (tableau.workbook) = {name:"HelloWorld.xlsx"};

message ItemConf {
  option (tableau.worksheet) = {name:"ItemConf" namerow:1 typerow:2 noterow:3 datarow:4};

  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"}];
    }
  }
}
HeroConf.json
{
    "itemMap": {
        "1": {
            "id": 1,
            "desc": "item1",
            "propertyMap": {
                "1": {
                    "id": 1,
                    "value": "10"
                },
                "2": {
                    "id": 2,
                    "value": "20"
                }
            }
        },
        "2": {
            "id": 2,
            "desc": "item2",
            "propertyMap": {
                "3": {
                    "id": 3,
                    "value": "30"
                },
                "4": {
                    "id": 4,
                    "value": "40"
                }
            }
        },
        "3": {
            "id": 3,
            "desc": "item3",
            "propertyMap": {
                "5": {
                    "id": 5,
                    "value": "50"
                }
            }
        }
    }
}

Ordered-map

In the metasheet @TABLEAU, set the OrderedMap option to true, then ordered map accessers will be generated. This feature is powered by tableauio/loader. Currently supported programming languages are:

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

Example

If we want ItemConf to generate ordered map accessers, then set OrderedMap option to true of metasheet @TABLEAU:

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

More useful options are illustrated at metasheet chapter. Metasheet @TABLEAU →