How do we retain polymorphism after serializing an object to Json - c#

We have a complex issue in our system regarding retaining polymorphism after we have saved a config file to the database.
We have simplified the problem for the sake of this question. See code below.
Here is the code:
class Wheel
{
public virtual string GetWidth()
{
return "Normal";
}
}
class SmallWheel : Wheel
{
public override string GetWidth()
{
return "Small";
}
}
class BigWheel : Wheel
{
public override string GetWidth()
{
return "Big";
}
}
public static async Task Main(string[] args)
{
//Build list of wheels
var wheels = new List<Wheel>()
{
new Wheel(),
new SmallWheel(),
new BigWheel(),
};
//We print wheels to check it works
foreach (var x in wheels)
{
Console.WriteLine(x.GetWidth());
}
//Save the list to the db as a string
var file = JsonConvert.SerializeObject(wheels);
//We just use file instead of db for simplictiydf
//Later we read the config file from the DB
var wheelsFromDb = JsonConvert.DeserializeObject<List<Wheel>>(file);
//We now want to print out our wheels.
Console.WriteLine("Printing WHeels from Db");
foreach (var x in wheelsFromDb)
{
Console.WriteLine(x.GetWidth());
}
}
Results when I run it:
Normal
Small
Big
Printing WHeels from Db
Normal
Normal
Normal
Now as you can see we are losing what type the wheel is after de-serialisation.
How can we go about solving this issue?
In essence, we need to store a config file with multiple children classes each with overrides functions. I realise that when Json deserializes the raw string it has no context of what type the class was before. Is there a way we can store that context or use a different library or database?

I am using this code for a list of derived classes. You can use a base class instead of an interface as well
IAnimal[] animals = new IAnimal[] {
new Cat{CatName="Tom"},
new Dog{DogName="Scoopy"},
new Rabbit{RabitName="Honey"}
};
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
var json = JsonConvert.SerializeObject(animals, jsonSerializerSettings);
List<IAnimal> animalsBack = ((JArray)JsonConvert.DeserializeObject(json)).Select(o => (IAnimal)JsonConvert.DeserializeObject(o.ToString(), Type.GetType((string)o["$type"]))).ToList();
classes
public interface IAnimal
{
}
public class Animal : IAnimal
{
}
public class Cat : IAnimal { public string CatName { get; set; } }
public class Dog : IAnimal { public string DogName { get; set; } }
public class Rabbit : IAnimal { public string RabitName { get; set; } }

JsonConvert creates property $type under the hood when TypeNameHandling.All is used.
So we can give a clue to compiler about what type actually is by creating Size property in classes:
public class Wheel
{
public virtual string Size { get; set; } = WheelSize.NORMAL;
public virtual string GetWidth() => Size;
}
public class SmallWheel : Wheel
{
public override string Size { get; set; } = WheelSize.SMALL;
}
public class BigWheel : Wheel
{
override public string Size { get; set; } = WheelSize.BIG;
}
This is a class which contains size of wheels:
public class WheelSize
{
public static string SMALL = "small";
public static string NORMAL = "normal";
public static string BIG = "big";
}
So far, so good. But how we can deserialize json based on size wheel? We can write custom deserializer:
internal class WheelJsonConverter : JsonConverter
{
private readonly Type[] _types;
public WheelJsonConverter(params Type[] types)
{
_types = types;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
List<Wheel> wheels = new List<Wheel>();
WheelToSize wheelToSize = new WheelToSize();
JArray jArray = JArray.Load(reader);
foreach (JToken token in jArray)
{
string wheelSize = token["size"].ToString();
Wheel wheel = wheelToSize.WheelBySize[wheelSize];
wheels.Add(wheel);
}
return wheels;
}
public override bool CanConvert(Type objectType)
{
if (objectType.IsGenericType
&& IsGenericTypeArgumentTheSame(objectType))
return true;
return false;
}
private bool IsGenericTypeArgumentTheSame(Type objectType) =>
objectType.GenericTypeArguments[0] == _types[0];
public override void WriteJson(JsonWriter writer, object? value,
JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
o.WriteTo(writer);
}
}
}
This is a class to create json in lower case:
public class LowercaseContractResolver : DefaultContractResolver
{
protected override string ResolvePropertyName(string propertyName)
{
return propertyName.ToLower();
}
}
This is a factory which creates an object based on size:
public class WheelToSize
{
public Dictionary<string, Wheel> WheelBySize { get; private set; } = new()
{
{ WheelSize.NORMAL, new Wheel() },
{ WheelSize.BIG, new BigWheel() },
{ WheelSize.SMALL, new SmallWheel() }
};
}
And then you can run code like this:
List<Wheel> wheels = new List<Wheel>()
{
new Wheel(),
new SmallWheel(),
new BigWheel(),
};
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new LowercaseContractResolver();
var file = JsonConvert.SerializeObject(wheels, Formatting.Indented, settings);
var wheelsFromDb = JsonConvert.DeserializeObject<List<Wheel>>(file,
new WheelJsonConverter(typeof(Wheel)));
//We now want to print out our wheels.
Console.WriteLine("Printing WHeels from Db");
foreach (var x in wheelsFromDb)
Console.WriteLine(x.GetWidth());

