Union

This guide demonstrates different features of excel union type.

Theory

In protoconf, union type means the tagged union: a data structure used to hold a value that could take on several different, but fixed, types. Only one of the types can be in use at any one time, and a tag field explicitly indicates which one is in use. More details can be learned from wikipedia Tagged union.

Tagged union in different programming languages:

Tableau use protobuf message to bundle enum type and oneof type together to implement tagged union. By default, each enum value (>0) is bound to a field with the same tag number of oneof type.

Union definition

For example, union type Target in common.proto is predefined as:

// Predefined union type.
message Target {
  option (tableau.union) = true;

  Type type = 9999 [(tableau.field) = { name: "Type" }];
  oneof value {
    option (tableau.oneof) = {
      field: "Field"
    };
    Pvp pvp = 1;      // Bound to enum value 1: TYPE_PVP.
    Pve pve = 2;      // Bound to enum value 2: TYPE_PVP.
    Story story = 3;  // Bound to enum value 3: TYPE_STORY.
    Skill skill = 4;  // Bound to enum value 4: TYPE_SKILL.
  }

  enum Type {
    TYPE_NIL = 0;
    TYPE_PVP = 1 [(tableau.evalue) = { name: "PVP" }];
    TYPE_PVE = 2 [(tableau.evalue) = { name: "PVE" }];
    TYPE_STORY = 3 [(tableau.evalue) = { name: "Story" }];
    TYPE_SKILL = 4 [(tableau.evalue) = { name: "Skill" }];
  }
  message Pvp {
    int32 type = 1;                          // scalar
    int64 damage = 2;                        // scalar
    repeated protoconf.FruitType types = 3;  // incell enum list
  }
  message Pve {
    Mission mission = 1;             // incell struct
    repeated int32 heros = 2;        // incell list
    map<int32, int64> dungeons = 3;  // incell map

    message Mission {
      int32 id = 1;
      uint32 level = 2;
      int64 damage = 3;
    }
  }
  message Story {
    protoconf.Item cost = 1;                     // incell predefined struct
    map<int32, protoconf.FruitType> fruits = 2;  // incell map with value as enum type
    map<int32, Flavor> flavors = 3;              // incell map with key as enum type
    message Flavor {
      protoconf.FruitFlavor key = 1 [(tableau.field) = { name: "Key" }];
      int32 value = 2 [(tableau.field) = { name: "Value" }];
    }
  }
  message Skill {
    int32 id = 1;      // scalar
    int64 damage = 2;  // scalar
    // no field tag 3
  }
}

Predefined union in list

Based on predefined union type Target.

A worksheet TaskConf in HelloWorld.xlsx:

IDTarget1TypeTarget1Field1Target1Field2Target1Field3Target2TypeTarget2Field1Target2Field2Target2Field3
map<int32, Task>[.Target]enum<.Target.Type>unionunionunionenum<.Target.Type>unionunionunion
IDTarget1’s typeTarget1’s field1Target1’s field2Target1’s field3Target2’s typeTarget2’s field1Target2’s field2Target2’s field3
1PVP110Apple,Orange,BananaPVE1,100,9991,2,31:10,2:20,3:30
2Story1001,101:Apple,2:OrangeFragrant:1,Sour:2Skill12

