Serializing a collection of view models with overridden Equals method - c#

Trying to serialize a collection of a custom type with an overloaded Equals(object obj) method. I am using Newtonsoft.Json.JsonConvert.SerializeObject(object value) to achieve this.
This is my abstract base view model from which the view model in question inherits:
public abstract class BaseCollectibleViewModel
{
protected abstract bool CompareParameters(object item);
protected abstract List<int> GetParameters();
public override bool Equals(object obj)
{
if (CompareParameters(obj))
{
return true;
}
return false;
}
public override int GetHashCode()
{
int hash = 13;
foreach (var parameter in GetParameters())
{
hash = (hash * 7) + parameter.GetHashCode();
}
return hash;
}
public static bool operator ==(BaseCollectibleViewModel a, BaseCollectibleViewModel b)
{
if (a.Equals(b))
{
return true;
}
return false;
}
public static bool operator !=(BaseCollectibleViewModel a, BaseCollectibleViewModel b)
{
if (a.Equals(b))
{
return false;
}
return true;
}
}
This is the actual view model:
public class ImagesViewModel : BaseCollectibleViewModel, ISourceImage
{
public string Name { get; private set; }
public string Type { get; private set; }
[ScriptIgnore]
public Stream Content { get; private set; }
[ScriptIgnore]
private HttpPostedFileBase _file;
[ScriptIgnore]
public HttpPostedFileBase File
{
get
{
return _file;
}
set
{
_file = value;
Name = File.FileName;
Type = File.ContentType;
Content = new MemoryStream();
File.InputStream.CopyTo(Content);
}
}
protected override bool CompareParameters(object obj)
{
var temp = obj as ImagesViewModel;
if(temp == null)
{
return false;
}
return
(Name == temp.Name &&
Type == temp.Type);
}
protected override List<int> GetParameters()
{
return new List<int>()
{
Name.GetHashCode(),
Type.GetHashCode()
};
}
}
Notice the ScriptIgnore attributes. I even have one on the private field. The program breaks on the == operator of the base class because both of the arguments that get passed are null.
This is the serializing code:
[HttpPost]
public string GetSessionImages()
{
var imagesInSession = _imagesSessionService.GetCollection();
return JsonConvert.SerializeObject(imagesInSession, Formatting.Indented);
}
Also this:
The screenshot is showing the implementation of the abstract CompareParameters(object obj) method on the inheriting view model. That stream is the Content property stream, I have checked. Why is this happening?
EDIT: When not overriding Equals I get a JsonSerializationException stating:
{"Error getting value from 'ReadTimeout' on
'System.IO.MemoryStream'."}
EDIT 2: Per dbc's comment I have replaced the attribute [ScriptIgnore] with [JsonIgnore] and the code worked to an extent.
However, I had to comment out the operator implementations because the '==' operator would be passed a null value as the BaseCollectibleViewModel b argument.

Since you are using json.net, you must mark members to ignore with [JsonIgnore]:
using Newtonsoft.Json;
public class ImagesViewModel : BaseCollectibleViewModel, ISourceImage
{
public string Name { get; private set; }
public string Type { get; private set; }
[ScriptIgnore]
[JsonIgnore]
public Stream Content { get; private set; }
[ScriptIgnore]
[JsonIgnore]
public HttpPostedFileBase File { get { ... } set { ... } }
It is not necessary to mark entirely private members with [JsonIgnore] as these are not serialized by default by Json.NET.
Alternatively, if you do not want your models to have a dependency on Json.NET, you could use conditional property serialization to unconditionally suppress the same members:
public class ImagesViewModel : BaseCollectibleViewModel, ISourceImage
{
public string Name { get; private set; }
public string Type { get; private set; }
[ScriptIgnore]
public Stream Content { get; private set; }
public bool ShouldSerializeContent() { return false; }
[ScriptIgnore]
public HttpPostedFileBase File { get { ... } set { ... } }
public bool ShouldSerializeFile() { return false; }
Note that the ShouldSerializeXXX() conditional serialization pattern is also respected by other serializers including XmlSerializer, as explained in ShouldSerialize*() vs *Specified Conditional Serialization Pattern - a side-effect which may be desirable, or not.
(Incidentally, you might want to check that you are not double-serializing your data as shown in JSON.NET Parser *seems* to be double serializing my objects.)

Related

C# Print TableEntity Properties, but ignore those properties with attribute [IgnoreProperty]

I am trying to print out an object that implements TableEntity class, without those that should be Ignored regarding the persistence. The approach I generally use to print out objects is to use the StatePrinter.
public class MyEntity : TableEntity
{
public string MyProperty { get; set; }
[IgnoreProperty]
public string MyIgnoredProperty { get; set; }
public override string ToString()
{
Stateprinter printer = new Stateprinter();
return printer.PrintObject(this);
}
}
While this works pretty good for any kind of classes, with this MyEntity class it also prints the MyIgnoredProperty. Is there a clever way to also ignore the properties that have [IgnoredProperty] as attribute when printing out the object?
You can configure what fields/properties the Stateprinter cares about by configuring what "field harvester" to use.
Here's a simple field harvester that only returns public properties without the 'IgnoreProperty' attribute.
class PersistencePropertiesHarvester : IFieldHarvester
{
public bool CanHandleType(Type type)
{
return typeof(TableEntity).IsAssignableFrom(type);
}
public List<SanitizedFieldInfo> GetFields(Type type)
{
var fields = new HarvestHelper().GetFieldsAndProperties(type);
return fields.Where(IsPerstistenceProperty).ToList();
}
private static bool IsPerstistenceProperty(SanitizedFieldInfo field)
{
return
// Only return properties ...
field.FieldInfo.MemberType == MemberTypes.Property
&&
// ... that has a public get method ...
(field.FieldInfo as PropertyInfo)?.GetGetMethod(false) != null
&&
// ... that does not have the IgnoreProperty attribute
field.FieldInfo.GetCustomAttribute<IgnoreProperty>() == null
;
}
}
Then you use it like this:
public class MyEntity : TableEntity
{
public string MyProperty { get; set; }
[IgnoreProperty]
public string MyIgnoredProperty { get; set; }
public override string ToString()
{
Stateprinter printer = new Stateprinter();
printer.Configuration.Add(new PersistencePropertiesHarvester());
return printer.PrintObject(this);
}
}
And the result of new MyEntity().ToString() is now
new MyEntity()
{
MyProperty = null
}

How to force JSON serialization to include type information for a polymorphic System.Enum member?

I am using JSON serialization and deserialization for serialize/deserialize the storage class of my big application.
some where in my application, I need to hold the enums of different enumsType in an Enum or sum times i will need to hold the different classes inherited from a parent class to a variable of type of parent class. please look at the below code which demonstrate an example for an enum. in these situation, the serialization gets error especially for enums and cannot find the enum because the type of variable in Enum. for example in the below sample code the inputEnum is of type Enum and can get enums of Enum1 and Enum2. but in deserialization the Json brings error that cannot convert int64 to type of Enum.
Now I need the JSON to hold the original type of the variables to deserialize them. for example in inputenum=Enum1.hi, the type to be hold as Enum1 instead of Enum that is occurring now.
Please give your solutions.
public enum Enum1
{
hi=0,
hello
}
public enum Enum2
{
by = 0,
goodbye
}
public class TUnitClass
{
public string Id { set; get; }
public string Name { set; get; }
public Enum inputenum { set; get; }
public string Comment { set; get; }
public string displayingString { set; get; }
}
private void Form1_Load(object sender, EventArgs e)
{
List<TUnitClass> cls1=new List<TUnitClass>()
{
new TUnitClass() {inputenum = Enum2.goodbye},
new TUnitClass() {inputenum = Enum2.by},
new TUnitClass() {inputenum = Enum1.hello},
new TUnitClass() {inputenum = Enum1.hi},
new TUnitClass() {inputenum = Enum2.goodbye},
};
string strSerialized = JsonConvert.SerializeObject(cls1);
List<TUnitClass> cls2 = JsonConvert.DeserializeObject<List<TUnitClass>>(strSerialized,new JsonSerializerSettings() {Error = ErrorOnDeserialization});
}
private void ErrorOnDeserialization(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs e)
{
}
Update
I have attempted to implement the solution from Deserialize specific enum into system.enum in Json.Net. Type information is correctly written to the JSON file during serialization, however during deserialization an exception is thrown:
Newtonsoft.Json.JsonSerializationException occurred
Message="Type specified in JSON 'MTC.Utility.UnitConvertor.TypeWrapper`1[[MTC.Utility.UnitConvertor.EAcceleration, MTC.Utility.UnitConvertor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], MTC.Utility.UnitConvertor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not compatible with 'System.Enum, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Path 'listOfUnitSystems[0].UnitsList[0].Unit.$type', line 17, position 269."
Source="Newtonsoft.Json"
StackTrace:
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolveTypeName(JsonReader reader, Type& objectType, JsonContract& contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, String qualifiedTypeName) in C:\Development\Releases\Json\Working\Newtonsoft.Json\Working-Signed\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:line 814
InnerException:
Here is the updated code:
public abstract class TypeWrapper
{
protected TypeWrapper() { }
[JsonIgnore]
public abstract object ObjectValue { get; }
public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}
public sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }
public TypeWrapper(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
public T Value { get; set; }
}
public class TUnitClass
{
public string Id { set; get; }
public string Name { set; get; }
public EQuantities Quantity { set; get; }
[JsonIgnore]
public System.Enum Unit { get; set; }
[JsonProperty("Unit", TypeNameHandling = TypeNameHandling.All)]
TypeWrapper UnitValue
{
get
{
return Unit == null ? null : TypeWrapper.CreateWrapper(Unit);
}
set
{
if (value == null || value.ObjectValue == null)
Unit = null;
else
Unit = (Enum)value.ObjectValue;
}
}
public string Comment { set; get; }
public string displayingString { set; get; }
public TUnitClass(string id, EQuantities quantity,Enum unit, string comment)
{
Id = id;
Name = quantity.ToString();
Quantity = quantity;
Unit = unit;
Comment = comment;
FieldInfo fi = unit.GetType().GetField(unit.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
displayingString= attributes[0].Description;
}
}
Update 2
Finally, If I have an Enum[] property instead of Enum, how can I behave with this problem?
The reason the solution from question 31351262 does not work is that your type TUnitClass does not have a default constructor. Instead, it has a single parameterized constructor. In cases like this, Json.NET will invoke that constructor, matching JSON objects to constructor arguments by name, modulo case. (For details, see this answer.) And unfortunately your constructor takes an abstract System.Enum as its unit argument:
public TUnitClass(string id, EQuantities quantity, Enum unit, string comment)
{
}
Json.NET will deserialize the surrogate TypeWrapper<TEnum> correctly but then try to cast it to a System.Enum in order to call the constructor, and fail, throwing the exception you are seeing.
The solution is to add a constructor that accepts a surrogate TypeWrapper unit value, and mark that constructor with [JsonConstructor]. It can be private as long as it is marked with [JsonConstructor]. The following should work:
public class TUnitClass
{
[JsonConstructor]
TUnitClass(string id, EQuantities quantity, TypeWrapper<Enum> unit, string comment)
: this(id, quantity, unit.Value(), comment)
{
}
public TUnitClass(string id, EQuantities quantity, Enum unit, string comment)
{
Id = id;
Name = quantity.ToString();
Quantity = quantity;
Unit = unit;
Comment = comment;
FieldInfo fi = unit.GetType().GetField(unit.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
displayingString = attributes[0].Description;
}
public string Id { set; get; }
public string Name { set; get; }
public EQuantities Quantity { set; get; }
[JsonIgnore]
public System.Enum Unit { get; set; }
[JsonProperty("Unit", TypeNameHandling = TypeNameHandling.All)]
TypeWrapper<Enum> UnitSurrogate
{
get
{
return Unit == null ? null : TypeWrapper<Enum>.CreateWrapper(Unit);
}
set
{
Unit = value.Value();
}
}
public string Comment { set; get; }
public string displayingString { set; get; }
public TUnitClass Copy()
{
TUnitClass copiedUnitClass=new TUnitClass(Id,Quantity,Unit,Comment);
return copiedUnitClass;
}
}
public abstract class TypeWrapper<TBase>
{
protected TypeWrapper() { }
[JsonIgnore]
public abstract TBase BaseValue { get; }
public static TypeWrapper<TBase> CreateWrapper<TDerived>(TDerived value) where TDerived : TBase
{
if (value == null)
return null;
var type = value.GetType();
if (type == typeof(TDerived))
return new TypeWrapper<TDerived, TBase>(value);
// Return actual type of subclass
return (TypeWrapper<TBase>)Activator.CreateInstance(typeof(TypeWrapper<,>).MakeGenericType(type, typeof(TBase)), value);
}
}
public static class TypeWrapperExtensions
{
public static TBase Value<TBase>(this TypeWrapper<TBase> wrapper)
{
if (wrapper == null || wrapper.BaseValue == null)
return default(TBase);
return wrapper.BaseValue;
}
}
public sealed class TypeWrapper<TDerived, TBase> : TypeWrapper<TBase> where TDerived : TBase
{
public TypeWrapper() : base() { }
public TypeWrapper(TDerived value)
: base()
{
this.Value = value;
}
public override TBase BaseValue { get { return Value; } }
public TDerived Value { get; set; }
}
TypeNameAssemblyFormat = FormatterAssemblyStyle.Full should no longer be necessary.
Note I modified the linked answer to add type safety to ensure the serialized type is in fact a System.Enum, which should increase security by preventing unexpected types during deserialization. See TypeNameHandling caution in Newtonsoft Json for a general discussion of TypeNameHandling security.
Update
If I have a Enum[] instead of Enum, how can I behave with this problem?
You would need to use a surrogate array of TypeWrapper<Enum> objects, like so:
public class TUnitArrayClass
{
[JsonConstructor]
TUnitArrayClass(TypeWrapper<Enum>[] units)
: this(units == null ? null : units.Select(s => s.Value()).ToArray())
{
}
public TUnitArrayClass(IEnumerable<System.Enum> units)
{
this.Units = (units ?? Enumerable.Empty<System.Enum>()).ToArray();
}
[JsonIgnore]
public System.Enum[] Units { get; set; }
[JsonProperty("Unit", TypeNameHandling = TypeNameHandling.All)]
TypeWrapper<Enum> [] UnitsSurrogate
{
get
{
return Units == null ? null : Units.Select(u => TypeWrapper<Enum>.CreateWrapper(u)).ToArray();
}
set
{
if (value == null)
Units = null;
else
Units = value.Select(s => s.Value()).ToArray();
}
}
}

Generic property-list with any generic argument

I need to instantiate a list-property where the generic type can be anything.
So my Main-method looks like this: (In real, ParsingObject<T> are objects I get from a service)
public static void Main()
{
Parser parser = new Parser();
parser.AddAnObject(
new ParsingObject<int>{PropertyName = "FirstProperty", Active=true, DefaultValue=1}
);
parser.AddAnObject(
new ParsingObject<bool>{PropertyName = "SecondProperty", Active=false, DefaultValue=false}
);
parser.Parse();
}
ParsingObject gets any type (I think only string, bool, int,...) as generic. Now in my parser I need to add this object into a List<ParsingObject<T>> like:
public class Parser
{
private readonly List<ParsingObject<T>> _listOfObjects = new List<ParsingObject<T>>();
public void AddAnObject<T>(ParsingObject<T> item)
{
_listOfObjects.Add(item);
}
public void Parse()
{
foreach(var item in _listOfObjects.Where(w=>Active))
{
DoSomething(item);
}
}
}
but I know, I cannot set T as generic argument when instantiating the list (compiler is crying..).
So I could solve this with using ArrayList - but then I can't access the properties of each object. (See the Parse()-method)
for completeness, here is my ParsingObject<T>-class:
public class ParsingObject<T>
{
public string PropertyName { get; set; }
public bool Active { get; set; }
public T DefaultValue { get; set; }
}
Any idea how I could solve this? I cannot modify the ParsingObject<T>-class.
Depending on what exactly is your end goal, maybe something like this would be sufficient:
public class ParsingObjectBase
{
public string PropertyName { get; set; }
public bool Active { get; set; }
public Type ValueType { get; protected set; }
public object DefVal { get; protected set; }
}
public class ParsingObject<T> : ParsingObjectBase
{
public object DefaultValue
{
get { return (T)DefVal; }
set { DefVal = value; }
}
public ParsingObject()
{
ValueType = typeof(T);
}
}
private readonly List<ParsingObjectBase> _listOfObjects = new List<ParsingObjectBase>();
public void AddAnObject<T>(ParsingObject<T> item)
{
_listOfObjects.Add(item);
}
public void Parse()
{
foreach(var item in _listOfObjects.Where(w=>w.Active))
{
DoSomething(item); //do what exactly?
}
}
You obviously can't do without casting either to concrete ParsingObject<T> or DefVal value in this case, but you have Type information stored in one place and have access to your specific properties. Maybe changing ValueType to some kind of enum would be easier to use with switch?

Newtonsoft JSON dynamic property name

Is there a way to change name of Data property during serialization, so I can reuse this class in my WEB Api.
For an example, if i am returning paged list of users, Data property should be serialized as "users", if i'm returning list of items, should be called "items", etc.
Is something like this possible:
public class PagedData
{
[JsonProperty(PropertyName = "Set from constructor")]??
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
EDIT:
I would like to have a control over this functionality, such as passing name to be used if possible. If my class is called UserDTO, I still want serialized property to be called Users, not UserDTOs.
Example
var usersPagedData = new PagedData("Users", params...);
You can do this with a custom ContractResolver. The resolver can look for a custom attribute which will signal that you want the name of the JSON property to be based on the class of the items in the enumerable. If the item class has another attribute on it specifying its plural name, that name will then be used for the enumerable property, otherwise the item class name itself will be pluralized and used as the enumerable property name. Below is the code you would need.
First let's define some custom attributes:
public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}
public class JsonPluralNameAttribute : Attribute
{
public string PluralName { get; set; }
public JsonPluralNameAttribute(string pluralName)
{
PluralName = pluralName;
}
}
And then the resolver:
public class CustomResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
if (prop.PropertyType.IsGenericType && member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type itemType = prop.PropertyType.GetGenericArguments().First();
JsonPluralNameAttribute att = itemType.GetCustomAttribute<JsonPluralNameAttribute>();
prop.PropertyName = att != null ? att.PluralName : Pluralize(itemType.Name);
}
return prop;
}
protected string Pluralize(string name)
{
if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("oy") && !name.EndsWith("uy"))
return name.Substring(0, name.Length - 1) + "ies";
if (name.EndsWith("s"))
return name + "es";
return name + "s";
}
}
Now you can decorate the variably-named property in your PagedData<T> class with the [JsonPropertyNameBasedOnItemClass] attribute:
public class PagedData<T>
{
[JsonPropertyNameBasedOnItemClass]
public IEnumerable<T> Data { get; private set; }
...
}
And decorate your DTO classes with the [JsonPluralName] attribute:
[JsonPluralName("Users")]
public class UserDTO
{
...
}
[JsonPluralName("Items")]
public class ItemDTO
{
...
}
Finally, to serialize, create an instance of JsonSerializerSettings, set the ContractResolver property, and pass the settings to JsonConvert.SerializeObject like so:
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
string json = JsonConvert.SerializeObject(pagedData, settings);
Fiddle: https://dotnetfiddle.net/GqKBnx
If you're using Web API (looks like you are), then you can install the custom resolver into the pipeline via the Register method of the WebApiConfig class (in the App_Start folder).
JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;
settings.ContractResolver = new CustomResolver();
Another Approach
Another possible approach uses a custom JsonConverter to handle the serialization of the PagedData class specifically instead using the more general "resolver + attributes" approach presented above. The converter approach requires that there be another property on your PagedData class which specifies the JSON name to use for the enumerable Data property. You could either pass this name in the PagedData constructor or set it separately, as long as you do it before serialization time. The converter will look for that name and use it when writing out JSON for the enumerable property.
Here is the code for the converter:
public class PagedDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(PagedData<>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
string dataPropertyName = (string)type.GetProperty("DataPropertyName", bindingFlags).GetValue(value);
if (string.IsNullOrEmpty(dataPropertyName))
{
dataPropertyName = "Data";
}
JObject jo = new JObject();
jo.Add(dataPropertyName, JArray.FromObject(type.GetProperty("Data").GetValue(value)));
foreach (PropertyInfo prop in type.GetProperties().Where(p => !p.Name.StartsWith("Data")))
{
jo.Add(prop.Name, new JValue(prop.GetValue(value)));
}
jo.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use this converter, first add a string property called DataPropertyName to your PagedData class (it can be private if you like), then add a [JsonConverter] attribute to the class to tie it to the converter:
[JsonConverter(typeof(PagedDataConverter))]
public class PagedData<T>
{
private string DataPropertyName { get; set; }
public IEnumerable<T> Data { get; private set; }
...
}
And that's it. As long as you've set the DataPropertyName property, it will be picked up by the converter on serialization.
Fiddle: https://dotnetfiddle.net/8E8fEE
UPD Sep 2020: #RyanHarlich pointed that proposed solution doesn't work out of the box. I found that Newtonsoft.Json doesn't initialize getter-only properties in newer versions, but I'm pretty sure it did ATM I wrote this answer in 2016 (no proofs, sorry :).
A quick-n-dirty solution is to add public setters to all properties ( example in dotnetfiddle ). I encourage you to find a better solution that keeps read-only interface for data objects. I haven't used .Net for 3 years, so cannot give you that solution myself, sorry :/
Another option with no need to play with json formatters or use string replacements - only inheritance and overriding (still not very nice solution, imo):
public class MyUser { }
public class MyItem { }
// you cannot use it out of the box, because it's abstract,
// i.e. only for what's intended [=implemented].
public abstract class PaginatedData<T>
{
// abstract, so you don't forget to override it in ancestors
public abstract IEnumerable<T> Data { get; }
public int Count { get; }
public int CurrentPage { get; }
public int Offset { get; }
public int RowsPerPage { get; }
public int? PreviousPage { get; }
public int? NextPage { get; }
}
// you specify class explicitly
// name is clear,.. still not clearer than PaginatedData<MyUser> though
public sealed class PaginatedUsers : PaginatedData<MyUser>
{
// explicit mapping - more agile than implicit name convension
[JsonProperty("Users")]
public override IEnumerable<MyUser> Data { get; }
}
public sealed class PaginatedItems : PaginatedData<MyItem>
{
[JsonProperty("Items")]
public override IEnumerable<MyItem> Data { get; }
}
Here is a solution that doesn't require any change in the way you use the Json serializer. In fact, it should also work with other serializers. It uses the cool DynamicObject class.
The usage is just like you wanted:
var usersPagedData = new PagedData<User>("Users");
....
public class PagedData<T> : DynamicObject
{
private string _name;
public PagedData(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
_name = name;
}
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
public override IEnumerable<string> GetDynamicMemberNames()
{
yield return _name;
foreach (var prop in GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.Name != nameof(Data)))
{
yield return prop.Name;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == _name)
{
result = Data;
return true;
}
return base.TryGetMember(binder, out result);
}
}
The following is another solution tested in .NET Standard 2.
public class PagedResult<T> where T : class
{
[JsonPropertyNameBasedOnItemClassAttribute]
public List<T> Results { get; set; }
[JsonProperty("count")]
public long Count { get; set; }
[JsonProperty("total_count")]
public long TotalCount { get; set; }
[JsonProperty("current_page")]
public long CurrentPage { get; set; }
[JsonProperty("per_page")]
public long PerPage { get; set; }
[JsonProperty("pages")]
public long Pages { get; set; }
}
I am using Humanizer for pluralization.
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type[] arguments = property.DeclaringType.GenericTypeArguments;
if(arguments.Length > 0)
{
string name = arguments[0].Name.ToString();
property.PropertyName = name.ToLower().Pluralize();
}
return property;
}
return base.CreateProperty(member, memberSerialization);
}
There's a package called SerializationInterceptor. Here's the GitHub link: https://github.com/Dorin-Mocan/SerializationInterceptor/wiki. You can also install the package using Nuget Package Manager.
The example from below uses Syste.Text.Json for serialization. You can use any other serializer(except Newtonsoft.Json). For more info on why Newtonsoft.Json not allowed, please refer to GitHub documentation.
You can create an interceptor
public class JsonPropertyNameInterceptorAttribute : InterceptorAttribute
{
public JsonPropertyNameInterceptorAttribute(string interceptorId)
: base(interceptorId, typeof(JsonPropertyNameAttribute))
{
}
protected override void Intercept(in AttributeParams originalAttributeParams, object context)
{
string theNameYouWant;
switch (InterceptorId)
{
case "some id":
theNameYouWant = (string)context;
break;
default:
return;
}
originalAttributeParams.ConstructorArgs.First().ArgValue = theNameYouWant;
}
}
And put the interceptor on the Data prop
public class PagedData<T>
{
[JsonPropertyNameInterceptor("some id")]
[JsonPropertyName("during serialization this value will be replaced with the one passed in context")]
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
And then you can serialize the object like this
var serializedObj = InterceptSerialization(
obj,
objType,
(o, t) =>
{
return JsonSerializer.Serialize(o, t, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve });
},
context: "the name you want");
Hope this will be of use to you.
have a look here:
How to rename JSON key
Its not done during serialization but with a string operation.
Not very nice (in my eyes) but at least a possibility.
Cheers Thomas

