YamlRecords 1.0.0

dotnet add package YamlRecords --version 1.0.0
                    
NuGet\Install-Package YamlRecords -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="YamlRecords" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="YamlRecords" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="YamlRecords" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add YamlRecords --version 1.0.0
                    
#r "nuget: YamlRecords, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package YamlRecords@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=YamlRecords&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=YamlRecords&version=1.0.0
                    
Install as a Cake Tool

YamlRecords

.NET NuGet

A small script that can deserialize and serialize to YAML from dotnet classes; it supports C# 9 records with primary constructors, and can also figure out inheritance with some derived type heuristics.

I built this for a Godot game I was working on, where I wanted to use minimal Record type definitions and inheritance. The incumbent dotnet project for YAML is YamlDotNet but at the time of writing (September 2025) that project, while being vastly more sophisticated and tested than this humble script, had not adapted to Records with primary constructors yet (there are workarounds that include adding parameterless constructors to each record, a bit ugly). Additionally it didn't support inheritance very well when deserializing, a perennial issue with serializers. I needed both.

To use, either use the Nuget package (link above), or just copy YamlRecords.cs into your project somewhere (change namespaces or trim down as needed) - its been built to just use the standard library, no external dependencies.

Note: I built this for my needs, and its possible it won't cover all edge cases - I've tried to make it fairly generic for things like lists and collections, nullable types and enums etc, but don't expect it to be perfect.

Example of use

Note: All of the below steps are performed in YamlRecords.Tests.cs; the only file you need for your own projects is YamlRecords.cs - the Nuget package also excludes all this testing stuff and their dependencies.

Using the record types defined in YamlRecords.Tests.cs, you can define a structure like this:

var test = new GameConfig(new()
{
    { "funds", new CardType("Funds", "res://assets/wealth_icon.png") },
    { "health", new CardType("Health", "res://assets/reputation_icon.png") },
},
new()
{
        { "work", new GameFlow("res://assets/authority_icon.png", "choose_path", new() {
            {
                "choose_path", new SocketState(new ()
                {
                    {"default", new StateVariant("Work", "Choose your path to earn funds", "Start", null)},
                    {"labour", new StateVariant("Work", "Physical work for small pay", "Start", new TransitionAction("labour"))}
                }
                , "default", [
                    new SocketConfig("work", ["reason","health","passion"], new() {
                        { "health", new VariantAction("labour") }
                    })
                ])
            },
            {
                "labour", new TimerState(new ()
                {
                    {"default", new StateVariant("Work", "The day stretches long, your hand's burn", "Running...", null)},
                }
                , "default", 60, null, new TransitionAction("choose_path")) // repeat for test
            },
        }) }
});

Note that this structure includes abstract types and their derived types, as well as record types using exclusively primary constructors.

You can then serialize this with:

var yaml = YamlRecords.Serialize(test);
Console.WriteLine(yaml);

Note the namespace is YamlRecordsSerializer

Which will produce:

cardTypes:
  funds:
    title: Funds
    iconPath: "res://assets/wealth_icon.png"
  health:
    title: Health
    iconPath: "res://assets/reputation_icon.png"
gameFlows:
  work:
    iconPath: "res://assets/authority_icon.png"
    startState: choose_path
    states:
      choose_path:
        sockets:
          - title: work
            accepts:
              - reason
              - health
              - passion
            onAccept:
              health:
                newVariant: labour
        variants:
          default:
            title: Work
            description: Choose your path to earn funds
            actionLabel: Start
            onAction:
          labour:
            title: Work
            description: Physical work for small pay
            actionLabel: Start
            onAction:
              newState: labour
        defaultVariant: default
      labour:
        seconds: 60
        socket:
        onElapsed:
          newState: choose_path
        variants:
          default:
            title: Work
            description: "The day stretches long, your hand's burn"
            actionLabel: Running...
            onAction:
        defaultVariant: default

Which you can then deserialize with:

var test = YamlRecords.Deserialize<GameConfig>(yaml);

And have no issues (re-serialize if necessary, to prove that the populated classes will once again generate identical YAML).

Schemas

The tool can also generate a json schema for a type, complete with support for derived types. For example:

var schema = YamlRecords.GenerateSchema<GameConfig>()

will generate something like:

{
  "type": "object",
  "title": "gameConfig",
  "nullable": true,
  "properties": {
    "cardTypes": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/defs/CardType"
      }
    },
    "gameFlows": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/defs/GameFlow"
      }
    },
    "startingCards": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "startingFlows": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false,
  "defs": {
    "CardType": {
      "type": "object",
      "title": "cardType",
      "nullable": true,
      "properties": {
        "title": {
          "type": "string"
        },
        "iconPath": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "GameFlow": {
      "type": "object",
      "title": "gameFlow",
      "nullable": true,
      "properties": {
        "iconPath": {
          "type": "string"
        },
        "startState": {
          "type": "string"
        },
        "states": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/defs/FlowState"
          }
        }
      },
      "additionalProperties": false
    },
    "FlowState": {
      "type": "object",
      "title": "flowState",
      "nullable": true,
      "properties": {
        "variants": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/defs/StateVariant"
          }
        },
        "defaultVariant": {
          "type": "string"
        }
      },
      "oneOf": [
        {
          "$ref": "#/defs/SocketState"
        },
        {
          "$ref": "#/defs/TimerState"
        },
        {
          "$ref": "#/defs/CardState"
        }
      ]
    },
    "StateVariant": {
      "type": "object",
      "title": "stateVariant",
      "nullable": true,
      "properties": {
        "title": {
          "type": "string"
        },
        "description": {
          "type": "string"
        },
        "actionLabel": {
          "type": "string"
        },
        "onAction": {
          "$ref": "#/defs/StateAction"
        }
      },
      "additionalProperties": false
    },
    "SocketState": {
      "type": "object",
      "title": "socketState",
      "nullable": true,
      "properties": {
        "sockets": {
          "type": "array",
          "items": {
            "$ref": "#/defs/SocketConfig"
          }
        },
        "variants": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/defs/StateVariant"
          }
        },
        "defaultVariant": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "TimerState": {
      "type": "object",
      "title": "timerState",
      "nullable": true,
      "properties": {
        "seconds": {
          "type": "number"
        },
        "socket": {
          "$ref": "#/defs/SocketConfig"
        },
        "onElapsed": {
          "$ref": "#/defs/StateAction"
        },
        "variants": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/defs/StateVariant"
          }
        },
        "defaultVariant": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "CardState": {
      "type": "object",
      "title": "cardState",
      "nullable": true,
      "properties": {
        "newCards": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "variants": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/defs/StateVariant"
          }
        },
        "defaultVariant": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "StateAction": {
      "type": "object",
      "title": "stateAction",
      "nullable": true,
      "oneOf": [
        {
          "$ref": "#/defs/TransitionAction"
        },
        {
          "$ref": "#/defs/VariantAction"
        }
      ]
    },
    "SocketConfig": {
      "type": "object",
      "title": "socketConfig",
      "nullable": true,
      "properties": {
        "title": {
          "type": "string"
        },
        "accepts": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "onAccept": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/defs/StateAction"
          }
        }
      },
      "additionalProperties": false
    },
    "TransitionAction": {
      "type": "object",
      "title": "transitionAction",
      "nullable": true,
      "properties": {
        "newState": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "VariantAction": {
      "type": "object",
      "title": "variantAction",
      "nullable": true,
      "properties": {
        "newVariant": {
          "type": "string"
        }
      },
      "additionalProperties": false
    }
  }
}
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 295 10/4/2025
1.0.0-release202510042122 181 10/4/2025