Generated:

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

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

  map<int32, Task> task_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}];
  message Task {
    int32 id = 1 [(tableau.field) = {name:"ID"}];
    repeated protoconf.Target target_list = 2 [(tableau.field) = {name:"Target" layout:LAYOUT_HORIZONTAL}];
  }
}
TaskConf.json
{
    "taskMap": {
        "1": {
            "id": 1,
            "targetList": [
                {
                    "type": "TYPE_PVP",
                    "pvp": {
                        "type": 1,
                        "damage": "10",
                        "types": [
                            "FRUIT_TYPE_APPLE",
                            "FRUIT_TYPE_ORANGE",
                            "FRUIT_TYPE_BANANA"
                        ]
                    }
                },
                {
                    "type": "TYPE_PVE",
                    "pve": {
                        "mission": {
                            "id": 1,
                            "level": 100,
                            "damage": "999"
                        },
                        "heros": [
                            1,
                            2,
                            3
                        ],
                        "dungeons": {
                            "1": "10",
                            "2": "20",
                            "3": "30"
                        }
                    }
                }
            ]
        },
        "2": {
            "id": 2,
            "targetList": [
                {
                    "type": "TYPE_STORY",
                    "story": {
                        "cost": {
                            "id": 1001,
                            "num": 10
                        },
                        "fruits": {
                            "1": "FRUIT_TYPE_APPLE",
                            "2": "FRUIT_TYPE_ORANGE"
                        },
                        "flavors": {
                            "1": {
                                "key": "FRUIT_FLAVOR_FRAGRANT",
                                "value": 1
                            },
                            "2": {
                                "key": "FRUIT_FLAVOR_SOUR",
                                "value": 2
                            }
                        }
                    }
                },
                {
                    "type": "TYPE_SKILL",
                    "skill": {
                        "id": 1,
                        "damage": "2"
                    }
                }
            ]
        }
    }
}
TaskConf.txt
task_map: {
  key: 1
  value: {
    id: 1
    target_list: {
      type: TYPE_PVP
      pvp: {
        type: 1
        damage: 10
        types: FRUIT_TYPE_APPLE
        types: FRUIT_TYPE_ORANGE
        types: FRUIT_TYPE_BANANA
      }
    }
    target_list: {
      type: TYPE_PVE
      pve: {
        mission: {
          id: 1
          level: 100
          damage: 999
        }
        heros: 1
        heros: 2
        heros: 3
        dungeons: {
          key: 1
          value: 10
        }
        dungeons: {
          key: 2
          value: 20
        }
        dungeons: {
          key: 3
          value: 30
        }
      }
    }
  }
}
task_map: {
  key: 2
  value: {
    id: 2
    target_list: {
      type: TYPE_STORY
      story: {
        cost: {
          id: 1001
          num: 10
        }
        fruits: {
          key: 1
          value: FRUIT_TYPE_APPLE
        }
        fruits: {
          key: 2
          value: FRUIT_TYPE_ORANGE
        }
        flavors: {
          key: 1
          value: {
            key: FRUIT_FLAVOR_FRAGRANT
            value: 1
          }
        }
        flavors: {
          key: 2
          value: {
            key: FRUIT_FLAVOR_SOUR
            value: 2
          }
        }
      }
    }
    target_list: {
      type: TYPE_SKILL
      skill: {
        id: 1
        damage: 2
      }
    }
  }
}

Predefined union in map

Based on predefined union type Target.

A worksheet TaskConf in HelloWorld.xlsx:

IDTargetTypeTargetField1TargetField2TargetField3Progress
map<int32, Task>{.Target}enum<.Target.Type>unionunionunionint32
IDTarget’s typeTarget’s field1Target’s field2Target’s field3Progress
1PVP110Apple,Orange,Banana3
2PVE1,100,9991,2,31:10,2:20,3:3010
3Story1001,101:Apple,2:OrangeFragrant:1,Sour:210
4Skill128

Generated:

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

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

  map<int32, Task> task_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}];
  message Task {
    int32 id = 1 [(tableau.field) = {name:"ID"}];
    protoconf.Target target = 2 [(tableau.field) = {name:"Target"}];
    int32 progress = 3 [(tableau.field) = {name:"Progress"}];
  }
}
TaskConf.json
{
    "taskMap":  {
        "1":  {
            "id":  1,
            "target":  {
                "type":  "TYPE_PVP",
                "pvp":  {
                    "type":  1,
                    "damage":  "10",
                    "types":  [
                        "FRUIT_TYPE_APPLE",
                        "FRUIT_TYPE_ORANGE",
                        "FRUIT_TYPE_BANANA"
                    ]
                }
            },
            "progress":  3
        },
        "2":  {
            "id":  2,
            "target":  {
                "type":  "TYPE_PVE",
                "pve":  {
                    "mission":  {
                        "id":  1,
                        "level":  100,
                        "damage":  "999"
                    },
                    "heros":  [
                        1,
                        2,
                        3
                    ],
                    "dungeons":  {
                        "1":  "10",
                        "2":  "20",
                        "3":  "30"
                    }
                }
            },
            "progress":  10
        },
        "3":  {
            "id":  3,
            "target":  {
                "type":  "TYPE_STORY",
                "story":  {
                    "cost":  {
                        "id":  1001,
                        "num":  10
                    },
                    "fruits":  {
                        "1":  "FRUIT_TYPE_APPLE",
                        "2":  "FRUIT_TYPE_ORANGE"
                    },
                    "flavors":  {
                        "1":  {
                            "key":  "FRUIT_FLAVOR_FRAGRANT",
                            "value":  1
                        },
                        "2":  {
                            "key":  "FRUIT_FLAVOR_SOUR",
                            "value":  2
                        }
                    }
                }
            },
            "progress":  10
        },
        "4":  {
            "id":  4,
            "target":  {
                "type":  "TYPE_SKILL",
                "skill":  {
                    "id":  1,
                    "damage":  "2"
                }
            },
            "progress":  8
        }
    }
}
TaskConf.txt
task_map:  {
    key:  1
    value:  {
        id:  1
        target:  {
            type:  TYPE_PVP
            pvp:  {
                type:  1
                damage:  10
                types:  FRUIT_TYPE_APPLE
                types:  FRUIT_TYPE_ORANGE
                types:  FRUIT_TYPE_BANANA
            }
        }
        progress:  3
    }
}
task_map:  {
    key:  2
    value:  {
        id:  2
        target:  {
            type:  TYPE_PVE
            pve:  {
                mission:  {
                    id:  1
                    level:  100
                    damage:  999
                }
                heros:  1
                heros:  2
                heros:  3
                dungeons:  {
                    key:  1
                    value:  10
                }
                dungeons:  {
                    key:  2
                    value:  20
                }
                dungeons:  {
                    key:  3
                    value:  30
                }
            }
        }
        progress:  10
    }
}
task_map:  {
    key:  3
    value:  {
        id:  3
        target:  {
            type:  TYPE_STORY
            story:  {
                cost:  {
                    id:  1001
                    num:  10
                }
                fruits:  {
                    key:  1
                    value:  FRUIT_TYPE_APPLE
                }
                fruits:  {
                    key:  2
                    value:  FRUIT_TYPE_ORANGE
                }
                flavors:  {
                    key:  1
                    value:  {
                        key:  FRUIT_FLAVOR_FRAGRANT
                        value:  1
                    }
                }
                flavors:  {
                    key:  2
                    value:  {
                        key:  FRUIT_FLAVOR_SOUR
                        value:  2
                    }
                }
            }
        }
        progress:  10
    }
}
task_map:  {
    key:  4
    value:  {
        id:  4
        target:  {
            type:  TYPE_SKILL
            skill:  {
                id:  1
                damage:  2
            }
        }
        progress:  8
    }
}