Related

Deserialize an abstract class that has only a getter using Newtonsoft

I'm trying to deseralize JSON I'm getting:
[
{
"Name":"0",
"Health":0,
"TypeName":"SpellInfo",
"Info":{
"Effect":1,
"EffectAmount":4
}
},
{
"Name":"1",
"Health":0,
"TypeName":"MonsterInfo",
"Info":{
"Health":10,
"AttackDamage":10
}
},
...
...
]
Created a class to handle the JSON:
[System.Serializable]
public class CardDataStructure
{
public string Name;
public int Health;
public string TypeName;
public Info Info;
}
I managed to get all the info I needed but the Info. From the research I did, I created a JsonConverter from a link - https://blog.codeinside.eu/2015/03/30/json-dotnet-deserialize-to-abstract-class-or-interface/
Which is actually pretty close,
public class InfoConvert: JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Info));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
if (jo.ToString().Contains("Effect"))
{
if (jo["Effect"].Value<string>() is string)
return jo.ToObject<SpellInfo>(serializer);
}
if (jo.ToString().Contains("Health"))
{
if (jo["Health"].Value<string>() is string)
return jo.ToObject<MonsterInfo>(serializer);
}
return null;
}
}
(It would have been better to find it by 'typename' but I couldn't figure out how to do that, so went with something simple)
When checking 'jo', the properties are there and go to the correct class yet once out of the converter I get default properties and not the once the converter showed.
I can't find the link but on the Newtonsoft doc it said somewhere there's a problem with deserializing an abstract class and if the abstract class doesn't have a public setter.
Both monsterinfo and spellinfo inherit from info:
[Serializable]
public abstract class Info
{
}
The monsterinfo and spellinfo look basically the same. Problem is they don't have a public setters and I cannot change them right now.
{
[Serializable]
public class MonsterInfo: Info
{
[SerializeField]
private int m_Health;
public int Health => m_Health;
[SerializeField]
private int m_AttackDamage;
public int AttackDamage => m_AttackDamage;
}
}
So, when trying to deseralize the JSON:
string contents = File.ReadAllText(source);
contents = "{\"cards\":" + contents + "}";
JsonConverter[] converters = { new InfoConvert() };
cardsData = JsonConvert.DeserializeObject<Cards>(contents, new JsonSerializerSettings() {
Converters = converters, NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.Auto});
*Cards is a list of CardDataStructure
Is it even possible to get the data in Info without giving them a public setter?
Best I got is all the data inside the JSON and an empty Monster/Spell Info.
At the end I just need to parse the json I'm getting, but while the 'name', 'health', 'typeinfo' are parsed correctly, info is always an empty object filled with 0s.
Edit: Corrected some things.
You should do that like this dude:
A marker interface for detecting the type or deserializing
A container class
Dto classes
//marker interface
public interface Info { }
public class HealthInfo : Info
{
public int MoreHealth { set; get; }
public int AttackDamage { set; get; }
}
public class SpellInfo : Info
{
public int Effect { set; get; }
public int EffectAmount { set; get; }
}
public class Card<T> where T : Info
{
public Card(string name, int health, T info)
{
this.Info = info;
this.Name = name;
this.Health = health;
}
public T Info { private set; get; }
public string Name { set; get; }
public int Health { set; get; }
}
public class InfoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (objectType == typeof(Card<Info>))
{
return true;
}
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
if (jObject.ContainsKey("TypeName"))
{
string typeName = jObject["TypeName"]?.ToString()?.Trim()?.ToLower();
if (typeName?.Equals("monsterinfo") == true)
{
Card<HealthInfo> deseerialized = jObject.ToObject<Card<HealthInfo>>();
return new Card<Info>(deseerialized.Name, deseerialized.Health, deseerialized.Info);
}
if (typeName?.Equals("spellinfo") == true)
{
string json = jObject.ToString();
Card<SpellInfo> deseerialized = jObject.ToObject<Card<SpellInfo>>();
return new Card<Info>(deseerialized.Name, deseerialized.Health, deseerialized.Info);
}
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And you should execute:
List<Card<Info>> list = JsonConvert.DeserializeObject<List<Card<Info>>>(jsonText, new InfoConverter());

C# Convert Json to object with duplicated property name with different data type

i have a json string with duplicated property name with different data types. if i removed all duplicated other data types, it will work fine.
{
"data":[
{
"ctr":0,
"spend":11839.8600,
"clicks":6402
},
{
"ctr":0,
"spend":12320.5000,
"clicks":5789
},
{
"clicks":{
"value":13156.0,
"prior_year":0.0,
"prior_month":14122.0,
"prior_month_perc":0.0684039087947882736156351792,
"prior_year_perc":0.0
}
}
],
"timing":null,
"warnings":[
],
"success":true
}
here is the my model class
public class MyTestModel
{
public int? ctr { get; set; }
public decimal? spend { get; set; }
public int? clicks { get; set; }
}
if i removed this snipped json part, program will work.
{
"clicks":{
"value":13156.0,
"prior_year":0.0,
"prior_month":14122.0,
"prior_month_perc":0.0684039087947882736156351792,
"prior_year_perc":0.0
}
are there any method to stop binding unsupported types to model property.
You can use a JsonConverter to support either/both types in a 'union' class.
public partial class ClicksClass
{
[JsonProperty("value")]
public long Value { get; set; }
[JsonProperty("prior_year")]
public long PriorYear { get; set; }
[JsonProperty("prior_month")]
public long PriorMonth { get; set; }
[JsonProperty("prior_month_perc")]
public double PriorMonthPerc { get; set; }
[JsonProperty("prior_year_perc")]
public long PriorYearPerc { get; set; }
}
public partial struct ClicksUnion
{
public ClicksClass ClicksClass;
public long? Integer;
public static implicit operator ClicksUnion(ClicksClass ClicksClass) => new ClicksUnion { ClicksClass = ClicksClass };
public static implicit operator ClicksUnion(long Integer) => new ClicksUnion { Integer = Integer };
}
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
ClicksUnionConverter.Singleton,
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
internal class ClicksUnionConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(ClicksUnion) || t == typeof(ClicksUnion?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.Integer:
var integerValue = serializer.Deserialize<long>(reader);
return new ClicksUnion { Integer = integerValue };
case JsonToken.StartObject:
var objectValue = serializer.Deserialize<ClicksClass>(reader);
return new ClicksUnion { ClicksClass = objectValue };
}
throw new Exception("Cannot unmarshal type ClicksUnion");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (ClicksUnion)untypedValue;
if (value.Integer != null)
{
serializer.Serialize(writer, value.Integer.Value);
return;
}
if (value.ClicksClass != null)
{
serializer.Serialize(writer, value.ClicksClass);
return;
}
throw new Exception("Cannot marshal type ClicksUnion");
}
public static readonly ClicksUnionConverter Singleton = new ClicksUnionConverter();
}
This means that, once parsed, whenever you get to the ClickUnion instances, you can check which field is not null, and that is the one you use. It depends on your usecase of course.
BTW, I used this web site to write the classes from the json
I assume you are using JsonConvert.DeserializeObject to deserialize your json string.
While doing so, the data type missmatch causes error and you can safely handle this error using JsonSerializerSettings as below
MyTestModel result = JsonConvert.DeserializeObject<MyTestModel>("{json string}",
new JsonSerializerSettings {
Error = MethodToHandleError
});
and then define MethodToHandleError something like below. This will be called whenever deserialization error occurs:
public void MethodToHandleError(object sender, ErrorEventArgs args)
{
// log error message -> args.ErrorContext.Error.Message
// you can also write your own logic here
args.ErrorContext.Handled = true;
}

Override asp.net core Newtonsoft.Json, to be able to handle inheritance [duplicate]

This question already has answers here:
Deserializing polymorphic json classes without type information using json.net
(6 answers)
Closed 5 years ago.
Consider next code snippet:
static void Main(string[] args)
{
var classes = new Classes()
{
Instances = new A[]{
new B
{
BirthDate = DateTime.Now,
Name = "B1",
SomethingElse = "Test"
},
new C
{
Name = "C1",
SomethingElse1 = "Test2",
SomethingElse2 = "Test3",
}
}
};
var serialized = JsonConvert.SerializeObject(classes);
var deserialized = JsonConvert.DeserializeObject<Classes>(serialized);
}
}
public class Classes
{
public A[] Instances { get; set; }
}
public enum ClassType
{
B = 1,
C = 2
}
public class A
{
public string Name { get; set; }
public virtual ClassType ClassType { get; }
}
public class B : A
{
public string SomethingElse { get; set; }
public DateTime BirthDate { get; set; }
public override ClassType ClassType => ClassType.B;
}
public class C : A
{
public string SomethingElse1 { get; set; }
public string SomethingElse2 { get; set; }
public override ClassType ClassType => ClassType.C;
}
I need to inject my own logic into the process how deserializer handle classes with inheritance. In that case I want to make decision based on ClassType property in JSON. Any ideas/hints how to do it?
BTW. I know that I can use feature of newtonsoft.json TypeNameHandling = TypeNameHandling.All, however I cannot control serialization process since the data is sent from 3rd party system. The only thing that I have control over is deserialization part.
Since you can't use TypeNameHandling, you'd have to parse it first, find the type, and then deserialize.
Like so:
var jObj = Newtonsoft.Json.Linq.JObject.Parse(serialized);
var instances = jObj["Instances"].AsJEnumerable();
var myCol = new List<A>();
myCol.AddRange(instances.Select(x => (x["ClassType"] as JToken)
.ToObject<ClassType>() == ClassType.B ?
(x as JObject).ToObject<B>() :
(x as JObject).ToObject<C>());
Thanks to #zaitsman and #DavidG I came-up with solution that works for me. Here it is:
public class AClassConverter : JsonConverter
{
private readonly Type[] _types;
public AClassConverter(params Type[] types)
{
_types = types;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var jObj = JObject.Load(reader);
var classType = jObj["ClassType"].ToObject<ClassType>();
return classType == ClassType.B ?
(A)jObj.ToObject<B>() :
(A)jObj.ToObject<C>();
}
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
}
and while deserializing:
var deserialized = JsonConvert.DeserializeObject<Classes>(serialized, new AClassConverter(typeof(A)));

JSON serialization with custom mapping in C#

I want to serialize/deserialize following classes into/from JSON:
public class Employee
{
string name;
Position position;
}
public class Position
{
string positionName;
int salary;
}
The tricky part is that I want to treat Position fields as Employee fields, so JSON would look like this:
{
"name": "John",
"positionName": "Manager",
"salary" : 1000
}
How to achieve this using Json.NET ?
You have either to deserialize it as anonymous object either (recommended ) implement a custom deserialization as stated here:
Merge two objects during serialization using json.net?
Please let us know if there any more questions.
Here's an example (you can find it in the provided link):
public class FlattenJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}
JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}
private void WriteJson(JsonWriter writer, JObject value)
{
foreach (var p in value.Properties())
{
if (p.Value is JObject)
WriteJson(writer, (JObject)p.Value);
else
p.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true; // works for any type
}
}
My solution is this
static void Main(string[] args)
{
Position p = new Position();
p.positionName = "Manager";
p.salary = 1000;
Employee e = new Employee();
e.name = "John";
e.position = p;
ResultJson r = new ResultJson();
r.name = e.name;
r.positionName = e.position.positionName;
r.salary = e.position.salary;
var result = JsonConvert.SerializeObject(r);
Console.WriteLine(result);
Console.ReadLine();
}
}
public class Employee
{
public string name { get; set; }
public Position position { get; set; }
}
public class Position
{
public string positionName { get; set; }
public int salary { get; set; }
}
public class ResultJson
{
public string name { get; set; }
public string positionName { get; set; }
public int salary { get; set; }
}
use seperate model for result
You can use this code with NewtonSoft.Json library
[JsonObject]
public class Employee
{
[JsonProperty("name")]
string name;
[JsonProperty("positionName")]
string positionName;
[JsonProperty("salary")]
int salary;
}
Use One class instead 2, or realize own parser
try in this way.
{
"name": "John",
"position":
[{
"positionName": "Manager",
"salary" : 1000
}]
}

