Override Json.NET annotation of base class in C# - c#

I am using a public library that exposes a model for SwaggerDocument. It comes with some serialization logic added via annotations to specify what should be ignored during serialization, and what order should be applied during serialization and deserialization:
[Newtonsoft.Json.JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 6, PropertyName = "basePath")]
public string BasePath;
I want to change these annotations, without having to creating my own class with all the other logic copied over. Can I extend this class and override the annotations? E.g.
MySwaggerDocument: SwaggerDocument
{
#override
[Newtonsoft.Json.JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate, Order = 4, PropertyName = "basePath")]
public string BasePath;
}

It is not an ideal solution, although this works.
You could use the following strategy to expose some of the attributes of the base class which are messing up the order in your custom derived class.
The drawback is to declare some of the base class' attributes, but as you can see, the logic behind this is quite simple (the get/set syntax is C# 7.0).
using Newtonsoft.Json;
using System;
namespace JsonTest
{
public class Base
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 1, PropertyName = "A")]
public string A { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 2, PropertyName = "X")]
public string X { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 3, PropertyName = "B")]
public string B { get; set; }
}
public class Derived : Base
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 4, PropertyName = "C")]
public string C { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 5, PropertyName = "X")]
public new string X
{
get => base.X;
set => base.X = value;
}
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, Order = 6, PropertyName = "D")]
public string D { get; set; }
}
class Program
{
static void Main(string[] args)
{
Base b = new Base() { A = "a", B = "b", X = "x" };
string serB = JsonConvert.SerializeObject(b);
Console.WriteLine($"Serialized base class:\r\n {serB}");
Derived d = new Derived() { A = "a", B = "b", C = "c", D = "d", X = "x" };
string serD = JsonConvert.SerializeObject(d);
Console.WriteLine($"Serialized derived class:\r\n {serD}");
}
}
}
Output:
Serialized base class:
{"A":"a","X":"x","B":"b"}
Serialized derived class:
{"A":"a","B":"b","C":"c","X":"x","D":"d"}

Related

Base Class Serialization Issue - do not serialize Derived Class Properties

I am tiring to serialize a fairly large list of entities, that are all derived from a base class.
I only need the base class properties in the client. How do I achieve this without instantiating a new instance of the base class?
I have tried creating a custom ContractResolver, but it seems that it does a getType() at runtime, instead of using the Type of the list/Array being serialized
See code sample below.
I want to achieve. castBaseString == actualBaseString ;
So I want castBaseString to = [{"Id":1},{"Id":2}] not [{"Value":"value","Id":1},{"Value":"value2","Id":2}]
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Tests {
[TestClass]
public class JsonNetTest {
class Base {
public int Id { get; set; }
}
class Derived : Base {
public string Value { get; set; }
}
class OtherDerived : Base {
public string Value { get; set; }
public string OtherValue { get; set; }
}
[TestMethod]
public void Test() {
IEnumerable<Derived> deriveds = new Derived[] {
new Derived {Id = 1, Value = "value" },
new Derived {Id = 2, Value = "value2" }
};
IEnumerable<Base> castBases = deriveds.Cast<Base>().ToList();
IEnumerable<Base> bases = new Base[] {
new Base {Id = 1 },
new Base {Id = 2 }
};
JsonSerializerSettings s = new JsonSerializerSettings();
var derString = JsonConvert.SerializeObject(deriveds, s);
var castBaseString = JsonConvert.SerializeObject(castBases, s);
var actualBaseString = JsonConvert.SerializeObject(bases, s);
Assert.AreEqual(actualBaseString, castBaseString);
Assert.AreNotEqual(castBaseString, derString);
}
}
}
EDIT BASED ON COMMENTS
Additional Context:
I just posted this simple test case for clarity.
the actual context this is being used is in an aspnet core application.
Consider there are 3 controllers
/api/DerivedController/
/api/OtherDerivedController/
/api/BaseController/
when a client calls 1, we want to return a list of Derived, when a client calls
2 we want to return a list of OtherDerived when they call 3, we want to return a list of Base
The data is stored in 2 different tables in the database TBL_DERIVED and TBL_OTHERDERIVED.
What we want to achieve when they call base is to return data from One or both of these tables, but just the common properties of these tables.
Hope this clarifies.
If you don't want to use attributes, you can use a ContractResolver to force only properties from Base to be serialized:
public class DerivedTypeFilterContractResolver<T> : DefaultContractResolver {
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) {
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (property.DeclaringType != typeof(T)) {
property.ShouldSerialize = instance => false;
}
return property;
}
}
Then use it like this:
void Main() {
IEnumerable<Derived> deriveds = new Derived[] {
new Derived {Id = 1, Value = "value" },
new Derived {Id = 2, Value = "value2" }
};
IEnumerable<Base> castBases = deriveds.Cast<Base>().ToList();
IEnumerable<Base> bases = new Base[] {
new Base {Id = 1 },
new Base {Id = 2 }
};
JsonSerializerSettings s = new JsonSerializerSettings {
ContractResolver = new DerivedTypeFilterContractResolver<Base>()
};
var derString = JsonConvert.SerializeObject(deriveds, s);
var castBaseString = JsonConvert.SerializeObject(castBases, s);
var actualBaseString = JsonConvert.SerializeObject(bases, s);
Console.WriteLine(castBaseString);
}
class Base {
public int Id { get; set; }
}
class Derived : Base {
public string Value { get; set; }
}
Output:
[{"Id":1},{"Id":2}]
Add [JsonIgnore] top of property.
class Derived : Base {
[JsonIgnore]
public string Value { get; set; }
}
Turns out this is not possible the way the json serialization works.
#stuartd answer might be a good workaround if you only have limited cases you want to do this for.