Predefined incell union in map

Based on predefined union type Target.

A worksheet TaskConf in HelloWorld.xlsx:

IDTarget1Target2Progress
map<int32, Task>{.Target}|{form:FORM_TEXT}{.Target}|{form:FORM_JSON}int32
IDTarget1Target2Progress
1type:TYPE_PVP pvp:{type:1 damage:10 types:FRUIT_TYPE_APPLE types:FRUIT_TYPE_ORANGE types:FRUIT_TYPE_BANANA}{“type”:“TYPE_PVP”,“pvp”:{“type”:1,“damage”:“10”,“types”:[“FRUIT_TYPE_APPLE”,“FRUIT_TYPE_ORANGE”,“FRUIT_TYPE_BANANA”]}}3
2type:TYPE_PVE pve:{mission:{id:1 level:100 damage:999} heros:1 heros:2 heros:3 dungeons:{key:1 value:10} dungeons:{key:2 value:20} dungeons:{key:3 value:30}}{“type”:“TYPE_PVE”,“pve”:{“mission”:{“id”:1,“level”:100,“damage”:“999”},“heros”:[1,2,3],“dungeons”:{“1”:“10”,“2”:“20”,“3”:“30”}}}10
3type:TYPE_STORY story:{cost:{id:1001 num:10} fruits:{key:1 value:FRUIT_TYPE_APPLE} fruits:{key:2 value:FRUIT_TYPE_ORANGE} flavors:{key:1 value:{key:FRUIT_FLAVOR_FRAGRANT value:1}} flavors:{key:2 value:{key:FRUIT_FLAVOR_SOUR value:2}}}{“type”:“TYPE_STORY”,“story”:{“cost”:{“id”:1001,“num”:10},“fruits”:{“1”:“FRUIT_TYPE_APPLE”,“2”:“FRUIT_TYPE_ORANGE”},“flavors”:{“1”:{“key”:“FRUIT_FLAVOR_FRAGRANT”,“value”:1},“2”:{“key”:“FRUIT_FLAVOR_SOUR”,“value”:2}}}}10
4type:TYPE_SKILL skill:{id:1 damage:2}{“type”:“TYPE_SKILL”,“skill”:{“id”:1,“damage”:“2”}}8

