-1

I've been trying to write a script to reformat some large .json files, and had a functional prototype. However, when implementing functionality to parse the actions, reactions, legendary_actions, and special_abilities arrays, I got a System.ArgumentNullException: 'Value cannot be null. (Parameter 'method')' exception when trying to deserialize.

This is the deserialization method:

static List<MonsterOld> TestClassObject()
        {
            string fileName = @"S:\VTT SIM\VTTsim\Assets\Database json files\Monsters(SRD).json";
            if (File.Exists(fileName))
            {
                var monsters = JsonConvert.DeserializeObject<List<MonsterOld>>(File.ReadAllText(fileName));

                return monsters;
            }
            return (null);
        }

An example .json item:

{
    "slug": "scout",
    "name": "Scout",
    "size": "Medium",
    "type": "humanoid",
    "subtype": "any race",
    "group": null,
    "alignment": "any alignment",
    "armor_class": 13,
    "armor_desc": "leather armor",
    "hit_points": 16,
    "hit_dice": "3d8+3",
    "speed": { "walk": 30 },
    "strength": 11,
    "dexterity": 14,
    "constitution": 12,
    "intelligence": 11,
    "wisdom": 13,
    "charisma": 11,
    "strength_save": null,
    "dexterity_save": null,
    "constitution_save": null,
    "intelligence_save": null,
    "wisdom_save": null,
    "charisma_save": null,
    "perception": 5,
    "skills": {
      "nature": 4,
      "perception": 5,
      "stealth": 6,
      "survival": 5
    },
    "damage_vulnerabilities": "",
    "damage_resistances": "",
    "damage_immunities": "",
    "condition_immunities": "",
    "senses": "passive Perception 15",
    "languages": "any one language (usually Common)",
    "challenge_rating": "1/2",
    "cr": 0.5,
    "actions": [
      {
        "name": "Multiattack",
        "desc": "The scout makes two melee attacks or two ranged attacks."
      },
      {
        "name": "Shortsword",
        "desc": "Melee Weapon Attack: +4 to hit, reach 5 ft., one target. Hit: 5 (1d6 + 2) piercing damage.",
        "attack_bonus": 4,
        "damage_dice": "1d6",
        "damage_bonus": 2
      },
      {
        "name": "Longbow",
        "desc": "Ranged Weapon Attack: +4 to hit, range 150/600 ft., one target. Hit: 6 (1d8 + 2) piercing damage.",
        "attack_bonus": 4,
        "damage_dice": "1d8",
        "damage_bonus": 2
      }
    ],
    "reactions": "",
    "legendary_desc": "",
    "legendary_actions": "",
    "special_abilities": [
      {
        "name": "Keen Hearing and Sight",
        "desc": "The scout has advantage on Wisdom (Perception) checks that rely on hearing or sight."
      }
    ],
  }

And the class I'm trying to deserialize the problem arrays into:

    public class MonsterOld
    {
        public string? name;
        public string? size;
        public string? type;
        public string? subtype;
        public string? alignment;
        public int armor_class;
        public string? armor_desc;
        public int hit_points;
        public string? hit_dice;
        public Speed? speed;
        public int? strength;
        public int? dexterity;
        public int? constitution;
        public int? intelligence;
        public int? wisdom;
        public int? charisma;
        public int? strength_save;
        public int? dexterity_save;
        public int? constitution_save;
        public int? intelligence_save;
        public int? wisdom_save;
        public int? charisma_save;
        public int? perception;
        public Skills? skills;
        public string? damage_vulnerabilities;
        public string? damage_resistances;
        public string? damage_immunities;
        public string? condition_immunities;
        public string? senses;
        public string? languages;
        public float cr;

        public List<Action>? actions ;
        public List<Action>? reactions ;
        public string? legendary_desc;
        public List<Action>? legendary_actions;
        public List<Action>? special_abilities;
    }

    [Serializable]
    public class Actions
    {
        public string? name;
        public string? desc;
        public string? damage_dice;
        public int? attack_bonus;
        public int? damage_bonus;
    }