Reflection in C#: Grouping properties and calculate sum of another properties

So, I have such class:
public class PaymentsModel
{
[ReportsSummary(isGroupingSource:true)]
public string PaymentType { get; set; }
[ReportsSummary(isGroupingTarget: true)]
public double Amount { get; set; }
public string GuestName { get; set; }
}
I have List of (generic) which contains different objects with different values, for example:
{"Bank", 1, "K"},
{"Card", 2, "C"},
{"Cash", 3, "D"},
{"Bank", 2, "E"},
{"Card", 3, "G"},
I need a method CalculateSum() which will use generic class and reflection, and return Dictionary with grouping by PaymentType, and sum Amount for each PaymentType.
So result should be:
[{"Bank", 3},
{"Card", 5},
{"Cash", 5}]
I created an attribute to understand, which property should be grouped, and which - summed:
class ReportsSummaryAttribute : Attribute
{
public bool IsGroupingSource { get; private set; }
public bool IsGroupingTarget { get; private set; }
public ReportsSummaryAttribute(bool isGroupingSource = false, bool isGroupingTarget = false)
{
IsGroupingSource = isGroupingSource;
IsGroupingTarget = isGroupingTarget;
}
}
But don't understand, how to create correct method.
a possible solution you could adapt:
public class MyGenericClass<T> where T:PaymentsModel//or common baseType
{
public Dictionary<string, double> genericMethod(List<T> source)
{
var result = source.GroupBy(x => x.PaymentType)
.Select(t => new { PaymentType = t.Key, Total = t.Sum(u => u.Amount) })
.ToDictionary(t => t.PaymentType, t => t.Total);
return result;
}
}
:
:
//in processus
var myGenericClass = new MyGenericClass<PaymentsModel>();
var result = myGenericClass.genericMethod(source);

Deserialize JSON into dynamic class

I need to deserialize a JSON string into a type which is not know at compile time. There are several classes that it can be deserialized into. The name of the class is provided as input into the application and based on that I want to instantiate the class (already done this through reflection):
var type = Type.GetType(className);
var myClassInstance = (IParser)Activator.CreateInstance(type);
...and then use its type as the generic type parameter for JsonConvert.DeserializeObject<typeof(myClassInstance).Name>(jsonString) but that doesn't work.
How can I provide the class to DeserializeObject<>() dynamically?
Instead of using an generic method overload like JsonConvert.DeserializeObject<T>(String) and having to resort to reflection as some comments state, you could simply use the non generic counterpart JsonConvert.DeserializeObject(String, Type), which just takes in a Type instance like you already have!
Implementation
Initialization
var class1s = new Class1() {
ID = 1, Name = "Test", Comment = "This Code is Tested!."
};
var class2s = new Class2() {
xVal1 = 1, XVal2 = 5, xval3 = 10
};
var JSON1 = Newtonsoft.Json.JsonConvert.SerializeObject(class1s);
var JSON2 = Newtonsoft.Json.JsonConvert.SerializeObject(class2s);
Calling Functions
var classname1 = typeof(Class1).FullName;
var type1 = Type.GetType(classname1);
var classname2 = typeof(Class2).FullName;
var type2 = Type.GetType(classname2);
var c = LocalConverter(JSON1, type1);
var c2 = LocalConverter(JSON2, type2);
Class Models
public class Class1 {
public int ID {
get;
set;
}
public string Name {
get;
set;
}
public string Comment {
get;
set;
}
}
public class Class2 {
public int xVal1 {
get;
set;
}
public int XVal2 {
get;
set;
}
public int xval3 {
get;
set;
}
}
Required Method
private object LocalConverter(string o, Type xtype) {
return Newtonsoft.Json.JsonConvert.DeserializeObject(o, xtype);
}