Generated:

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

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

  map<int32, Task> task_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}];
  message Task {
    int32 id = 1 [(tableau.field) = {name:"ID"}];
    protoconf.Target target_1 = 2 [(tableau.field) = {name:"Target1" span:SPAN_INNER_CELL prop:{form:FORM_TEXT}}];
    protoconf.Target target_2 = 3 [(tableau.field) = {name:"Target2" span:SPAN_INNER_CELL prop:{form:FORM_JSON}}];
    int32 progress = 4 [(tableau.field) = {name:"Progress"}];
  }
}
TaskConf.json
{
    "taskMap":  {
        "1":  {
            "id":  1,
            "target1":  {
                "type":  "TYPE_PVP",
                "pvp":  {
                    "type":  1,
                    "damage":  "10",
                    "types":  [
                        "FRUIT_TYPE_APPLE",
                        "FRUIT_TYPE_ORANGE",
                        "FRUIT_TYPE_BANANA"
                    ]
                }
            },
            "target2":  {
                "type":  "TYPE_PVP",
                "pvp":  {
                    "type":  1,
                    "damage":  "10",
                    "types":  [
                        "FRUIT_TYPE_APPLE",
                        "FRUIT_TYPE_ORANGE",
                        "FRUIT_TYPE_BANANA"
                    ]
                }
            },
            "progress":  3
        },
        "2":  {
            "id":  2,
            "target1":  {
                "type":  "TYPE_PVE",
                "pve":  {
                    "mission":  {
                        "id":  1,
                        "level":  100,
                        "damage":  "999"
                    },
                    "heros":  [
                        1,
                        2,
                        3
                    ],
                    "dungeons":  {
                        "1":  "10",
                        "2":  "20",
                        "3":  "30"
                    }
                }
            },
            "target2":  {
                "type":  "TYPE_PVE",
                "pve":  {
                    "mission":  {
                        "id":  1,
                        "level":  100,
                        "damage":  "999"
                    },
                    "heros":  [
                        1,
                        2,
                        3
                    ],
                    "dungeons":  {
                        "1":  "10",
                        "2":  "20",
                        "3":  "30"
                    }
                }
            },
            "progress":  10
        },
        "3":  {
            "id":  3,
            "target1":  {
                "type":  "TYPE_STORY",
                "story":  {
                    "cost":  {
                        "id":  1001,
                        "num":  10
                    },
                    "fruits":  {
                        "1":  "FRUIT_TYPE_APPLE",
                        "2":  "FRUIT_TYPE_ORANGE"
                    },
                    "flavors":  {
                        "1":  {
                            "key":  "FRUIT_FLAVOR_FRAGRANT",
                            "value":  1
                        },
                        "2":  {
                            "key":  "FRUIT_FLAVOR_SOUR",
                            "value":  2
                        }
                    }
                }
            },
            "target2":  {
                "type":  "TYPE_STORY",
                "story":  {
                    "cost":  {
                        "id":  1001,
                        "num":  10
                    },
                    "fruits":  {
                        "1":  "FRUIT_TYPE_APPLE",
                        "2":  "FRUIT_TYPE_ORANGE"
                    },
                    "flavors":  {
                        "1":  {
                            "key":  "FRUIT_FLAVOR_FRAGRANT",
                            "value":  1
                        },
                        "2":  {
                            "key":  "FRUIT_FLAVOR_SOUR",
                            "value":  2
                        }
                    }
                }
            },
            "progress":  10
        },
        "4":  {
            "id":  4,
            "target1":  {
                "type":  "TYPE_SKILL",
                "skill":  {
                    "id":  1,
                    "damage":  "2"
                }
            },
            "target2":  {
                "type":  "TYPE_SKILL",
                "skill":  {
                    "id":  1,
                    "damage":  "2"
                }
            },
            "progress":  8
        }
    }
}

Define union type in sheet

There are two kinds of Mode (in metasheet @TABLEAU) to define union types in a sheet:

  • MODE_UNION_TYPE: define single union type in a sheet.
  • MODE_UNION_TYPE_MULTI: define multiple union types in a sheet.

You can define each union field by following types:

Single union type in sheet

You should specify Mode option to MODE_UNION_TYPE in metasheet @TABLEAU.

For example, a worksheet Target in HelloWorld.xlsx:

NameAliasField1Field2Field3
PVPAliasPVPID
uint32
Note
Damage
int64
Note
Type
enum<.FruitType>
Note
PVEAliasPVEHero
[]uint32
Note
Dungeon
map<int32, int64>
Note
SkillAliasSkillStartTime
datetime
Note
Duration
duration
Note
SheetMode
TargetMODE_UNION_TYPE

Generated:

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