Deserialize JSON to list in C#

json - http://pastebin.com/Ss1YZsLK
I need to get market_hash_name values to list. I can receive first value so far:
using (WebClient webClient = new System.Net.WebClient()) {
WebClient web = new WebClient();
var json = web.DownloadString(">JSON LINK<");
Desc data = JsonConvert.DeserializeObject<Desc>(json);
Console.WriteLine(data.rgDescriptions.something.market_hash_name);
}
public class Desc {
public Something rgDescriptions { get; set; }
}
public class Something {
[JsonProperty("822948188_338584038")]
public Name something { get; set; }
}
public class Name {
public string market_hash_name { get; set; }
}
How can I get all if them?
Since there is no array inside the rgDescriptions but some randomly named looking properties I think you would need a custom JsonConverter. The following console application seems to be working and displaying the market_hash_names correctly:
class Program
{
static void Main(string[] args)
{
string json = File.ReadAllText("Sample.json");
Desc result = JsonConvert.DeserializeObject<Desc>(json);
result.rgDescriptions.ForEach(s => Console.WriteLine(s.market_hash_name));
Console.ReadLine();
}
}
public class Desc
{
[JsonConverter(typeof(DescConverter))]
public List<Something> rgDescriptions { get; set; }
}
public class Something
{
public string appid { get; set; }
public string classid { get; set; }
public string market_hash_name { get; set; }
}
class DescConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Something[]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var descriptions = serializer.Deserialize<JObject>(reader);
var result = new List<Something>();
foreach (JProperty property in descriptions.Properties())
{
var something = property.Value.ToObject<Something>();
result.Add(something);
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Output:
Genuine Tactics Pin
Silver Operation Breakout Coin
Operation Phoenix Challenge Coin
Operation Bravo Challenge Coin

Categories

Resources