How to set value of a property that is a class in C#

I am trying to set the value of a property that is a class.
protected bool TryUpdate(PropertyInfo prop, object value)
{
try
{
prop.SetValue(this, value);
// If not set probably a complex type
if (value != prop.GetValue(this))
{
//... Don't know what to do
}
// If still not set update failed
if (value != prop.GetValue(this))
{
return false;
}
return true;
}
}
I'm calling this method on a number of properties from a variety of classes. This issue is when I have a class like the following:
public class Test
{
public string Name { get; set; }
public string Number { get; set; }
public IComplexObject Object { get; set; }
}
Name and Number are set just fine, but if I try and set an instance of a class that inherits from IComplexObject on Object there is no errors it just remains null.
Is there an easy way to set an instance of a class as property?
So for example if I pass in prop as {IComplexObject Object} and object as
var object = (object) new ComplexObject
{
prop1 = "Property"
prop2 = "OtherProperty"
}
At the end there are no errors but Object remains null and is not set to the instance of ComplexObject. It needs to be generic so I can pass in any class and the property will be updated.
This example works. I've put it for a reference.
I've changed it to await the task and extract the return value to the result variable so you could see that it returns true.
public class Test
{
public string Name { get; set; }
public string Number { get; set; }
public IComplexObject Object { get; set; }
public async Task<bool> TryUpdate(PropertyInfo prop, object value) {
try {
prop.SetValue(this, value);
return true;
}
catch (Exception) {
}
return false;
}
}
public class ComplexObject : IComplexObject
{
}
public interface IComplexObject
{
}
static class Program
{
static void Main() {
TestMethod();
}
static async void TestMethod() {
var test = new Test();
var result = await test.TryUpdate(test.GetType().GetProperty("Object"), new ComplexObject());
}
}
Your code is needlessly complicated, but it works completely fine. I've taken your code and expanded it into a complete example that runs.
The output is this.
Name: asdf, Number: A1B2, Object: hello this is complexobject
It was not null
This shows that the Object property is no different from any of the others. "Complex object" is not a term that really means anything in .net. Additionally your use of async seems unnecessary and confusing.
async void Main() {
Test t = new Test();
Type type = typeof(Test);
await t.TryUpdate(type.GetProperty(nameof(t.Name)), "asdf");
await t.TryUpdate(type.GetProperty(nameof(t.Number)), "A1B2");
await t.TryUpdate(type.GetProperty(nameof(t.Object)), (object)new ComplexObject());
Console.WriteLine(t.ToString());
PropertyInfo prop = type.GetProperty(nameof(t.Object));
if (prop.GetValue(t) == null) {
Console.WriteLine("It was null");
} else {
Console.WriteLine("It was not null");
}
}
public class Test {
public string Name { get; set; }
public string Number { get; set; }
public IComplexObject Object { get; set; }
// Added for the purpose if showing contents
public override string ToString() => $"Name: {Name}, Number: {Number}, Object: {Object}";
// Why is this async? Your code does not await
public async Task<bool> TryUpdate(PropertyInfo prop, object value) {
await Task.Delay(0); // Added to satisfy async
try {
prop.SetValue(this, value);
// If not set probably a complex type
if (value != prop.GetValue(this)) {
//... Don't know what to do
}
return true;
}
catch {
return false;
}
}
}
public interface IComplexObject { }
class ComplexObject : IComplexObject {
public override string ToString() => "hello this is complexobject";
}

Categories