// Generated from sheet: Target.
message Target {
  option (tableau.union) = true;

  Type type = 9999 [(tableau.field) = { name: "Type" }];
  oneof value {
    option (tableau.oneof) = {field: "Field"};

    PVP pvp = 1; // Bound to enum value: TYPE_PVP.
    PVE pve = 2; // Bound to enum value: TYPE_PVE.
    Skill skill = 3; // Bound to enum value: TYPE_SKILL.
  }
  enum Type {
    TYPE_INVALID = 0;
    TYPE_PVP = 1 [(tableau.evalue).name = "AliasPVP"];
    TYPE_PVE = 2 [(tableau.evalue).name = "AliasPVE"];
    TYPE_SKILL = 3 [(tableau.evalue).name = "AliasSkill"];
  }

  message PVP {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    int64 damage = 2 [(tableau.field) = {name:"Damage"}];
    protoconf.FruitType type = 3 [(tableau.field) = {name:"Type"}];
  }
  message PVE {
    repeated uint32 hero_list = 1 [(tableau.field) = {name:"Hero" layout:LAYOUT_INCELL}];
    map<int32, int64> dungeon_map = 2 [(tableau.field) = {name:"Dungeon" layout:LAYOUT_INCELL}];
  }
  message Skill {
    google.protobuf.Timestamp start_time = 1 [(tableau.field) = {name:"StartTime"}];
    google.protobuf.Duration duration = 2 [(tableau.field) = {name:"Duration"}];
  }
}

Multiple union types in sheet

You should specify Mode option to MODE_UNION_TYPE_MULTI in metasheet @TABLEAU.

For example, a worksheet Target in HelloWorld.xlsx:

WishTargetWishTarget note
NameAliasField1Field2Field3
HigherWishHigherHeight
int32
RicherWishRicherID
uint32
Bank
map<int32, string>
HeroTargetHeroTarget note
NameAliasField1Field2Field3
StarUpHeroStarUpID
uint32
Star
int32
LevelUpHeroLevelUpID
[]uint32
Level
int32
Super
bool
BattleTargetBattleTarget note
NameAliasField1Field2Field3
PVPBattlePVPBattleID
int32
Damage
int64
PVEBattlePVEHeroID
[]int32
Dungeon
map<int32, int64>
Boss
{uint32 ID, int64 Damage}Boss
SheetMode
TargetMODE_UNION_TYPE_MULTI

Generated:

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

message WishTarget {
  option (tableau.union) = {name:"UnionType" note:"WishTarget note"};

  Type type = 9999 [(tableau.field) = {name:"Type"}];
  oneof value {
    option (tableau.oneof) = {note:"WishTarget note" field:"Field"};

    Higher higher = 1; // Bound to enum value: TYPE_HIGHER.
    Richer richer = 2; // Bound to enum value: TYPE_RICHER.
  }

  enum Type {
    TYPE_INVALID = 0;
    TYPE_HIGHER = 1 [(tableau.evalue).name = "WishHigher"]; // WishHigher
    TYPE_RICHER = 2 [(tableau.evalue).name = "WishRicher"]; // WishRicher
  }

  message Higher {
    int32 height = 1 [(tableau.field) = {name:"Height"}];
  }
  message Richer {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    map<int32, string> bank_map = 2 [(tableau.field) = {name:"Bank" layout:LAYOUT_INCELL}];
  }
}

message HeroTarget {
  option (tableau.union) = {name:"UnionType" note:"HeroTarget note"};

  Type type = 9999 [(tableau.field) = {name:"Type"}];
  oneof value {
    option (tableau.oneof) = {note:"HeroTarget note" field:"Field"};

    StarUp star_up = 1; // Bound to enum value: TYPE_STAR_UP.
    LevelUp level_up = 2; // Bound to enum value: TYPE_LEVEL_UP.
  }

  enum Type {
    TYPE_INVALID = 0;
    TYPE_STAR_UP = 1 [(tableau.evalue).name = "HeroStarUp"]; // HeroStarUp
    TYPE_LEVEL_UP = 2 [(tableau.evalue).name = "HeroLevelUp"]; // HeroLevelUp
  }

  message StarUp {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    int32 star = 2 [(tableau.field) = {name:"Star"}];
  }
  message LevelUp {
    repeated uint32 id_list = 1 [(tableau.field) = {name:"ID" layout:LAYOUT_INCELL}];
    int32 level = 2 [(tableau.field) = {name:"Level"}];
    bool super = 3 [(tableau.field) = {name:"Super"}];
  }
}

