How to deserialize YAML property into a class with passed parameters?

Viewed 682

I'm trying to achieve a functionality implemented in the game "RimWorld" with XML, using YamlDotNet.

I am aware of how to deserialize basic information, like so: instanceOfDeserializer.Deserialize<MyBaseClass>(someTextReaderInstance)

I am trying to deserialize a list of derived types into a list of their base types.

The goal is to be able to take a string as a class name, and instantiate it with parameters listed in the YAML. Here's how it's done in XML:

<ThingDef ParentName="ResourceBase">
    <defName>Chemfuel</defName>
    <label>chemfuel</label>
    <description>A volatile liquid chemical. Used to fuel engines and rockets, or to transmute into propellant for projectiles, or as an incendiary weapon.</description>
    <stackLimit>150</stackLimit>
    <comps>
      <li Class="CompProperties_Explosive">
        <explosiveRadius>1.1</explosiveRadius>
        <explosiveDamageType>Flame</explosiveDamageType>
        <explosiveExpandPerStackcount>0.037</explosiveExpandPerStackcount>
        <startWickOnDamageTaken>
          <li>Flame</li>
        </startWickOnDamageTaken>
        <startWickHitPointsPercent>0.333</startWickHitPointsPercent>
        <preExplosionSpawnThingDef>Filth_Fuel</preExplosionSpawnThingDef>
        <preExplosionSpawnChance>1</preExplosionSpawnChance>
        <wickTicks>70~150</wickTicks>
      </li>
    </comps>
  </ThingDef>

The comps list is a list of outside classes I'd like to reference. In this case, we want to add an explosive property to an item chemfuel. It seems like in XML, the developer referenced the wanted class with the Class="CompProperties_Explosive" tag. I'd need similar functionality in a YAML document.

Here's how I envisioned this in my YAML document:

Type: Item
Name: fuel_cansiter
DisplayName: Fuel Canister
Sprite: fuel_canister_1
MaxStackSize: 10
MaxHP: 15
Value: 30
Components:
      - Explosive:
        - Damage: 10
        - Range: 25
      - Burnable:
        - FlameSize: 10
        - HealthThreshold: 0.4

I'd like to instantiate this FuelCanister item with the Explosive.cs and Burnable.cs classes as components, but pass the dynamic variables such as damage and range via YAML.

In this case, the derived types are Explosive and Burnable, which should all be able to be deserialized into their base type, e.g., Component.

In the end, The FuelCanister.cs class should end up with a list of Component types, from which Explosive and Burnable are derived from.

Is there a built-in function in YamlDotNet to easily do this?

EDIT: Perhaps the XML way uses XML attributes to extract the name of the class, and then with that name, we can use Reflection to instantiate the class with passed parameters? Is there a way to do this with YAML?

1 Answers

In YAML, you usually specify types using tags. With tags, your YAML could look like this:

!Item
Name: fuel_cansiter
DisplayName: Fuel Canister
Sprite: fuel_canister_1
MaxStackSize: 10
MaxHP: 15
Value: 30
Components:
      - !Explosive
        Damage: 10
        Range: 25
      - !Burnable
        FlameSize: 10
        HealthThreshold: 0.4

In this document, !Item, !Explosive and !Burnable are tags. It is up to you to define what type they map to. Using the DeserializerBuilder class you can associate a tag with a .NET type like this:

var deserializer = new DeserializerBuilder()
    .WithTagMapping("!Item", typeof(Item))
    .WithTagMapping("!Explosive", typeof(Explosive))
    .WithTagMapping("!Burnable", typeof(Burnable))
    .Build();

This assumes that you have the following classes:

class Item
{
    public string Name { get; set; }
    public string DisplayName { get; set; }
    public string Sprite { get; set; }
    public int MaxStackSize { get; set; }
    public int MaxHP { get; set; }
    public int Value { get; set; }
    public List<Component> Components { get; set; }
}

abstract class Component {}

class Explosive : Component
{
    public int Damage { get; set; }
    public int Range { get; set; }
}

class Burnable : Component
{
    public int FlameSize { get; set; }
    public double HealthThreshold { get; set; }
}

Once you have this, you can deserialize like this:

var item = deserializer.Deserialize<object>(yaml);

In this case I used object because I assume that you may want your document to contain different object types. If you know beforehand that the document contains an Item, you can remove the !Item tag and use this:

var item = deserializer.Deserialize<Item>(yaml);

You can find some documentation about the WithTagMapping method in YamlDotNet's wiki.

Related