Json.net JsonIgnore doesn't work for nested classes

I am trying to serialize some third party json using Json.net and the problem is that they started sending Ids as strings insted of Guids. So I am trying to ignore the Ids within serialization but there seems to be a problem within nested properties that the JsonIgnore doesn't work. For that reason I decided to add my own Ids after but the serialisation itself doesn't seem to ignore what I need it to.
My classes used for serialization:
public class ItemA: General.Common
{
[JsonIgnore]
public new Guid Id { get; set; } //hiding Guid Id Common
public Folder ParentFolder { get; set; }
//...
}
public class Folder
{
public string Id { get; set; }
public string Path { get; set; }
}
public class ItemB: NotImportant
{
//...
public List<Folder> Folders { get; set; } = new List<Folder>();
public List<ItemA> ItemAs{ get; set; } = new List<ItemA>();
}
My Code:
var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
string json = "some Json-that includes some Ids as strings";
dynamic d = JsonConvert.DeserializeObject<ItemB>(json, jsonSettings);
//serialization error here that cannot convert string to Guid in ItemAs
((ItemB)d).ItemAs.ForEach(x => x.Id = new Guid());
EDIT:
The error is something like this:
Error converting value "RgAAAAAD01CCe0GCRpDdKTQq2OCQBwAIuTruAfDrRZi9RPZnww3OAAAAAAEMAAAIuTruAfDrRZi9RPZnww3OAABE1hqaAAAA" to type 'System.Guid'...
There is an official Issue about this:
Issue #463 - JsonIgnore attribute on shadowed properties
They are separate properties, one happens to be hiding the other. By ignoring the one on Derived then the property on base is no longer hidden and is being serialized instead. If you want to ignore both then place [JsonIgnore] on both, or if you want [JsonIgnore] on the Derived class to ignore both then base the property virtual and override it on Derived. - JamesNK
It's possible to ignore during deserialize : fully working example based on your code: (see Ignore a property when deserializing using Json.Net with ItemRequired = Required.Always)
using System;
using System.IO;
using System.Collections.Generic;
using Newtonsoft.Json;
[JsonObject(ItemRequired = Required.Always)]
public class ItemA : General.Common
{
[JsonIgnore]
[JsonProperty(Required = Required.Default)]
public new Guid Id { get; set; } //hiding Guid Id Common
public Folder ParentFolder { get; set; }
//...
}
public class Folder
{
public string Id { get; set; }
public string Path { get; set; }
}
public class ItemB : NotImportant
{
//...
public List<Folder> Folders { get; set; } = new List<Folder>();
public List<ItemA> ItemAs { get; set; } = new List<ItemA>();
}
public class Test
{
var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
//ItemB b = new ItemB()
//{
// Folders = new List<Folder>() {
// new Folder() { Id = "1", Path = "myPath1" },
// new Folder() { Id = "2", Path = "myPath2" },
// new Folder() { Id = "3", Path = "myPath3" } },
// ItemAs = new List<ItemA>() {
// new ItemA() { Id = Guid.NewGuid(), ParentFolder = new Folder()
// { Id = "p1", Path = "parentpath1" } },
//new ItemA()
//{ Id = Guid.NewGuid(),
// ParentFolder = new Folder()
//{ Id = "p2", Path = "parentpath2" } }}
//};
//string json = JsonConvert.SerializeObject(b);
string json = "{\"Folders\":[{\"Id\":\"1\",\"Path\":\"myPath1\"},{\"Id\":\"2\",\"Path\":\"myPath2\"},{\"Id\":\"3\",\"Path\":\"myPath3\"}],\"ItemAs\":[{\"Id\":\"RgAAAAAD01CCe0GCRpDdKTQq2OCQBwAIuTruAfDrRZi9RPZnww3OAAAAAAEMAAAIuTruAfDrRZi9RPZnww3OAABE1hqaAAAA\",\"ParentFolder\":{\"Id\":\"p1\",\"Path\":\"parentpath1\"}},{\"Id\":\"c0af33a9-3e6f-4405-a2d4-ff469cb67fce\",\"ParentFolder\":{\"Id\":\"p2\",\"Path\":\"parentpath2\"}}]}";
dynamic d = JsonConvert.DeserializeObject<ItemB>(json, jsonSettings);
//no serialization error
((ItemB)d).ItemAs.ForEach(x => x.Id = Guid.NewGuid());
}

