Context; Using the Unity Engine. JSON files store game data of assets in the game.
I have the below JSON file which stores data about possible Units in the game.
This is a Dictionary<string, UnitEntity>
where UnitEntity
inherits Entity
(which is an abstract class)
The KEY is the name of the Unit The VALUE is the Unit:Entity itself
{
"Infantry": {
"moveSpeed": 2.0,
"turnRateRadians": 2.0,
"unitArmourType": 0,
"unitDamage": 10.0,
"unitAttackCooldownSeconds": 2.0,
"unitAttackAoERange": 0.0,
// List of structs of an enum and float
"damageToArmourTypes": [
{
"armour": 0,
"damageRatio": 1.0
},
{
"armour": 1,
"damageRatio": 0.25
},
{
"armour": 2,
"damageRatio": 0.0
}
],
// Below fields are from the abstract Base class
"id": 0,
"name": "Infantry",
"faction": "USA",
"description": "Basic Infantry of the USA",
"menuLocation": 3,
"menuOrderIndex": -1,
"maxHealth": 50,
"entityPrefabReference": "Assets/Resources/Prefabs/Units/InfantryPrefab.prefab",
// A struct of an enum and int
"constructionCost": [
{
"_resourceType": 0,
"_resourceCount": 250
}
]
}
}
Above is an example of the JSON that is created on Serialize which is what I'd expect (perhaps not the base data being at the bottom half, but if its consistent....sure)
Serialize seems to execute fine. No errors. But when I attempt;
string factionEntityJson = File.ReadAllText(filePath);
Dictionary<string, UnitEntity> entity = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, UnitEntity>>(factionEntityJson); ;
I get the error;
Value cannot be null.
Parameter name: collection
I attempted to instead cast to a Dictionary<string, object>
and whilst it executed, inspecting the contents it said something along the lines of "Method is not implemented" for the JToken.Value (or something along those lines).
What is the issue here (does NewtonSoft.Json not like inheritance / derived types?) and how do I fix it?
EDIT:
The UnitEntity Class as requested:
[Serializable]
public struct ArmourDamageRatio
{
public Unit_Armour_Type armour;
public float damageRatio;
}
[Serializable]
public class UnitEntity : Entity
{
public float moveSpeed = 1f;
public float turnRateRadians = 1f;
public Unit_Armour_Type unitArmourType = Unit_Armour_Type.NONE;
public float unitDamage = 5f;
public float unitAttackCooldownSeconds = 1f;
public float unitAttackAoERange = 0f; // 0 range means no AoE (IE bullets)
public List<ArmourDamageRatio> damageToArmourTypes = new List<ArmourDamageRatio>();
public UnitEntity(int id)
: base(id)
{
}
[JsonConstructor]
public UnitEntity(int id,
string faction,
string name,
string desc,
int menuLoc,
int menuOrder,
string buildingPath,
int maxHp,
List<GameResourcePair> cost,
float moveSpd,
float turnRate,
Unit_Armour_Type armourType,
float dmgAmt,
float attackCooldown,
float aoeRange,
List<ArmourDamageRatio> damageTypes)
: base(id, faction, name, desc, menuLoc, menuOrder, buildingPath, maxHp, cost)
{
this.moveSpeed = moveSpd;
this.turnRateRadians = turnRate;
this.unitArmourType = armourType;
this.unitDamage = dmgAmt;
this.unitAttackCooldownSeconds = attackCooldown;
this.unitAttackAoERange = aoeRange;
this.damageToArmourTypes.AddRange(damageTypes);
}
}
The Entity Base class:
[Serializable]
public abstract class Entity
{
public int id; // Unique ID of entity (Not set by user / dev)
public string name; // Name of the Entity
public string faction; // Faction entity belongs to
public string description; // Description of the Entity
public UI_Menu menuLocation; // Which UI menu will it appear in (Infrastructure, Defence, Barracks etc)
public int menuOrderIndex; // Order entity appears in the menu (-1 indicates not set)
public int maxHealth; // Max health of entity
public string entityPrefabReference; // Entity prefab Object (File path to load at runtime)
public List<GameResourcePair> constructionCost; // List of construction costs of Building Entity
public Entity(int id)
{
this.id = id;
this.name = "";
this.faction = "";
this.description = "";
this.menuLocation = UI_Menu.NONE;
this.menuOrderIndex = -1;
this.maxHealth = 0;
this.entityPrefabReference = null;
this.constructionCost = new List<GameResourcePair>();
}
[JsonConstructor]
public Entity(int id, string faction, string name, string desc, int menuLoc, int menuOrder, string buildingPath, int maxHp, List<GameResourcePair> cost)
{
this.id = id;
this.name = name;
this.faction = faction;
this.description = desc;
this.menuLocation = (UI_Menu)menuLoc;
this.menuOrderIndex = menuOrder;
this.maxHealth = maxHp;
this.constructionCost = cost;
this.entityPrefabReference = buildingPath;
}
// Other functions (==. != overrides etc)
}
CodePudding user response:
Looks like there a lot of mismatches between the serialized json and the expected keys you're looking for in the UnitEntity and Entity classes, for example moveSpd
in the constructor and moveSpeed
in the serialized json.
This is happening since deserialization goes through the constructor and these properties get renamed, but when serializing them you're not doing that renaming back.
The exception you're getting is yet another mismatch, as you're calling addRange(damageTypes)
even though the actual serialized value is damageToArmourTypes
. renaming the constructor parameter fixed the issue.
This might only be my personal opinion here, but you should really try to avoid having your domain entities be the actual classes that get serialized. I think that you're over-relying on serializer magic when you should be making these mappings explicit. I'd refactor to create a class representation of the entities that is a representation of the serialized data and convert most serializer voodoo into explicit code. Yes, it is more verbose and possibly ugly, but you can be certain on what is going on in your code.
CodePudding user response:
you have a bug in your code, change the input parameter name "damageTypes" of UnitEntity constructor to "damageToArmourTypes" and IMHO it is better to add a null validation for the future
[JsonConstructor]
public UnitEntity(int id,
//...another parameters
float aoeRange,
List<ArmourDamageRatio> damageToArmourTypes)
{
// ...another code
if (damageToArmourTypes != null)
this.damageToArmourTypes.AddRange(damageToArmourTypes);
//...
}