Newtonsoft JSON Serialize/Deserialize derived types - c#

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)
}

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);
//...
}

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.

Related

How to append positions when creating a Polyline with Xamarin.Forms.Maps in runtime?

I am creating a Xamarin.Forms.Maps.Polyline in runtime. How can I append positions dynamically, given that the Polyline.Geopath property is read-only?
Createing a polyline in runtime
Following the document to create a polyline : Xamarin.Forms Map Polygons and Polylines. this link is a tutorial with fixed position in the code. How to assign position in runtime dynamically.
using Xamarin.Forms.Maps;
// ...
Map map = new Map
{
// ...
};
Create a object to store routing data (data extracted from json)
public class MapRoute
{
//[JsonPropertyName("timestamp")]
public long timestamp { get; set; }
//[JsonPropertyName("lat")]
public double lat { get; set; }
//[JsonPropertyName("lng")]
public double lng { get; set; }
public MapRoute(long v1, double v2, double v3)
{
timestamp = v1;
lat = v2;
lng = v3;
}
}
Serialize routing object to JsonString.
public void RouteAppend(MapRoute route)
{
JsonString.Append(JsonSerializer.Serialize(route));
JsonString.Append(",");
}
In real life, there are more than 2 elements in jsonString (there are more than 1000 elements which stored in jsonString)
readonly string jsonString = " [ {\"timestamp\": 1514172600000, \"Lat\": 37.33417925, \"Lng\": -122.04153133}, " + "{\"timestamp\": 1514172610000, \"Lat\": 37.33419725, \"Lng\": -122.04151333} ]";
JsonDocument doc;
JsonElement root;
private IList<Position> pos;
doc = Parse(testString);
root = doc.RootElement;
var routes = root.EnumerateArray();
while (routes.MoveNext())
{
var user = routes.Current;
pos.Add(new Position(Convert.ToDouble(user.GetProperty("lat")), Convert.ToDouble(user.GetProperty("lng"))));
}
Finally, there is a list pos with a lot of Position, I would assign the pos to Geopath. Unfortunately, it isn't allowed. It is a readonly property:
// instantiate a polyline
Polyline polyline = new Polyline
{
StrokeColor = Color.Blue,
StrokeWidth = 12,
Geopath = pos // It is a readonly property, cannot assign pos to Geopath
}
// add the polyline to the map's MapElements collection
map.MapElements.Add(polyline);
How can this problem be resolved?
While Polyline.Geopath is a get-only property, the returned IList<Position> is not a read-only collection, so you can add your Position objects to it after construction.
Thus you can create the following factory method:
public static class PolylineFactory
{
const string latitudeJsonName = "Lat";
const string longitudeJsonName = "Lng";
public static Polyline FromLatLngJson(string jsonString, float? strokeWidth = default, Color strokeColor = default)
{
using var doc = JsonDocument.Parse(jsonString);
var query = doc.RootElement.EnumerateArray()
// GetProperty performs property name matching as an ordinal, case-sensitive comparison.
.Select(e => new Position(e.GetProperty(latitudeJsonName).GetDouble(), e.GetProperty(longitudeJsonName).GetDouble()));
var polyline = new Polyline();
if (strokeColor != default)
polyline.StrokeColor = strokeColor;
if (strokeWidth != default)
polyline.StrokeWidth = strokeWidth.Value;
foreach (var position in query)
polyline.Geopath.Add(position);
return polyline;
}
}
Alternatively, in .NET 5 JsonSerializer supports deserializing to objects with a single, parameterized constructor, so if you modify your MapRoute class slightly as follows, you can deserialize your jsonString to a List<MapRoute> directly:
public class MapRoute
{
public long timestamp { get; set; }
public double lat { get; set; }
public double lng { get; set; }
public MapRoute(long timestamp, double lat, double lng)
{
// The constructor argument names and property names must match for JsonSerializer to bind to the constructor successfully
this.timestamp = timestamp;
this.lat = lat;
this.lng = lng;
}
}
public static class PolylineFactory
{
public static Polyline FromLatLngJson(string jsonString, float? strokeWidth = default, Color strokeColor = default)
{
var routes = JsonSerializer.Deserialize<List<MapRoute>>(jsonString, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
var polyline = new Polyline();
if (strokeColor != default)
polyline.StrokeColor = strokeColor;
if (strokeWidth != default)
polyline.StrokeWidth = strokeWidth.Value;
foreach (var route in routes)
polyline.Geopath.Add(new Position(route.lat, route.lng));
return polyline;
}
}
(In .NET Core 3.x you would need to add a parameterless constructor to MapRoute and ensure all properties are mutable to deserialize it successfully.)
Either way, you can call the factory method as follows:
var polyline = PolylineFactory.FromLatLngJson(jsonString, 12, Color.Blue);
Notes:
JsonElement.GetProperty() performs property name matching as an ordinal, case-sensitive comparison, so you need to pass "Lat" and "Lng" rather than "lat" and "lng", since your jsonString is pascal-cased not camel-cased.
If this is a mistake in your question and your JSON string is really camel-cased, modify latitudeJsonName and longitudeJsonName appropriately.
If you need to ignore case when fetching a property see Ignore case when using TryGetProperty.
Convert.ToDouble() interprets the incoming value by using the formatting conventions of the current thread culture. E.g. in many European locales a , comma is used as the decimal separator when formatting floating values. As this is inconsistent with the JSON standard it is better to use the built-in method JsonElement.GetDouble() which will always use the correct decimal separator.
JsonDocument is disposable:
This class utilizes resources from pooled memory to minimize the impact of the garbage collector (GC) in high-usage scenarios. Failure to properly dispose this object will result in the memory not being returned to the pool, which will increase GC impact across various parts of the framework.
In your code you do not dispose of your doc, but you should.
Demo fiddle #1 here using JsonDocument, and #2 here using MapRoute.

How can I use a string to call an object in C#?

I am currently making a simple console based RPG dungeon game, I am still fairly new to C#, I took a visual basic course in school, and I have played around with unity for about a month.
My problem is, that I am programming the first battle that the player will encounter. I have constructed a few weapons, and I want to be able to call the persons current weapon with a separate string, for example, My current weapon is the dagger, or the object wp1, I want my "weapon" to be attached to wp1 in some way, so that I can do something like,
Console.WriteLine("Damage: " + weapon.dmg); rather than hardcoding wp1.dmg, so that later in the game making process, when the player has the opportunity to purchase a better weapon, I can do it with variables, for example, the player now has the shortsword (wp2)
private string weapon = "wp2";
...
Console.WriteLine("Damage: " + weapon.dmg);
I have tried to simply put,
String weapon = wp1;
then call weapon.dmg, but this doesn't work because it thinks i'm trying to call
weapon.dmg, and not wp1.dmg
//players base damage
int damage = 0;
//players strength attribute
int strength = 0;
//weapon constructor
class wp
{
public int dmg;
public int cost;
public string name;
public wp(int d, int c, string n)
{
dmg = d;
cost = c;
name = n;
}
}
//three weapons that are constructed
wp wp1 = new wp(1, 25, "dg");
wp wp2 = new wp(3, 100, "ss");
wp wp3 = new wp(5, 250, "ls");
//the current weapon string
public string weapon = "wp1";
void attack()
{
//calculates damage based off of the players damage, strength, and weapon
int newDamage = damage * strength + (weapon.dmg);
}
Expected result:
the program should use the player's current weapon's damage value
Actual result, the program tries to find the dmg value of the weapon, but that is not possible because "weapon" is just a string, so it throws an error
As I said in a comment above, get into good habits now, as a beginner and you won't have to break those habits later. Let's see how we might design your system. Start off with a well-designed class hierarchy. Let's say we have three kinds of weapons: swords, daggers, and clubs. The stuff they have in common goes into an abstract base class:
abstract class Weapon
{
What do they have in common? Damage, cost, and a name. So make abstract, read-only properties for those:
public abstract int Damage { get; }
public abstract decimal Cost { get; }
public abstract string Name { get; }
}
Now make some derived classes. Are you planning on extending them further? If not, seal them:
sealed class Sword : Weapon
{
public override int Damage => 10;
public override decimal Cost => 12.5m;
public override string Name => "normal sword";
}
And so on.
Now do the same for player. Let's say that we can change a player's weapon, but not their name. So Name should be a read-only property, and Weapon should be a read-write property:
sealed class Player
{
public string Name { get; private set; }
public Weapon Weapon { get; set; }
public int Strength { get; private set; }
public int BaseDamage { get; private set; }
public Player(string name, Weapon weapon, int strength, int baseDamage)
{
this.Name = name;
this.Weapon = weapon;
this.Strength = strength;
this.BaseDamage = baseDamage;
}
}
Now we can make some weapons:
Weapon weapon1 = new Sword();
Weapon weapon2 = new Dagger();
Weapon weapon3 = new Club();
Player fighter = new Player("Bob", weapon3, 5, 10);
Or, better:
var weapons = new List<Weapon> { new Sword(), new Dagger(), new Club() };
// weapons is indexed 0, 1, 2.
Player fighter = new Player("Bob", weapons[2], 5, 10);
And now if you have a Player in hand:
static void Attack(Player p)
{
int damage = p.BaseDamage * p.Strength + p.Weapon.Damage;
string text = $"Player {p.Name} attacks with {p.Weapon.Name}";
No strings for referencing objects! Do not use strings for anything except text. References to objects should be references to objects, not strings.
Now, for advanced players only, there are times when you do need to look something up by a string. The way you do that in C# is:
var d = new Dictionary<string, Weapon> {
{ "weapon1", new Sword() },
{ "weapon2", new Dagger() },
{ "weapon3", new Club() }
};
Weapon w = d["weapon1"]; // w is a Sword.
But do not do this by default. That's not the normal way to refer to something in C#.
one way to achieve what you want:
if(weapon=="wp1")
//use wp1 object here.
But a Better way would be to put your 3 wp's into a list or array (since you literally hardcoded a list of 3 objects). Then wp1 would be at
wpArray[0];

What is the cleanest way to make an immutable class if the class takes many parameters?

I have been reading the book Clean Code and it says that function arguments should not more than 2, anything more than that is confusing for anyone using my function. My question is, how does this apply to immutable classes?. Lets say for example that I have something like the following:
public class Song
{
private readonly string name;
private readonly double price;
private readonly string owner;
private readonly int id;
public string Name{get{return name;}}
public double Price{get{return price;}}
public string Owner{get{return owner;}}
public int Id{get{return id;}}
public Song(string name, double price, string owner, int id)
{
this.name = name;
this.price = price;
this.owner = owner;
this.id = id;
}
}
I have 4 parameters in my constructor and this does not look clean, is there a better way to create an immutable class? or maybe im putting too much thought into it and should not worry about this.
Uncle Bob recommends that functions (methods) should ideally have no arguments. But, when there is a need, keep them under two unless there is a good reason for a third. Any more than three is a code smell.
The reasoning behind this is that your functions should be small and simple. This makes them easy to read and understand (i.e. "clean"). If you have more than three arguments, the method is most likely doing more than one thing. Split it up into separate methods.
That being said, I don't think his advice on limiting the number of parameters applies to class constructors. The constructor isn't supposed to really do anything besides initialize fields or properties.
Constructors have the same name as the class or struct, and they usually initialize the data members of the new object.
There isn't much complexity to simply initializing fields/properties. I'd say your code is still "clean".
Edit: As Eric Lippert pointed out in the comments, you could simplify your code with auto-implemented properties:
public class Song
{
public string Name { get; }
public decimal Price { get; }
public string Owner { get; }
public int Id { get; }
public Song(string name, decimal price, string owner, int id)
{
this.Name = name;
this.Price = price;
this.Owner = owner;
this.Id = id;
}
}
You need to pass in the parameters in the constructor of your immutable class. So if you have a lengthy class, there will be many parameters. You can create a copy-constructor if this feels cleaner, but the instance you pass in will again need to be defined using the lengthy constructor variant, passing in each parameter to map to an instance field or property. We can compact the code using C# 6.0 readonly properties at least too. Give the following code a go in Linqpad and you will see Linqpad inform that we cannot set the song from "Thunderstruck" to "Big Gun", since we declared the property to be readonly. Less ceremony, less code, that is what we like. Perhaps we could do something elegant with expression trees to perhaps compact the code even more? This is my suggestion at least.
void Main()
{
var song = new Song("Thunderstruck", 15, "AC/DC Records",
123
);
var songAwesome = new Song(song);
songAwesome.Name.Dump();
songAwesome.Name = "Big Gun"; //This line fails because the property is redonly
}
public class Song
{
public string Name { get; } //readonly props in C# 6.0
public double Price { get;}
public string Owner { get; }
public int Id { get; }
public Song(Song song) : this(song.Name, song.Price,
song.Owner, song.Id){
} //Let us have a copy constructor just for the fun of it
public Song(string name, double price, string owner, int id)
{
this.Name = name;
this.Price = price;
this.Owner = owner;
this.Id = id;
}
}
// Define other methods and classes here

Know when variables inside a struct become null in Visual Studio?

I have a struct on the server-side with a layout like this:
struct SomeStruct
{
public string SomeString { get; set; };
public string SomeString1;
public string SomeString2;
public string SomeString3;
}
I am using a client/server model, and an instance of this struct gets referenced a lot of times as it has really important information (over 200 times).
The thing is that when some function gets called, the values inside this struct become null. I don't know why and it is been bugging me for a really long time.
I call a lot of methods before realizing that this values are null, so I don't know which section of my code nullify my strings.
I am using VS2012, but I have 2010 and 2008 ultimate as well. I was wondering if there is a way to perform a trigger when some section of code nullifies my strings.
I tried to add some properties like this, bot the exception was never thrown:
struct SomeStruct {
string somestr;
public string SomeString
{
get { return somestr; }
set
{
if (value == null)
{
throw new Exception("stirng is null");
}
somestr = value;
}
}
public string SomeString1;
public string SomeString2;
public string SomeString3;
}
Might not be important, but this is one of the structs I am using (the Name variable becomes null in some part of my code, and the rest turns into default()):
[ProtoContract]
public struct CharacterInformation
{
[ProtoMember(2)]
public string Name;
[ProtoMember(3)]
public IntegerVector2 Position;
[ProtoMember(5)]
public CharacterDirection Direction;
[ProtoMember(6)]
public CharacterStatus Status;
[ProtoMember(7)]
public CharacterClass Class;
[ProtoMember(8)]
public CharacterRace Race;
[ProtoMember(9)]
public CharacterType Type;
[ProtoMember(10)]
public CharacterFaction Faction;
[ProtoMember(11)]
public float MovementModifier;
[ProtoMember(12)]
public CharacterEquipment Equipment;
}
Edit: The only instance of this struct is created on a Sql-related function:
public CharacterServerInformation GetInformation(int charID)
{
CharacterServerInformation information = new CharacterServerInformation();
if (!authInstance.CharacterExists(charID))
{
// char doesn't exists
throw new Exception("Character doesn't exists");
}
information.ID = charID;
information.Experience = GetExperience(charID);
information.Info.Direction = CharacterDirection.Bottom;
information.Info.Name = authInstance.CharacterGetName(charID);
information.Info.Class = GetClass(charID);
information.Info.Faction = GetFaction(charID);
information.Info.Position = GetPosition(charID);
information.Info.Race = GetRace(charID);
information.Info.Status = GetStatus(charID);
information.Info.Type = GetType(charID);
information.Info.MovementModifier = 1f; // should store old movement modifier, but well, whatever
information.HealthLeft = GetHealthLastLogout(charID);
return information;
}
I suspect the problem is purely because you're using struct and not making a class. Since struct members are copied by value into methods and when returned from methods, including property getters, it's likely that you're "losing" the information by accidentally writing a new struct somewhere.
In this case, class seems is far more appropriate. If you read Choosing Between Classes and Structures, you'll see that struct should only be used when:
It logically represents a single value, similar to primitive types (integer, double, and so on).
It has an instance size smaller than 16 bytes.
It is immutable.
It will not have to be boxed frequently.
In your case, all of these criteria ( except maybe the last) are being violated, so class would be more appropriate.

Passing a type as parameter to an attribute

I wrote a somewhat generic deserialization mechanism that allows me to construct objects from a binary file format used by a C++ application.
To keep things clean and easy to change, I made a Field class that extends Attribute, is constructed with Field(int offset, string type, int length, int padding) and is applied to the class attributes I wish to deserialize. This is how it looks like :
[Field(0x04, "int")]
public int ID = 0;
[Field(0x08, "string", 0x48)]
public string Name = "0";
[Field(0x6C, "byte", 3)]
public byte[] Color = { 0, 0, 0 };
[Field(0x70, "int")]
public int BackgroundSoundEffect = 0;
[Field(0x74, "byte", 3)]
public byte[] BackgroundColor = { 0, 0, 0 };
[Field(0x78, "byte", 3)]
public byte[] BackgroundLightPower = { 0, 0, 0 };
[Field(0x7C, "float", 3)]
public float[] BackgroundLightAngle = { 0.0f, 0.0f, 0.0f };
Calling myClass.Decompile(pathToBinaryFile) will then extract the data from the file, reading the proper types and sizes at the proper offsets.
However, I find that passing the type name as a string is ugly.
Is it possible to pass the type in a more elegant yet short way, and how ?
Thank you.
Use the typeof operator (returns an instance of Type):
[Field(0x7C, typeof(float), 3)]
Yes: make the attribute take a Type as a parameter, and then pass e.g. typeof(int).
Yes, the parameter must be of type Type and then you can pass the type as follows:
[Field(0x7C, typeof(float), 3)]
public float[] BackgroundLightAngle = { 0.0f, 0.0f, 0.0f };
I don't think you need to put the type in the constructor of the attribute, you can get it from the field. See the example:
public class FieldAttribute : Attribute { }
class Data
{
[Field]
public int Num;
[Field]
public string Name;
public decimal NonField;
}
class Deserializer
{
public static void Deserialize(object data)
{
var fields = data.GetType().GetFields();
foreach (var field in fields)
{
Type t = field.FieldType;
FieldAttribute attr = field.GetCustomAttributes(false)
.Where(x => x is FieldAttribute)
.FirstOrDefault() as FieldAttribute;
if (attr == null) return;
//now you have the type and the attribute
//and even the field with its value
}
}
}
With C# 10 there is a new feature (in preview at time of this post) that allows you to create generic attributes!
Using this new feature, you can create a generic attribute instead:
public class GenericAttribute<T> : Attribute { }
Then, specify the type parameter to use the attribute:
[GenericAttribute<string>()]
public string Method() => default;

Categories