message BattleTarget {
  option (tableau.union) = {name:"UnionType" note:"BattleTarget note"};

  Type type = 9999 [(tableau.field) = {name:"Type"}];
  oneof value {
    option (tableau.oneof) = {note:"BattleTarget note" field:"Field"};

    PVP pvp = 1; // Bound to enum value: TYPE_PVP.
    PVE pve = 2; // Bound to enum value: TYPE_PVE.
  }

  enum Type {
    TYPE_INVALID = 0;
    TYPE_PVP = 1 [(tableau.evalue).name = "BattlePVP"]; // BattlePVP
    TYPE_PVE = 2 [(tableau.evalue).name = "BattlePVE"]; // BattlePVE
  }

  message PVP {
    int32 battle_id = 1 [(tableau.field) = {name:"BattleID"}];
    int64 damage = 2 [(tableau.field) = {name:"Damage"}];
  }
  message PVE {
    repeated int32 hero_id_list = 1 [(tableau.field) = {name:"HeroID" layout:LAYOUT_INCELL}];
    map<int32, int64> dungeon_map = 2 [(tableau.field) = {name:"Dungeon" layout:LAYOUT_INCELL}];
    Boss boss = 3 [(tableau.field) = {name:"Boss" span:SPAN_INNER_CELL}];
    message Boss {
      uint32 id = 1 [(tableau.field) = {name:"ID"}];
      int64 damage = 2 [(tableau.field) = {name:"Damage"}];
    }
  }
}

Specify Number column

In Number column, you can specify custom unique field number and corresponding enum value number.

For example, a worksheet Target in HelloWorld.xlsx:

NumberNameAliasField1Field2Field3
1PVPAliasPVPID
uint32
Note
Damage
int64
Note
Type
enum<.FruitType>
Note
20PVEAliasPVEHero
[]uint32
Note
Dungeon
map<int32, int64>
Note
30SkillAliasSkillStartTime
datetime
Note
Duration
duration
Note
SheetMode
TargetMODE_UNION_TYPE

Generated:

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

// Generated from sheet: Target.
message Target {
  option (tableau.union) = true;

  Type type = 9999 [(tableau.field) = { name: "Type" }];
  oneof value {
    option (tableau.oneof) = {field: "Field"};

    PVP pvp = 1; // Bound to enum value: TYPE_PVP.
    PVE pve = 20; // Bound to enum value: TYPE_PVE.
    Skill skill = 30; // Bound to enum value: TYPE_SKILL.
  }
  enum Type {
    TYPE_INVALID = 0;
    TYPE_PVP = 1 [(tableau.evalue).name = "AliasPVP"];
    TYPE_PVE = 20 [(tableau.evalue).name = "AliasPVE"];
    TYPE_SKILL = 30 [(tableau.evalue).name = "AliasSkill"];
  }

  message PVP {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    int64 damage = 2 [(tableau.field) = {name:"Damage"}];
    protoconf.FruitType type = 3 [(tableau.field) = {name:"Type"}];
  }
  message PVE {
    repeated uint32 hero_list = 1 [(tableau.field) = {name:"Hero" layout:LAYOUT_INCELL}];
    map<int32, int64> dungeon_map = 2 [(tableau.field) = {name:"Dungeon" layout:LAYOUT_INCELL}];
  }
  message Skill {
    google.protobuf.Timestamp start_time = 1 [(tableau.field) = {name:"StartTime"}];
    google.protobuf.Duration duration = 2 [(tableau.field) = {name:"Duration"}];
  }
}

Specify Type column

By default, each union’s oneof field is a message type with the name specified by Name column. Now, you can add Type column and specify custom oneof field type:

  • scalar
  • enum
  • global predefined struct
  • custom named struct
  • local predefined struct in the same level

For example, a worksheet Target in HelloWorld.xlsx:

NameAliasTypeField1Field2#Note
FruitFruitenum<.FruitType>Bound to enum
PointPointint32Bound to scalar
ItemItem.ItemBound to global predefined struct
PlayerPlayerID
uint32
Name
string
Bound to local defined struct
FriendFriendPlayerBound to local predefined in the same level
MonsterMonsterCustomMonsterHealth
uint32
Attack
int32
Bound to local defined struct with custom type name
BossBossCustomMonsterBound to local predefined struct in the same level
SheetMode
TargetMODE_UNION_TYPE

Generated:

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

