{"id":15011635,"url":"https://github.com/cathei/bakingsheet","last_synced_at":"2025-04-05T18:12:04.566Z","repository":{"id":56746227,"uuid":"299796624","full_name":"cathei/BakingSheet","owner":"cathei","description":"Easy datasheet management for C# and Unity. Supports Excel, Google Sheet, JSON and CSV format.","archived":false,"fork":false,"pushed_at":"2024-06-18T04:22:04.000Z","size":8392,"stargazers_count":377,"open_issues_count":14,"forks_count":40,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-05T18:11:58.981Z","etag":null,"topics":["csharp","csharp-library","csv","datasheet","excel","google-sheets","json","nuget-package","openupm","spreadsheet","unity3d","upm"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cathei.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-09-30T03:11:08.000Z","updated_at":"2025-03-29T15:16:57.000Z","dependencies_parsed_at":"2024-01-13T23:16:18.227Z","dependency_job_id":"34e21dec-b1fb-4856-b599-f5c4b3d8131a","html_url":"https://github.com/cathei/BakingSheet","commit_stats":{"total_commits":303,"total_committers":2,"mean_commits":151.5,"dds":"0.0033003300330033403","last_synced_commit":"bcbcb96088cb1eea68c59a18ed3976286fe116c1"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cathei%2FBakingSheet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cathei%2FBakingSheet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cathei%2FBakingSheet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cathei%2FBakingSheet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cathei","download_url":"https://codeload.github.com/cathei/BakingSheet/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247378152,"owners_count":20929297,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","csharp-library","csv","datasheet","excel","google-sheets","json","nuget-package","openupm","spreadsheet","unity3d","upm"],"created_at":"2024-09-24T19:41:22.373Z","updated_at":"2025-04-05T18:12:04.545Z","avatar_url":"https://github.com/cathei.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Nuget](https://img.shields.io/nuget/v/BakingSheet)](https://www.nuget.org/packages?q=BakingSheet) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/cathei/BakingSheet)](https://github.com/cathei/BakingSheet/releases) [![openupm](https://img.shields.io/npm/v/com.cathei.bakingsheet?label=openupm\u0026registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.cathei.bakingsheet/) [![GitHub](https://img.shields.io/github/license/cathei/BakingSheet)](https://github.com/cathei/BakingSheet/blob/master/LICENSE) [![Discord](https://img.shields.io/discord/942240862354702376?color=%235865F2\u0026label=discord\u0026logo=discord\u0026logoColor=%23FFFFFF)](https://discord.gg/wXjxjfrDQa)\n\n# BakingSheet 🍞\nEasy datasheet management for C# and Unity. Supports Excel, Google Sheet, JSON and CSV format. It has been used for several mobile games that released on Google Play and AppStore.\n\n## Table of Contents\n* [Concept](#concept)\n* [Features](#features)\n* [Install](#install)\n    + [Need help?](#need-help-)\n* [Contribution](#contribution)\n* Usages\n    * [First Step](#first-step)\n    * [Supported Column Type](#supported-column-type)\n    * [Converters](#converters)\n    * [Save and Load Converted Datasheet](#save-and-load-converted-datasheet)\n    * [Accessing Row](#accessing-row)\n    * [Using List Column](#using-list-column)\n    * [Using Dictionary Column](#using-dictionary-column)\n    * [Using Nested Type Column](#using-nested-type-column)\n    * [Using Row Array](#using-row-array)\n    * [Using Cross-Sheet Reference](#using-cross-sheet-reference)\n    * [Using Non-String Column as Id](#using-non-string-column-as-id)\n    * [Using Post Load Hook](#using-post-load-hook)\n    * [Using AssetPostProcessor to Automate Converting](#using-assetpostprocessor-to-automate-converting)\n    * [About AOT Code Stripping (Unity)](#about-aot-code-stripping--unity-)\n    * [Optional Script Defining Symbols (Unity)](#optional-script-defining-symbols--unity-)\n\n## Concept\nThroughout all stage of game development, you'll need to deal with various data. Characters, stats, stages, currencies and so on! If you're using Unity, scriptable object and inspector is not good enough for mass edition and lacks powerful features like functions or fill up. With BakingSheet your designers can use existing spreadsheet editor, while you, the programmer, can directly use C# object without messy parsing logics or code generations.\n\nLet's say your team is making a RPG game. Your game has 100 characters and 10 stats for each character. If your team use Unity's scriptable object, designers will have to spend lots of time adding and editing from Unity inspector. And after setup what if you need mass edit, like to double ATK stat of all characters? Will you go through all characters with Unity inspector, or make Editor script for every time mass edit is required? With BakingSheet, designers can work easily with spreadsheet functions and fill ups without programmer help!\n\n![Concept](.github/images/concept.png)\n\nBakingSheet's core concept is controlling datasheet schema from C# code, make things flexible while supporting multiple sources like Excel files or Google sheets. You can think it as datasheet version of [ORM](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping). Also, you won't have to include source Excel files or parsing libraries for production builds. BakingSheet supports JSON serialization by default, you can ship your build with JSON or your custom format.\n\nBakingSheet's basic workflow is like this:\n1. Programmers make C# schema that represents Datasheet. (They can provide sample Excel files or Google Sheet with headers.)\n2. Designers fill up the Datasheet, using any powerful functions and features of spreadsheet.\n3. Edit-time script converts Datasheet to JSON (or any custom format) with your C# schema and validates data.\n4. Runtime script reads from JSON (or any custom format) with your C# schema.\n5. Your business logic directly uses C# instance of your schema.\n6. Profit!\n\nDon't trust me that it's better than using ScriptableObject? You might change your mind if you see how famous SuperCell ships their games with CSV, like [Clash Royale](https://github.com/smlbiobot/cr-csv/tree/master/assets/csv_logic) or [Brawl Stars](https://github.com/weeco/brawlstars-assets/tree/master/7.278.1/csv_logic). Though of course, their games aren't made with Unity, still a very good example to show how you can utilize spreadsheet!\n\n![Sample1](.github/images/sample_simple.jpg)\n![Sample2](.github/images/sample_complex.jpg)\n\n## Features\n* Easy-to-use Datasheet management.\n* Define schema as C# classes - no messy metadata on your datasheet.\n* Supports importing from Excel, Google sheet, CSV and JSON.\n* Supports exporting to CSV and JSON.\n* Supports .NET platforms and all Unity platforms.\n* Powerful Cross-sheet reference feature.\n* Referencing Asset data with [AssetPath](docs/asset-path.md).\n* [Customizable value converter](docs/value-converter.md).\n* [Customizable data verification](docs/data-verification.md).\n* [Partial sheet import](https://github.com/cathei/BakingSheet/issues/22).\n\n## Install\nFor C# projects or server, download with [NuGet](https://www.nuget.org/packages?q=BakingSheet).\n\nFor Unity projects, add git package from Package Manager.\n```\nhttps://github.com/cathei/BakingSheet.git?path=UnityProject/Packages/com.cathei.bakingsheet#v4.1.3\n```\n\nOr install it via [OpenUPM](https://openupm.com/packages/com.cathei.bakingsheet/).\n```\nopenupm add com.cathei.bakingsheet\n```\n\nSample `.unitypackage` is available in [releases](https://github.com/cathei/BakingSheet/releases). (Main package should be installed first.)\n\nIf you are planning to use StreamingAssets folder on Android, install [BetterStreamingAssets](docs/streaming-assets.md) as well.\n\n### Need help?\nBefore you start, we want to mention that if you have problem or need help, you can always ask directly on [Discord Channel](https://discord.gg/wXjxjfrDQa)!\n\n## Contribution\nWe appreciate any contribution. Please create [issue](https://github.com/cathei/BakingSheet/issues) for bugs or feature requests. Any contribution to feature, test case, or documentation through [pull requests](https://github.com/cathei/BakingSheet/pulls) are welcome! Any blog posts, articles, shares about this project will be greatful!\n\n## First Step\nBakingSheet manages datasheet schema as C# code. `Sheet` class represents a table and `SheetRow` class represents a record. Below is example content of file `Consumables` page in `MySheets.xlsx`. Also, any column starts with `$` will be considered as comment and ignored.\n\n![Plain Sample](.github/images/sample_plain.png)\n\n\u003cdetails\u003e\n\u003csummary\u003eMarkdown version\u003c/summary\u003e\n\n| Id         | Name              | Price | $Comment   |\n|------------|-------------------|-------|------------|\n| LVUP_001   | Warrior's Shield  | 10000 | Warrior Lv up material |\n| LVUP_002   | Mage's Staff      | 10000 | Mage Lv up material |\n| LVUP_003   | Assassin's Dagger | 10000 | Assassin Lv up material |\n| POTION_001 | Health Potion     | 30    | Heal 20 Hp |\n| POTION_002 | Mana Potion       | 50    | Heal 20 Mp |\n\u003c/details\u003e\n\nCode below is corresponding BakingSheet class.\n```csharp\npublic class ConsumableSheet : Sheet\u003cConsumableSheet.Row\u003e\n{\n    public class Row : SheetRow\n    {\n        // use name of matching column\n        public string Name { get; private set; }\n        public int Price { get; private set; }\n    }\n}\n```\nYou can see there are two classes, `ConsumableSheet` and `ConsumableSheet.Row`. Each represents a page of sheet and a single row. `ConsumableSheet` is surrounding `Row` class (It is not forced but recommended convention). Important part is they will inherit from `Sheet\u003cTRow\u003e` and `SheetRow`.\n\n`Id` column is mandatory, so it is already defined in base `SheetRow` class. `Id` is `string` by default, but you can change type. See [this section](#using-non-string-column-as-id) to use non-string type for `Id`.\n\nTo represent collection of sheets, a document, let's create `SheetContainer` class inherits from `SheetContainerBase`.\n```csharp\npublic class SheetContainer : SheetContainerBase\n{\n    public SheetContainer(Microsoft.Extensions.Logging.ILogger logger) : base(logger) {}\n\n    // property name matches with corresponding sheet name\n    // for .xlsx or google sheet, **property name matches with the name of sheet tab in workbook**\n    // for .csv or .json, **property name matches with the name of file**\n    public ConsumableSheet Consumables { get; private set; }\n\n    // add other sheets as you extend your project\n    public CharacterSheet Characters { get; private set; }\n}\n```\nYou can add as many sheets you want as properties of your `SheetContainer`. This class is designed to be \"fat\", means single `SheetContainer` should contain all your sheets unless there is specific reason to partition your sheets. For example when you want to deploy some `Sheet` only exclusive to server program, you might want to partition `ServerSheetContainer` and `ClientSheetContainer`.\n\n## Supported Column Type\n* `string`\n* Numeric primitive types (`int`, `long`, `float`, `double`, and so on)\n* `bool` (\"TRUE\" or \"FALSE\")\n* Custom `enum` types\n* `DateTime` and `TimeSpan`\n* Cross-sheet reference (`Sheet\u003c\u003e.Reference`)\n* Nullable for any other supported value type (for example `int?`)\n* `List\u003c\u003e` and `Dictionary\u003c,\u003e`\n* Custom `struct` and `class` as [nested column](#using-nested-type-column)\n* Custom type converted with [ValueConverter](docs/value-converter.md)\n\n\u003e **Note**  \n\u003e When using `JsonConverter`, `enum` is serialized as `string` by default so you won't have issue when reordering them.\n\n## Converters\nConverters are simple implementation import/export records from datasheet sources. These come as separated library, as it's user's decision to select datasheet source.\nUser can have converting process, to convert datasheet to other format ahead of time and not include heavy converters in production applications.\n\nBakingSheet supports four basic converters. They're included in Unity package as well.\n\n| Package Name                                                                                   | Format                       | Supports Import | Supports Export |\n|------------------------------------------------------------------------------------------------|------------------------------|-----------------|-----------------|\n| [BakingSheet.Converters.Excel](https://www.nuget.org/packages/BakingSheet.Converters.Excel/)   | Microsoft Excel              | O               | X               |\n| [BakingSheet.Converters.Google](https://www.nuget.org/packages/BakingSheet.Converters.Google/) | Google Sheet                 | O               | X               |\n| [BakingSheet.Converters.Csv](https://www.nuget.org/packages/BakingSheet.Converters.Csv/)       | Comma-Separated Values (CSV) | O               | O               |\n| [BakingSheet.Converters.Json](https://www.nuget.org/packages/BakingSheet.Converters.Json/)     | JSON                         | O               | O               |\n| [ScriptableObject Converter](docs/scriptable-object.md) (Unity only)                           | ScriptableObject             | O               | O (Read-only)   |\n\nBelow code shows how to convert `.xlsx` files from `Excel/Files/Path` directory.\n```csharp\n// any ILogger will work, there is built-in UnityLogger\nvar logger = new UnityLogger();\n\n// pass logger to receive logs\nvar sheetContainer = new SheetContainer(logger);\n\n// create excel converter from path\nvar excelConverter = new ExcelSheetConverter(\"Excel/Files/Path\");\n\n// bake sheets from excel converter\nawait sheetContainer.Bake(excelConverter);\n```\n\nFor Google Sheet, first create your service account through Google API Console. Then add it to your sheet with `Viewer` permission. Use Google credential for that service account to create converter. For detailed information about how to create service account and link to your sheet, see [How to import from Google Sheet](./docs/google-sheet-import.md).\n\n```csharp\n// replace with your Google sheet identifier\n// https://developers.google.com/sheets/api/guides/concepts\nstring googleSheetId = \"1iWMZVI4FgtGbig4EgPIun_BRbzp4ulqRIzINZQl-AFI\";\n\n// service account credential than can read the sheet you're converting\n// this starts with { \"type\": \"service_account\", \"project_id\": ...\nstring googleCredential = File.ReadAllText(\"Some/Path/Credential.json\");\n\nvar googleConverter = new GoogleSheetConverter(googleSheetId, googleCredential);\n\n// bake sheets from google converter\nawait sheetContainer.Bake(googleConverter);\n```\n\n## Save and Load Converted Datasheet\nBelow code shows how to load sheet from Excel and save as JSON. This typically happens through Unity Editor script or any pre-build time script.\n\n```csharp\n// create excel converter from path\nvar excelConverter = new ExcelSheetConverter(\"Excel/Files/Path\");\n\n// create json converter from path\nvar jsonConverter = new JsonSheetConverter(\"Json/Files/Path\");\n\n// convert from excel\nawait sheetContainer.Bake(excelConverter);\n\n// save as json\nawait sheetContainer.Store(jsonConverter);\n```\n\nThen, for runtime you can load your data from JSON.\n\n```csharp\n// create json converter from path\nvar jsonConverter = new JsonSheetConverter(\"Json/Files/Path\");\n\n// load from json\nawait sheetContainer.Bake(jsonConverter);\n```\n\nYou can extend `JsonSheetConverter` to customize serialization process. For example encrypting data or prettifying JSON.\n\n\u003e **Note**  \n\u003e For AOT platforms (iOS, Android), read about [AOT Code Stripping](#about-aot-code-stripping-unity).\n\n\u003e **Note**  \n\u003e If you are using `StreamingAssets` on Android, also see [Reading From StreamingAssets](docs/streaming-assets.md).\n\n## Accessing Row\nNow you have `SheetContainer` loaded from your data, accessing to the row is fairly simple. Below code shows how to access specific `ConsumableSheet.Row`.\n```csharp\n// same as sheetContainer.Consumables.Find(\"LVUP_003\");\n// returns null if no row found\nvar row = sheetContainer.Consumables[\"LVUP_003\"];\n\n// print \"Assassin's dagger\"\nlogger.LogInformation(row.Name);\n```\n\n`Sheet\u003cT\u003e` is `KeyedCollection`, you can loop through it and order is guaranteed to be as same as your spreadsheet. Plus of course you can use all benefits of `IEnumerable\u003cT\u003e`.\n```csharp\n// loop through all rows and print their names\nforeach (var row in sheetContainer.Consumables)\n    logger.LogInformation(row.Name);\n\n// loop through consumable ids that price over 5000\nforeach (var consumableId in sheetContainer.Consumables.Where(row =\u003e row.Price \u003e 5000).Select(row =\u003e row.Id))\n    logger.LogInformation(consumableId);\n```\n\n## Using List Column\nList columns are used for simple array.\n\n![List Sample](.github/images/sample_list.png)\n\n\u003cdetails\u003e\n\u003csummary\u003eFlat header\u003c/summary\u003e\n\n| Id         | Name          | Monsters:1 | Monsters:2 | Monsters:3 | Loots:1    | Loots:2  |\n| ---------- | ------------- | ---------- | ---------- | ---------- |------------|----------|\n| DUNGEON001 | Easy Field    | MONSTER001 |            |            | POTION_001 | LVUP_001 |\n| DUNGEON002 | Expert Zone   | MONSTER001 | MONSTER002 |            | POTION_002 | LVUP_002 |\n| DUNGEON003 | Dragon’s Nest | MONSTER003 | MONSTER004 | MONSTER005 | LVUP_003   |          |\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eSplit header\u003c/summary\u003e\n\n| Id         | Name          | Monsters   |            |            | Loots      |          |\n|------------|---------------|------------|------------|------------|------------|----------|\n|            |               | 1          | 2          | 3          | 1          | 2        |\n| DUNGEON001 | Easy Field    | MONSTER001 |            |            | POTION_001 | LVUP_001 |\n| DUNGEON002 | Expert Zone   | MONSTER001 | MONSTER002 |            | POTION_002 | LVUP_002 |\n| DUNGEON003 | Dragon’s Nest | MONSTER003 | MONSTER004 | MONSTER005 | LVUP_003   |          |\n\u003c/details\u003e\n\n```csharp\npublic class DungeonSheet : Sheet\u003cDungeonSheet.Row\u003e\n{\n    public class Row : SheetRow\n    {\n        public string Name { get; private set; }\n\n        // you can use any supported type as list\n        // to know more about sheet reference types, see cross-sheet reference section\n        public List\u003cMonsterSheet.Reference\u003e Monsters { get; private set; }\n        public List\u003cConsumableSheet.Reference\u003e Loots { get; private set; }\n    }\n}\n```\nUse it as simple as just including a column has type implmenting `IList\u003cT\u003e`. Since spreadsheet is designer's area, index on sheet is 1-based. So be aware when you access it from code.\n\nAlso you can pick between flat-header style(`Monsters:1`) and split-header style(`Monsters`, `1`) as the example shows. There is no problem to mix-and-match or nest them.\n\n## Using Dictionary Column\nDictionary columns are used when key-based access of value is needed.\n\n![Dictionary Sample](.github/images/sample_dict.png)\n\n\u003cdetails\u003e\n\u003csummary\u003eFlat header\u003c/summary\u003e\n\n| Id     | Name          | Texts:Greeting    | Texts:Purchasing | Texts:Leaving     |\n| ------ | ------------- | ----------------- | ---------------- | ----------------- |\n| NPC001 | Fat Baker     | Morning traveler! | Thank you!       | Come again!       |\n| NPC002 | Blacksmith    | G'day!            | Good choice.     | Take care.        |\n| NPC003 | Potion Master | What do you want? | Take it already. | Don't come again. |\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eSplit header\u003c/summary\u003e\n\n| Id     | Name          | Texts             |                  |                   |\n|--------|---------------|-------------------|------------------|-------------------|\n|        |               | Greeting          | Purchasing       | Leaving           |\n| NPC001 | Fat Baker     | Morning traveler! | Thank you!       | Come again!       |\n| NPC002 | Blacksmith    | G'day!            | Good choice.     | Take care.        |\n| NPC003 | Potion Master | What do you want? | Take it already. | Don't come again. |\n\u003c/details\u003e\n\n```csharp\npublic enum Situation\n{\n    Greeting,\n    Purchasing,\n    Leaving\n}\n\npublic class NpcSheet : Sheet\u003cNpcSheet.Row\u003e\n{\n    public class Row : SheetRow\n    {\n        public string Name { get; private set; }\n\n        public Dictionary\u003cSituation, string\u003e Texts { get; private set; }\n    }\n}\n```\nUse it as simple as just including a column has type implmenting `IDictionary\u003cTKey, TValue\u003e`.\n\n## Using Nested Type Column\nNested type columns are used for complex structure.\n\n![Nested Type Sample](.github/images/sample_dict.png)\n\n\u003cdetails\u003e\n\u003csummary\u003eFlat header\u003c/summary\u003e\n\n| Id     | Name          | Texts:Greeting    | Texts:Purchasing | Texts:Leaving     |\n| ------ | ------------- | ----------------- | ---------------- | ----------------- |\n| NPC001 | Fat Baker     | Morning traveler! | Thank you!       | Come again!       |\n| NPC002 | Blacksmith    | G'day!            | Good choice.     | Take care.        |\n| NPC003 | Potion Master | What do you want? | Take it already. | Don't come again. |\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eSplit header\u003c/summary\u003e\n\n| Id     | Name          | Texts             |                  |                   |\n|--------|---------------|-------------------|------------------|-------------------|\n|        |               | Greeting          | Purchasing       | Leaving           |\n| NPC001 | Fat Baker     | Morning traveler! | Thank you!       | Come again!       |\n| NPC002 | Blacksmith    | G'day!            | Good choice.     | Take care.        |\n| NPC003 | Potion Master | What do you want? | Take it already. | Don't come again. |\n\u003c/details\u003e\n\n```csharp\npublic struct SituationText\n{\n    public string Greeting { get; private set; }\n    public string Purchasing { get; private set; }\n    public string Leaving { get; private set; }\n}\n\npublic class NpcSheet : Sheet\u003cNpcSheet.Row\u003e\n{\n    public class Row : SheetRow\n    {\n        public string Name { get; private set; }\n\n        public SituationText Texts { get; private set; }\n    }\n}\n```\nAs you see, content of the datasheet is just same as when using Dictionary column. The data type of column determines how BakingSheet reads the column.\n\n## Using Row Array\nRow arrays are used for 2-dimentional structure. Below is example content of file `Heroes.xlsx`.\n\n![Row Array Sample](.github/images/sample_rowarray.png)\n\n\u003cdetails\u003e\n\u003csummary\u003eMarkdown version\u003c/summary\u003e\n\n| Id      | Name     | Strength | Inteligence | Vitality | StatMultiplier | RequiredExp | RequiredMaterial |\n|---------|----------|----------|-------------|----------|----------------|-------------|------------------|\n| HERO001 | Warrior  | 100      | 80          | 140      | 1              | 0           |                  |\n|         |          |          |             |          | 1.2            | 10          |                  |\n|         |          |          |             |          | 1.4            | 20          |                  |\n|         |          |          |             |          | 1.6            | 40          |                  |\n|         |          |          |             |          | 2              | 100         | LVUP_001         |\n| HERO002 | Mage     | 60       | 160         | 80       | 1              | 0           |                  |\n|         |          |          |             |          | 1.2            | 10          |                  |\n|         |          |          |             |          | 1.4            | 20          |                  |\n|         |          |          |             |          | 1.6            | 40          |                  |\n|         |          |          |             |          | 2              | 100         | LVUP_002         |\n| HERO003 | Assassin | 140      | 100         | 80       | 1              | 0           |                  |\n|         |          |          |             |          | 1.2            | 10          |                  |\n|         |          |          |             |          | 1.4            | 20          |                  |\n|         |          |          |             |          | 1.6            | 40          |                  |\n|         |          |          |             |          | 2              | 100         | LVUP_003         |\n\u003c/details\u003e\n\nRows without `Id` is considered as part of previous row. You can merge the non-array cells to make it visually intuitive. Below corresponding code shows how to define row arrays.\n\n```csharp\npublic class HeroSheet : Sheet\u003cHeroSheet.Row\u003e\n{\n    public class Row : SheetRowArray\u003cElem\u003e\n    {\n        public string Name { get; private set; }\n\n        public int Strength { get; private set; }\n        public int Inteligence { get; private set; }\n        public int Vitality { get; private set; }\n\n        public Elem GetLevel(int level)\n        {\n            // Level 1 would be index 0\n            return this[level - 1];\n        }\n\n        // Max level would be count of elements\n        public int MaxLevel =\u003e Count;\n    }\n\n    public class Elem : SheetRowElem\n    {\n        public float StatMultiplier { get; private set; }\n        public int RequiredExp { get; private set; }\n        public string RequiredMaterial { get; private set; }\n    }\n}\n```\n`SheetRowArray\u003cTElem\u003e` implements `IEnumerable\u003cTElem\u003e`, indexer `this[int]` and `Count` property.\n\n\u003e **Note**  \n\u003e It is worth mention you can use `VerticalList\u003cT\u003e` to cover the case you want to vertically extend your `List\u003cT\u003e` without pairing them as `Elem`. Though we recommend to split the sheet in that case if possible.\n\n## Using Cross-Sheet Reference\nBelow code shows how to replace `string RequiredMaterial` to `ConsumableSheet.Reference RequiredMaterial` to add extra reliablity. `Sheet\u003cTKey, TRow\u003e.Reference` type is serialized as `TKey`, and verifies that row with same id exists in the sheet.\n\n```csharp\npublic class HeroSheet : Sheet\u003cHeroSheet.Row\u003e\n{\n    public class Row : SheetRowArray\u003cElem\u003e\n    {\n        // ...\n    }\n\n    public class Elem : SheetRowElem\n    {\n        public float StatMultiplier { get; private set; }\n        public int RequiredExp { get; private set; }\n        public ConsumableSheet.Reference RequiredMaterial { get; private set; }\n    }\n}\n```\n\n```csharp\npublic class SheetContainer : SheetContainerBase\n{\n    // ...\n\n    // use name of each matching sheet name from source\n    public HeroSheet Heroes { get; private set; }\n    public ConsumableSheet Consumables { get; private set; }\n}\n```\nBoth `ConsumableSheet` and `HeroSheet` must be the properties on same `SheetContainer` class to reference each other's row.\n\nNow, not only error message will pop up when `RequiredMaterial` doesn't exist in `SheetContainer.Consumables`, you can access `ConsumableSheet.Row` directly through it.\n\n```csharp\nvar heroRow = sheetContainer.Heroes[\"HERO001\"];\n\n// LVUP_001 from Consumables sheet\nvar consumableRow = heroRow.GetLevel(5).RequiredMaterial.Ref;\n\n// print \"Warrior's Shield\"\nlogger.LogInformation(consumableRow.Name);\n```\n\n## Using Non-String Column as Id\nAny type can be used value can be also used as `Id`. This is possible as passing type argument to generic class `SheetRow\u003cTKey\u003e` and `Sheet\u003cTKey, TRow\u003e`. Below is example content of file `Contstants.xlsx`.\n\n![Const Sample](.github/images/sample_const.png)\n\n\u003cdetails\u003e\n\u003csummary\u003eMarkdown version\u003c/summary\u003e\n\n| Id             | Value                                 |\n|----------------|---------------------------------------|\n| ServerAddress  | https://github.com/cathei/BakingSheet |\n| InitialGold    | 1000                                  |\n| CriticalChance | 0.1                                   |\n\u003c/details\u003e\n\nBelow code shows how to use enumeration type as Id.\n```csharp\npublic enum GameConstant\n{\n    ServerAddress,\n    InitialGold,\n    CriticalChance,\n}\n\npublic class ConstantSheet : Sheet\u003cGameConstant, ConstantSheet.Row\u003e\n{\n    public class Row : SheetRow\u003cGameConstant\u003e\n    {\n        public string Value { get; private set; }\n    }\n}\n```\n\n## Using Post Load Hook\nYou can override `PostLoad` method of `Sheet`, `SheetRow` or `SheetRowElem` to execute post load process.\n\nBelow code shows how to convert loaded sheet value dynamically.\n```csharp\npublic class ConstantSheet : Sheet\u003cGameConstant, ConstantSheet.Row\u003e\n{\n    public class Row : SheetRow\u003cGameConstant\u003e\n    {\n        public string Value { get; private set; }\n\n        private int valueInt;\n        public int ValueInt =\u003e valueInt;\n\n        private float valueFloat;\n        public float ValueFloat =\u003e valueFloat;\n\n        public override void PostLoad(SheetConvertingContext context)\n        {\n            base.PostLoad(context);\n\n            int.TryParse(Value, out valueInt);\n            float.TryParse(Value, out valueFloat);\n        }\n    }\n\n    public string GetString(GameConstant key)\n    {\n        return Find(key).Value;\n    }\n\n    public int GetInt(GameConstant key)\n    {\n        return Find(key).ValueInt;\n    }\n\n    public float GetFloat(GameConstant key)\n    {\n        return Find(key).ValueFloat;\n    }\n}\n```\n\n\u003e **Note**  \n\u003e Properties without setter are not serialized. Alternatively you can use `[NonSerialized]` attribute.\n\n## Using AssetPostProcessor to Automate Converting\nFor Excel and CSV, you could set up `AssetPostProcessor` to automate converting process. Recommended practice is keeping both source `.xlsx` and `.csv` files alongside with destination `.json` files in your version control system. For Google Sheet, it is instead recommended to use custom `MenuItem` to convert into destination `.json` files that keeped in your version control.\n\nThe below is example source code that triggers when any `.xlsx` is changed, convert Excel sheet under `Assets/Excel` into `.json` under `Assets/StreamingAssets/Json`. You can customize this logic with your desired source and destination folder.\n```csharp\npublic class ExcelPostprocessor : AssetPostprocessor\n{\n    static async void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)\n    {\n        // automatically run postprocessor if any excel file is imported\n        string excelAsset = importedAssets.FirstOrDefault(x =\u003e x.EndsWith(\".xlsx\"));\n\n        if (excelAsset != null)\n        {\n            // excel path as \"Assets/Excel\"\n            var excelPath = Path.Combine(Application.dataPath, \"Excel\");\n\n            // json path as \"Assets/StreamingAssets/Json\"\n            var jsonPath = Path.Combine(Application.streamingAssetsPath, \"Json\");\n\n            var logger = new UnityLogger();\n            var sheetContainer = new SheetContainer(logger);\n\n            // create excel converter from path\n            var excelConverter = new ExcelSheetConverter(excelPath);\n\n            // bake sheets from excel converter\n            await sheetContainer.Bake(excelConverter);\n\n            // create json converter to path\n            var jsonConverter = new JsonSheetConverter(jsonPath);\n\n            // save datasheet to streaming assets\n            await sheetContainer.Store(jsonConverter);\n\n            AssetDatabase.Refresh();\n\n            Debug.Log(\"Excel sheet converted.\");\n        }\n    }\n}\n```\n\n## About AOT Code Stripping (Unity)\nIf you are working on AOT (IL2CPP) environment, you would have option called `Managed Stripping Level` in your Player Settings. Since BakingSheet uses reflection, if you set stripping level `Medium` or `High`, the stripper might remove the code piece that required. Especially some property setters.\n\nYou can prevent this by either using `Low` stripping level, or adding own `link.xml` to preserve your sheet classes. The below is simplest example of `link.xml`. If you want to know more about it, see [Unity's Documentation](https://docs.unity3d.com/Manual/ManagedCodeStripping.html#LinkXMLAnnotation).\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\" ?\u003e\n\u003clinker\u003e\n  \u003c!--\n    Replace `MyCompany.MyGame.Sheet` to your assembly to prevent Unity code stripping\n  --\u003e\n  \u003cassembly fullname=\"MyCompany.MyGame.Sheet\" preserve=\"all\"/\u003e\n\u003c/linker\u003e\n```\n\n## Optional Script Defining Symbols (Unity)\nThere is few optional symbols that can be defined for runtime usage. By default only JSON and ScriptableObject converters will be included to the build.\n\n| Symbol                              | Effect                                                                                                                                                   |\n|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|\n| BAKINGSHEET_RUNTIME_GOOGLECONVERTER | Include Google Converter to your build.\u003cbr/\u003eSee also: [Google Sheet Converter](docs/google-sheet-import.md#how-to-use-google-sheet-converter-on-runtime) |\n| BAKINGSHEET_RUNTIME_CSVCONVERTER    | Include CSV Converter to your build.                                                                                                                     |\n| BAKINGSHEET_EXTERNAL_LOGGING_DLL    | Use external `Microsoft.Extensions.Logging.Abstractions.dll`. Useful if you already have same dll in your project for different dependency.              |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcathei%2Fbakingsheet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcathei%2Fbakingsheet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcathei%2Fbakingsheet/lists"}