2

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
    `instanceOfDeserializer.Deserialize(someTextReaderInstance)`? – ProgrammingLlama Jul 28 '21 at 06:47
  • I am aware of that. In this case, "MyBaseClass" would be , a common base class for all items including FuelCanister. But I need to be able to have a list (of any size, with any components, such as Explosive or Burnable) and reference those classes. Not sure which part of the question is unclear? – khaleesarcode Jul 28 '21 at 06:53
  • 1
    It's not clear that you're aware of that from your question. You haven't shown any code at all, and you're talking about using reflection. Perhaps you should directly mention that you're trying to deserialize derived types into a list of their base type, and that that's what is presenting an issue for you. Make that front and centre at the top of your question so that it's clear what you're asking. Also show some code for what you already have. – ProgrammingLlama Jul 28 '21 at 06:57
  • Thank you for the constructive criticism. I wasn't sure how to properly ask what I need. I've added additional information, please inform me if there is anything else that can be improved. – khaleesarcode Jul 28 '21 at 07:06
  • https://stackoverflow.com/questions/51197869/convert-xml-to-json-to-c-sharp-object – A Farmanbar Jul 28 '21 at 07:15
  • @AFarmanbar The link you posted explains JSON/XML and I'm not sure how to convert it to my use case with YAML. – khaleesarcode Jul 28 '21 at 09:42
  • @khaleesarcode save it as XML format with .yaml file extension – A Farmanbar Jul 28 '21 at 09:49
  • In that example, it seems that they are deserializing a single object whose properties are all strings. I need to deserialize an object where some strings need to be dynamically converted into classes and initialized with dynamic parameters. I've updated the question with more information, maybe now it's more clear what I need to do. – khaleesarcode Jul 28 '21 at 10:01

1 Answers1

2

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.

Antoine Aubry
  • 12,203
  • 10
  • 45
  • 74