// Generated from sheet: Target.
message Target {
  option (tableau.union) = true;

  Type type = 9999 [(tableau.field) = {name:"Type"}];
  oneof value {
    option (tableau.oneof) = {field:"Field"};

    protoconf.FruitType fruit = 1; // Bound to enum value: TYPE_FRUIT.
    int32 point = 2; // Bound to enum value: TYPE_POINT.
    protoconf.Item item = 3; // Bound to enum value: TYPE_ITEM.
    Player player = 4; // Bound to enum value: TYPE_PLAYER.
    Player friend = 5; // Bound to enum value: TYPE_FRIEND.
    CustomMonster monster = 6; // Bound to enum value: TYPE_MONSTER.
    CustomMonster boss = 7; // Bound to enum value: TYPE_BOSS.
  }

  enum Type {
    TYPE_INVALID = 0;
    TYPE_FRUIT = 1 [(tableau.evalue).name = "Fruit"]; // Fruit
    TYPE_POINT = 2 [(tableau.evalue).name = "Point"]; // Point
    TYPE_ITEM = 3 [(tableau.evalue).name = "Item"]; // Item
    TYPE_PLAYER = 4 [(tableau.evalue).name = "Player"]; // Player
    TYPE_FRIEND = 5 [(tableau.evalue).name = "Friend"]; // Friend
    TYPE_MONSTER = 6 [(tableau.evalue).name = "Monster"]; // Monster
    TYPE_BOSS = 7 [(tableau.evalue).name = "Boss"]; // Boss
  }

  message Player {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    string name = 2 [(tableau.field) = {name:"Name"}];
  }
  message CustomMonster {
    int32 health = 1 [(tableau.field) = {name:"Health"}];
    int32 attack = 2 [(tableau.field) = {name:"Attack"}];
  }
}

Complex union type in sheet

For example, two worksheets Target and TaskConf in HelloWorld.xlsx:

NameAliasField1Field2Field3
PVPAliasPVPID
uint32
Note
Damage
int64
Note
Type
[]enum<.FruitType>
Note
PVEAliasPVEMission
{uint32 ID, enum<.ItemType> Type}Mission
Note
Hero
[]uint32
Note
Dungeon
map<int32, int64>
Note
StoryAliasStoryCost
{.Item}
Note
Fruit
map<int32, enum<.FruitType»
Note
Flavor
map<enum<.FruitFlavor>, enum<.FruitType»
Note
HobbyAliasHobbyFlavor
map<enum<.FruitFlavor>, enum<.FruitType»
Note
StartTime
datetime
Note
Duration
duration
Note
SkillAliasSkillID
uint32
Note
Damage
int64
Note
EmptyAliasEmpty
IDTargetTypeTargetField1TargetField2TargetField3Progress
map<int32, Task>{.Target}enum<.Target.Type>unionunionunionint32
IDTarget’s typeTarget’s field1Target’s field2Target’s field3Progress
1AliasPVP110Apple,Orange,Banana3
2AliasPVE1,Equip1,2,31:10,2:20,3:3010
3AliasStory1001,101:Apple,2:OrangeFragrant:Apple,Sour:Orange10
4AliasHobbyFragrant:Apple,Sour:Orange2023-06-01 10:00:0022s12
5AliasSkill12008
6AliasEmpty
SheetMode
TargetMODE_UNION_TYPE
Task

Generated:

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