Deserialising XML with different element name in c#

XML:
<?xml version="1.0" encoding="UTF-8"?>
<Images>
<I0>
<Path>123.com</Path>
<I0>
<I1>
<Path>123.com</Path>
<I1>
<I2>
<Path>123.com</Path>
<I2>
</Images>
Can serializer.Deserialize() be used to get tags with different names into a collection?
currently, in my object I have:
C#:
public class rootObject
{
[XmlElement(ElementName = "I0")]
public I0 I0 { get; set; }
[XmlElement(ElementName = "I1")]
public I1 I1 { get; set; }
[XmlElement(ElementName = "I2")]
public I2 I2 { get; set; }
}
But I would like to have (Because Images can have more or fewer elements):
public class rootObject
{
public List<I> Is { get; set; }
}
You can do what you are suggesting you just merely need to pass in the type argument in your class doing the generic. The key point to remember when you do a deserialization routine is that the routine needs to know the sub reference. So if I was to say string.Deserialize it would bomb. It would need to know a reference string.Deserialize> where Sub could be the class object that may change.
Say I have a base class and I want 'T' to be a type I can change for extensible abilities later.
[Serializable]
public class Test<T> where T : class
{
public Test() { }
public int TestId { get; set; }
public string Name { get; set; }
public List<T> Shipments { get; set; }
}
I want to test this with two classes I just make up that have different properties slightly
[Serializable]
public class Sub1
{
public int Id { get; set; }
public string Desc { get; set; }
}
[Serializable]
public class Sub2
{
public int IdWhatever { get; set; }
public string DescWhatever { get; set; }
}
Now let's do a main program and test serialization.
class Program
{
static void Main(string[] args)
{
var serializeTest = new Test<Sub1> { TestId = 1, Name = "Test", Shipments = new List<Sub1> { new Sub1 { Id = 1, Desc = "Test" }, new Sub1 { Id = 2, Desc = "Test2" } } };
var serializeTest2 = new Test<Sub2> { TestId = 1, Name = "Test", Shipments = new List<Sub2> { new Sub2 { IdWhatever = 1, DescWhatever = "Test" }, new Sub2 { IdWhatever = 2, DescWhatever = "Test2" } } };
var serialized = serializeTest.SerializeToXml();
var serialized2 = serializeTest2.SerializeToXml();
var deserialized = serialized.DeserializeXml<Test<Sub1>>();
var deserialized2 = serialized2.DeserializeXml<Test<Sub2>>();
Console.WriteLine(serialized);
Console.WriteLine();
Console.WriteLine(serialized2);
Console.ReadLine();
}
}
And my Serialize and DeSerialize extension methods:
public static string SerializeToXml<T>(this T valueToSerialize, string namespaceUsed = null)
{
var ns = new XmlSerializerNamespaces(new XmlQualifiedName[] { new XmlQualifiedName(string.Empty, (namespaceUsed != null) ? namespaceUsed : string.Empty) });
using (var sw = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(sw, new XmlWriterSettings { OmitXmlDeclaration = true }))
{
dynamic xmler = new XmlSerializer(typeof(T));
xmler.Serialize(writer, valueToSerialize, ns);
}
return sw.ToString();
}
}
public static T DeserializeXml<T>(this string xmlToDeserialize)
{
dynamic serializer = new XmlSerializer(typeof(T));
using (TextReader reader = new StringReader(xmlToDeserialize))
{
return (T)serializer.Deserialize(reader);
}
}
You don't need to specify the XmlElement name when the properties match the XML. A few solutions, some kinda hacky :).
HACKY: use regex string replace to replace <I#> and </I#> to
just <I> and </I>
SOMEWHAT HACKY: This might work for you:
How to deserialize an XML array containing multiple types of elements in C#,
but you'd have to add an attribute for i0, i1 ... i100, etc.
BEST: Is that your entire XML? I'd honestly just use LINQToXml and
do a Descendants("Path") and get an array of strings back with 1 line of code. Serialization is not really the best solution for this.

Categories

Resources