I'm still very new to C#, so simpler explanations or answers would be very much appreciated.

  • The following may be of interest: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/ignore-properties?pivots=dotnet-7-0 and https://stackoverflow.com/questions/6507889/how-to-ignore-a-property-in-class-if-null-using-json-net – Tu deschizi eu inchid Jul 20 '23 at 16:12
  • 1
    The problem with self made json classes is that you may miss something. If you are using Visual Studio I would advice using the "Paste json as classes" option, so useful. That being said, the first line in your json says `"slug": "scout"` and I see nohing named `slug` in your `MonsterOld` class. – Cleptus Jul 20 '23 at 16:20
  • 1) You have a class `Actions` but you declare `public List? legendary_actions;` -- note `Action` is missing a trailing **`s`** so you seem to be actually declaring a list of [`System.Action`](https://learn.microsoft.com/en-us/dotnet/api/system.action?view=net-7.0). That's not going to work. – dbc Jul 20 '23 at 16:30
  • 2) You declare `public List? legendary_actions;` as a list but in the JSON the value is a string: `"legendary_actions": "",`. So you need to declare it as a string. – dbc Jul 20 '23 at 16:33
  • 3
    Use one of the tools from [How to auto-generate a C# class file from a JSON string](https://stackoverflow.com/q/21611674/3744182) to auto-generate correct classes, then re-test. – dbc Jul 20 '23 at 16:35

1 Answers1

0

In a case like this, I would start looking at the json file and the classes that you are using to deserialize your object. Then I would add error handling to the code that you are using making sure that logging is also included.

Important Note: Before making any of the changes that I am suggesting, I would make sure that you read the previously mentioned articles.

I would rewrite the test method and MonsterOld class that you are using as follows:


static List<MonsterOld> TestClassObject()
{
    var fileName = @"S:\VTT SIM\VTTsim\Assets\Database json files\Monsters(SRD).json";

    try
    {
        if (File.Exists(fileName))
        {
            var fileContent = File.ReadAllText(fileName);
            var monsters = JsonConvert.DeserializeObject<List<MonsterOld>>(fileContent);

            return monsters;
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"An error occurred while reading or deserializing the file: {ex.Message}");
    }

    return null;
}

//  I have not tested this code but it seems that your json file is not matching the structure of the MonsterOld
public class MonsterOld
{
    public string slug { get; set; }
    public string name { get; set; }
    public string size { get; set; }
    public string type { get; set; }
    public string subtype { get; set; }
    public string group { get; set; }
    public string alignment { get; set; }
    public int armor_class { get; set; }
    public string armor_desc { get; set; }
    public int hit_points { get; set; }
    public string hit_dice { get; set; }
    public Speed speed { get; set; }
    public int? strength { get; set; }
    public int? dexterity { get; set; }
    public int? constitution { get; set; }
    public int? intelligence { get; set; }
    public int? wisdom { get; set; }
    public int? charisma { get; set; }
    public int? strength_save { get; set; }
    public int? dexterity_save { get; set; }
    public int? constitution_save { get; set; }
    public int? intelligence_save { get; set; }
    public int? wisdom_save { get; set; }
    public int? charisma_save { get; set; }
    public int? perception { get; set; }
    public Skills skills { get; set; }
    public string damage_vulnerabilities { get; set; }
    public string damage_resistances { get; set; }
    public string damage_immunities { get; set; }
    public string condition_immunities { get; set; }
    public string senses { get; set; }
    public string languages { get; set; }
    public string challenge_rating { get; set; }
    public float cr { get; set; }
    public List<Action> actions { get; set; }
    public List<Action> reactions { get; set; }
    public string legendary_desc { get; set; }
    public List<Action> legendary_actions { get; set; }
    public List<Action> special_abilities { get; set; }
}

public class Speed
{
    public int walk { get; set; }
}

public class Skills
{
    public int nature { get; set; }
    public int perception { get; set; }
    public int stealth { get; set; }
    public int survival { get; set; }
}

public class Action
{
    public string name { get; set; }
    public string desc { get; set; }
    public int? attack_bonus { get; set; }
    public string damage_dice { get; set; }
    public int? damage_bonus { get; set; }
}

I am also learning so I am more than happy to learn from the community as well.

Happy coding!

  • Some advice about automatically generating the classes or using external tools would be useful in your answer. – Cleptus Jul 20 '23 at 18:08
  • I agree with you but I noticed that there was some advice already posted already about automatically generating classes or using external tools. The code that I provided was generated by hand. My reasoning for that is that I get to avoid depending on external tools and forces me to understand what I am doing. –  Jul 20 '23 at 18:39
  • Understanding the structure is important, but that can also be achieved reviewing the generated code – Cleptus Jul 21 '23 at 07:31
  • Doing things manually, as I've pointed out, can provide a deeper understanding of what's going on, and can also help to avoid unnecessary dependencies on external tools. This approach often leads to better learning, especially for beginners, as it requires a thorough understanding of the underlying structures and processes. On the other hand, using tools or automation can save a lot of time and effort, especially when dealing with large or complex tasks. It can also help to avoid mistakes that might be easy to make when doing things manually. Both approaches have their place. –  Jul 21 '23 at 07:42