// Generated from sheet: Target.
message Target {
  option (tableau.union) = true;

  Type type = 9999 [(tableau.field) = { name: "Type" }];
  oneof value {
    option (tableau.oneof) = {field: "Field"};

    PVP pvp = 1; // Bound to enum value: TYPE_PVP.
    PVE pve = 2; // Bound to enum value: TYPE_PVE.
    Story story = 3; // Bound to enum value: TYPE_STORY.
    Hobby hobby = 4; // Bound to enum value: TYPE_HOBBY.
    Skill skill = 5; // Bound to enum value: TYPE_SKILL.
    Empty empty = 6; // Bound to enum value: TYPE_EMPTY.
  }
  enum Type {
    TYPE_INVALID = 0;
    TYPE_PVP = 1 [(tableau.evalue).name = "AliasPVP"];
    TYPE_PVE = 2 [(tableau.evalue).name = "AliasPVE"];
    TYPE_STORY = 3 [(tableau.evalue).name = "AliasStory"];
    TYPE_HOBBY = 4 [(tableau.evalue).name = "AliasHobby"];
    TYPE_SKILL = 5 [(tableau.evalue).name = "AliasSkill"];
    TYPE_EMPTY = 6 [(tableau.evalue).name = "AliasEmpty"];
  }

  message PVP {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    int64 damage = 2 [(tableau.field) = {name:"Damage"}];
    repeated protoconf.FruitType type_list = 3 [(tableau.field) = {name:"Type" layout:LAYOUT_INCELL}];
  }
  message PVE {
    Mission mission = 1 [(tableau.field) = {name:"Mission" span:SPAN_INNER_CELL}];
    message Mission {
      uint32 id = 1 [(tableau.field) = {name:"ID"}];
      protoconf.ItemType type = 2 [(tableau.field) = {name:"Type"}];
    }
    repeated uint32 hero_list = 2 [(tableau.field) = {name:"Hero" layout:LAYOUT_INCELL}];
    map<int32, int64> dungeon_map = 3 [(tableau.field) = {name:"Dungeon" layout:LAYOUT_INCELL}];
  }
  message Story {
    protoconf.Item cost = 1 [(tableau.field) = {name:"Cost" span:SPAN_INNER_CELL}];
    map<int32, protoconf.FruitType> fruit_map = 2 [(tableau.field) = {name:"Fruit" layout:LAYOUT_INCELL}];
    map<int32, Flavor> flavor_map = 3 [(tableau.field) = {name:"Flavor" key:"Key" layout:LAYOUT_INCELL}];
    message Flavor {
      protoconf.FruitFlavor key = 1 [(tableau.field) = {name:"Key"}];
      protoconf.FruitType value = 2 [(tableau.field) = {name:"Value"}];
    }
  }
  message Hobby {
    map<int32, Flavor> flavor_map = 1 [(tableau.field) = {name:"Flavor" key:"Key" layout:LAYOUT_INCELL}];
    message Flavor {
      protoconf.FruitFlavor key = 1 [(tableau.field) = {name:"Key"}];
      protoconf.FruitType value = 2 [(tableau.field) = {name:"Value"}];
    }
    google.protobuf.Timestamp start_time = 2 [(tableau.field) = {name:"StartTime"}];
    google.protobuf.Duration duration = 3 [(tableau.field) = {name:"Duration"}];
  }
  message Skill {
    uint32 id = 1 [(tableau.field) = {name:"ID"}];
    int64 damage = 2 [(tableau.field) = {name:"Damage"}];
  }
  message Empty {
  }
}

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

  map<int32, Task> task_map = 1 [(tableau.field) = {key:"ID" layout:LAYOUT_VERTICAL}];
  message Task {
    int32 id = 1 [(tableau.field) = {name:"ID"}];
    protoconf.Target target = 2 [(tableau.field) = {name:"Target"}];
    int32 progress = 3 [(tableau.field) = {name:"Progress"}];
  }
}
TaskConf.json
{
    "taskMap": {
        "1": {
            "id": 1,
            "target": {
                "type": "TYPE_PVP",
                "pvp": {
                    "id": 1,
                    "damage": "10",
                    "typeList": [
                        "FRUIT_TYPE_APPLE",
                        "FRUIT_TYPE_ORANGE",
                        "FRUIT_TYPE_BANANA"
                    ]
                }
            },
            "progress": 3
        },
        "2": {
            "id": 2,
            "target": {
                "type": "TYPE_PVE",
                "pve": {
                    "mission": {
                        "id": 1,
                        "type": "ITEM_TYPE_EQUIP"
                    },
                    "heroList": [
                        1,
                        2,
                        3
                    ],
                    "dungeonMap": {
                        "1": "10",
                        "2": "20",
                        "3": "30"
                    }
                }
            },
            "progress": 10
        },
        "3": {
            "id": 3,
            "target": {
                "type": "TYPE_STORY",
                "story": {
                    "cost": {
                        "id": 1001,
                        "num": 10
                    },
                    "fruitMap": {
                        "1": "FRUIT_TYPE_APPLE",
                        "2": "FRUIT_TYPE_ORANGE"
                    },
                    "flavorMap": {
                        "1": {
                            "key": "FRUIT_FLAVOR_FRAGRANT",
                            "value": "FRUIT_TYPE_APPLE"
                        },
                        "2": {
                            "key": "FRUIT_FLAVOR_SOUR",
                            "value": "FRUIT_TYPE_ORANGE"
                        }
                    }
                }
            },
            "progress": 10
        },
        "4": {
            "id": 4,
            "target": {
                "type": "TYPE_HOBBY",
                "hobby": {
                    "flavorMap": {
                        "1": {
                            "key": "FRUIT_FLAVOR_FRAGRANT",
                            "value": "FRUIT_TYPE_APPLE"
                        },
                        "2": {
                            "key": "FRUIT_FLAVOR_SOUR",
                            "value": "FRUIT_TYPE_ORANGE"
                        }
                    },
                    "startTime": "2023-06-01T02:00:00Z",
                    "duration": "22s"
                }
            },
            "progress": 12
        },
        "5": {
            "id": 5,
            "target": {
                "type": "TYPE_SKILL",
                "skill": {
                    "id": 1,
                    "damage": "200"
                }
            },
            "progress": 8
        },
        "6": {
            "id": 6,
            "target": {
                "type": "TYPE_EMPTY",
                "empty": {}
            },
            "progress": 0
        }
    }
}