I have an ModelChangeInfo object that define two enums : ModelType and ChangeType. When i deserialize ModelChangeInfo object, only ChangeTypevalue can deserialize, ModelType cannot, ModelType value is null.
This is my code
public class ModelChangeInfo
{
public ModelChangeInfo(Guid id, ModelType? modelType, ChangeType? changeType, ParentInfo parentInfo = null)
{
Id = id;
ElementType = modelType;
ChangeType = changeType;
ParentInfo = parentInfo;
}
/// <summary>
/// Gets model's id
/// </summary>
public Guid Id { get; private set; }
/// <summary>
/// Gets model's element type
/// </summary>
public ModelType? ElementType { get; private set; }
/// <summary>
/// Gets change type
/// </summary>
public ChangeType? ChangeType { get; private set; }
/// <summary>
/// Gets parent information
/// </summary>
public ParentInfo ParentInfo { get; private set; }
public override string ToString()
{
return string.Format("ElementType {0}. ChangeType {1}. Parent ElementType {2}", ElementType, ChangeType, ParentInfo.ElementType);
}
}
public class ParentInfo
{
public ParentInfo(Guid id, ModelType? modelType)
{
Id = id;
ElementType = modelType;
}
/// <summary>
/// Gets model's id
/// </summary>
public Guid Id { get; private set; }
/// <summary>
/// Gets model's element type
/// </summary>
public ModelType? ElementType { get; private set; }
}
[JsonConverter(typeof(StringEnumConverter))]
public enum ModelType
{
Network,
TradingPartner,
Transaction,
Task,
File
}
[JsonConverter(typeof(StringEnumConverter))]
public enum ChangeType
{
Add,
Update,
Delete
}
public class JsonDemo
{
static void Main(string[] args)
{
ParentInfo parentInfo = new ParentInfo(Guid.NewGuid(), ModelType.TradingPartner);
ModelChangeInfo modelChangeInfo = new ModelChangeInfo(Guid.NewGuid(), ModelType.File, ChangeType.Update, parentInfo);
JsonSerializer serializer = new JsonSerializer();
using (StreamWriter sw = new StreamWriter(#"C:\json.txt"))
{
using (JsonWriter writer = new JsonTextWriter(sw))
{
serializer.Serialize(writer, modelChangeInfo);
}
}
using (StreamReader sr = new StreamReader(#"D:\json.txt"))
{
using (JsonReader reader = new JsonTextReader(sr))
{
ModelChangeInfo temp = serializer.Deserialize<ModelChangeInfo>(reader);
Console.WriteLine(temp);
}
}
Console.Read();
}
}
Related
I am using the summary and example tags for the swagger documentation.
I have a problem with the tag, it's not recognized by swagger when I use array :
I use swashbuckle.aspnetcore package Nuget.
Example :
[DataContract]
public class HeaderResponse
{
/// <summary>
/// Statut code
/// </summary>
///<example>400</example>
[DataMember]
public int StatusCode { get; set; }
/// <summary>
/// Title
/// </summary>
/// <example>Erreur sur methode1</example>
[DataMember]
public string Title { get; set; }
/// <summary>
/// List of errors
/// </summary>
///<example>["entry1", "entry2", "entry3"]</example>
[DataMember]
public List<string> ErrorList { get; } = new List<string>();
}
On swagger documentation, array is not interpreted :
I found others solutions by using ISchemaFilter like this :
public class SwaggerExcludeFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
switch (context.Type.Name)
{
case "HeaderResponse":
foreach (var property in schema.Properties)
{
if (property.Value.Type == "array")
{
var array = new OpenApiArray();
array.Add(new OpenApiString("item1"));
array.Add(new OpenApiString("item2"));
array.Add(new OpenApiString("item3"));
property.Value.Example = array;
}
}
break;
}
}
}
Is there no other way than to use ISchemaFilter to handle tags of type array?
You can try to do like this:
public List<string> ErrorList { get; } = new List<string>{"entry1", "entry2", "entry3"};
or:
[DataContract]
public class HeaderResponse
{
public HeaderResponse()
{
ErrorList = new List<string> {"entry1", "entry2", "entry3" };
}
/// <summary>
/// Statut code
/// </summary>
///<example>400</example>
[DataMember]
public int StatusCode { get; set; }
/// <summary>
/// Title
/// </summary>
/// <example>Erreur sur methode1</example>
[DataMember]
public string Title { get; set; }
/// <summary>
/// List of errors
/// </summary>
[DataMember]
public List<string> ErrorList { get; set; }
}
Here is a demo:
[HttpPost("TestPar")]
public IActionResult TestPar(HeaderResponse h)
{
return Json(h);
}
result:
The quotes must be escaped using "
/// <summary>
/// List of errors
/// </summary>
///<example>["entry1","entry2","entry3"]</example>
[DataMember]
public List<string> ErrorList { get; } = new List<string>();
I have the same problem and I try this. you can add a condition on the name of the property and add the null return in order not to lose the example on the other properties. But the best solution would be to be able to find a generic solution. How is it possible to get the example tag and the property name with ISchemaFilter ? This is my solution :
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
schema.Example = GetExampleOrNullFor(schema, context);
}
private IOpenApiAny GetExampleOrNullFor(OpenApiSchema schema, SchemaFilterContext context)
{
switch (context.Type.Name)
{
case "MyClass":
foreach (var property in schema.Properties)
{
//Array property, which wraps its elements
if (property.Value.Type == "array")
{
if (property.Key == "MyProperty1")
{
var array = new OpenApiArray();
array.Add(new OpenApiString("item1"));
array.Add(new OpenApiString("item2"));
property.Value.Example = array;
}
else if (property.Key == "MyProperty2")
{
var array = new OpenApiArray();
array.Add(new OpenApiString("item1"));
property.Value.Example = array;
}
}
}
return null;
default:
return null;
}
}
Support for examples using XML comments on arrays was added in Swashbuckle.AspNetCore version 6.0.0.
It's mentioned in this GitHub issue and their documentation shows the following example:
/// <summary>
/// The sizes the product is available in
/// </summary>
/// <example>["Small", "Medium", "Large"]</example>
public List<string> Sizes { get; set; }
I have a data model consisting of a couple of properties and methods, this data model inherits from an interface. However, I've now created a static extension and would like to set one of my model properties to the result of that extension method.
These are my models and extensions:
PublishedContentModel:
namespace CMS.Core.Models
{
public class PublishedContentModel : IPublishedContent
{
/// <summary>
/// The id of this node.
/// </summary>
public Int32 Id { get; set; }
/// <summary>
/// The name of this node.
/// </summary>
public String Name { get; set; }
/// <summary>
/// The url path.
/// </summary>
public String Path { get; set; }
/// <summary>
/// This nodes document id.
/// </summary>
public Int32 DocumentId { get; set; }
/// <summary>
/// The parent node's id.
/// </summary>
public IPublishedContent Parent { get; set; }
/// <summary>
/// The collection of this node's children.
/// </summary>
public IEnumerable<IPublishedContent> Children { get; set; }
/// <summary>
/// The collection of ancestors.
/// </summary>
public IEnumerable<IPublishedContent> Ancestors ()
{
IEnumerable<IPublishedContent> colContentNodes = ContentService.GetAllContentNodes();
List<IPublishedContent> colAncestors = new List<IPublishedContent>();
foreach (IPublishedContent objContentNode in colContentNodes.Where(x => x.Descendants().Any(y => y.Id == Id)))
{
colAncestors.Add(objContentNode);
}
return colAncestors;
}
/// <summary>
/// The collection of all nodes under this.
/// </summary>
public IEnumerable<IPublishedContent> Descendants ()
{
// - Get the children and create a collection for descendants.
IEnumerable<IPublishedContent> colChildren = Children;
List<IPublishedContent> colDescendants = new List<IPublishedContent>();
if (colChildren.Any())
{
colDescendants = colChildren.ToList();
foreach (IPublishedContent objChild in colChildren)
{
IEnumerable<IPublishedContent> colChildsChildren = objChild.Children;
// - Check if this node has children.
if (colChildsChildren.Any())
{
colDescendants.AddRange(colChildsChildren);
}
}
}
return colDescendants;
}
}
}
ContentModelExtensions:
namespace CMS.Core.Models
{
public static class ContentModelExtensions
{
public static IEnumerable<IPublishedContent> Children (this IPublishedContent Content)
{
IEnumerable<IPublishedContent> colContentNodes = ContentService.GetAllContentNodes();
List<IPublishedContent> colChildren = new List<IPublishedContent>();
foreach (IPublishedContent objContentNode in colContentNodes.Where(x => x.Parent != null))
{
colChildren.Add(objContentNode);
}
return colChildren;
}
public static IEnumerable<T> Children<T> (this IPublishedContent Content) where T : class, IPublishedContent
{
IEnumerable<IPublishedContent> colContentNodes = ContentService.GetAllContentNodes();
List<T> colChildren = new List<T>();
foreach (T objContentNode in colContentNodes.Where(x => x.Parent != null))
{
colChildren.Add(objContentNode);
}
return colChildren;
}
}
}
The issue with this code is that I can't do Children.Any() seeing as the IEnumerable for this is null. I tried fixing this by setting the Children property when it gets sent to the view, like this:
objContentNode = new PublishedContentModel ()
{
Id = Convert.ToInt32(objReader["Id"]),
Name = Convert.ToString(objReader["Name"]),
Parent = GetContentNodeById(Convert.ToInt32(objReader["ParentId"])),
Path = Convert.ToString(objReader["Path"]),
DocumentId = Convert.ToInt32(objReader["DocumentId"]),
Children = ContentModelExtensions.Children(objContentNode)
};
Put that just makes the connection timeout, any help would be appreciated. Let me know if I haven't explained my issue well enough.
TLDR:
I want to set public IEnumerable<IPublishedContent> Children { get; set; } to the result of ContentModelExtensions.Children()
As far as I know, protobuf-net starting from v2 supports references but they cannot be used in conjunction with surrogate (exception "A reference-tracked object changed reference during deserialization" is thrown in this case)
I'm wondering if there is some workaround that I didn't consider to make it works.
Here following the code of my test case that reproduces the above exception.
Classes
public class Person
{
public Person(string name, GenderType gender)
{
Name = name;
Gender = gender;
}
public string Name { get; set; }
public GenderType Gender { get; set; }
}
[Flags]
public enum GenderType : byte
{
Male = 1,
Female = 2,
Both = Male | Female
}
public class Family
{
public Family(List<Person> people, Person familyHead = null)
{
People = people;
FamilyHead = familyHead;
}
public List<Person> People { get; set; }
public Person FamilyHead { get; set; }
}
public class PersonSurrogate
{
public string Name { get; set; }
public byte Gender { get; set; }
public PersonSurrogate(string name, byte gender)
{
Name = name;
Gender = gender;
}
#region Static Methods
public static implicit operator Person(PersonSurrogate surrogate)
{
if (surrogate == null) return null;
return new Person(surrogate.Name, (GenderType)surrogate.Gender);
}
public static implicit operator PersonSurrogate(Person source)
{
return source == null ? null : new PersonSurrogate(source.Name, (byte)source.Gender);
}
#endregion
}
public class FamilySurrogate
{
public FamilySurrogate(List<Person> people, Person familyHead)
{
People = people;
FamilyHead = familyHead;
}
public List<Person> People { get; set; }
public Person FamilyHead { get; set; }
#region Static Methods
public static implicit operator Family(FamilySurrogate surrogate)
{
if (surrogate == null) return null;
return new Family(surrogate.People, surrogate.FamilyHead);
}
public static implicit operator FamilySurrogate(Family source)
{
return source == null ? null : new FamilySurrogate(source.People, source.FamilyHead);
}
#endregion
}
Serializer
/// <summary>
/// Class with model for protobuf serialization
/// </summary>
public class FamilySerializer
{
public GenderType GenderToInclude;
public FamilySerializer(Family family, GenderType genderToInclude = GenderType.Both)
{
GenderToInclude = genderToInclude;
Family = family;
Init();
}
private void Init()
{
Model = RuntimeTypeModel.Create();
FillModel();
Model.CompileInPlace();
}
public FamilySerializer()
{
Init();
}
public Family Family { get; set; }
public RuntimeTypeModel Model { get; protected set; }
protected virtual void FillModel()
{
Model = RuntimeTypeModel.Create();
Model.Add(typeof(Family), false)
.SetSurrogate(typeof(FamilySurrogate));
MetaType mt = Model[typeof(FamilySurrogate)];
mt.Add(1, "People");
mt.AddField(2, "FamilyHead").AsReference = true; // Exception "A reference-tracked object changed reference during deserialization" - because using surrogate.
mt.UseConstructor = false;
Model.Add(typeof(Person), false)
.SetSurrogate(typeof(PersonSurrogate));
mt = Model[typeof(PersonSurrogate)]
.Add(1, "Name")
.Add(2, "Gender");
mt.UseConstructor = false; // Avoids to use the parameterless constructor.
}
public void Save(string fileName)
{
using (Stream s = File.Open(fileName, FileMode.Create, FileAccess.Write))
{
Model.Serialize(s, Family, new ProtoBuf.SerializationContext(){Context = this});
}
}
public void Open(string fileName)
{
using (Stream s = File.Open(fileName, FileMode.Open, FileAccess.Read))
{
Family = (Family)Model.Deserialize(s, null, typeof(Family), new ProtoBuf.SerializationContext(){Context = this});
}
}
}
Test Case
private Family FamilyTestCase(string fileName, bool save)
{
if (save)
{
var people = new List<Person>()
{
new Person("Angus", GenderType.Male),
new Person("John", GenderType.Male),
new Person("Katrina", GenderType.Female),
};
var fam = new Family(people, people[0]);
var famSer = new FamilySerializer(fam);
famSer.Save(fileName);
return fam;
}
else
{
var famSer = new FamilySerializer();
famSer.Open(fileName);
if (Object.ReferenceEquals(fam.People[0], fam.FamilyHead))
{
// I'd like this condition would be satisfied
}
return famSer.Family;
}
}
I think for now this is just an unsupported scenario and I'm unaware of a way to make it magically work; it may be something that I can get back to at some point, but there are many much higher priority things that would take precedence.
My usual advice here - and this applies to any serializer, not just protobuf-net: any time you find yourself hitting a limitation of the serializer, or even just something that is awkward to configure in the serializer: stop fighting the serializer. This kind of problem almost always arises when people try to serialize their regular domain model, and something in the domain model isn't a perfect fit for their chosen serializer. Instead of trying arcane magic: split your model - have your domain model be a good fit for what you want your application to see, and create a separate model that is a great fit for your serializer. Then you don't need concepts like "surrogates". If you're using multiple serialization formats, or have multiple different "versions" of layout in the same serialization format: have multiple serialization models.
It really isn't worth the headache of trying to make on model serve multiple masters.
Since I understand that will be not a supported scenario, I found a way to handle this and I'd like to share my complete solution, in case someone else needs this (or if someone else wanted to share a better solution or improve my approach)
Classes
public class Person
{
public Person(string name, GenderType gender)
{
Name = name;
Gender = gender;
}
public string Name { get; set; }
public GenderType Gender { get; set; }
}
[Flags]
public enum GenderType : byte
{
Male = 1,
Female = 2,
Both = Male | Female
}
public class Family
{
public Family(List<Person> people, Person familyHead = null)
{
People = people;
FamilyHead = familyHead;
}
public List<Person> People { get; set; }
public Person FamilyHead { get; set; }
}
#region Interfaces
/// <summary>
/// Interface for objects supporting the object graph reference.
/// </summary>
public interface ISurrogateWithReferenceId
{
/// <summary>
/// Gets or sets the id for the object referenced more than once during the process of serialization/deserialization.
/// </summary>
/// <remarks>Default value is -1.</remarks>
int ReferenceId { get; set; }
}
#endregion
public class PersonSurrogate : ISurrogateWithReferenceId
{
/// <summary>
/// Standard constructor.
/// </summary>
public PersonSurrogate(string name, byte gender)
{
Name = name;
Gender = gender;
ReferenceId = -1;
}
/// <summary>
/// Private constructor for object graph reference handling.
/// </summary>
private PersonSurrogate(int referenceId)
{
ReferenceId = referenceId;
}
public string Name { get; set; }
public byte Gender { get; set; }
#region object graph reference
/// <summary>
/// Gets the unique id assigned to the surrogate during the process of serialization/deserialization to handle object graph reference.
/// </summary>
/// <remarks>Default value is -1.</remarks>
public int ReferenceId { get; set; }
public override bool Equals(object obj)
{
return base.Equals(obj) || (ReferenceId > 0 && obj is ISurrogateWithReferenceId oi && oi.ReferenceId == ReferenceId);
}
public override int GetHashCode()
{
if (ReferenceId > 0)
return ReferenceId;
return base.GetHashCode();
}
#endregion object graph reference
protected virtual bool CheckSurrogateData(GenderType gender)
{
return gender == GenderType.Both || (GenderType)Gender == gender;
}
#region Static Methods
/// <summary>
/// Converts the surrogate to the related object during the deserialization process.
/// </summary>
public static implicit operator Person(PersonSurrogate surrogate)
{
if (surrogate == null) return null;
if (FamilySerializer.GetCachedObject(surrogate) is Person obj)
return obj;
obj = new Person(surrogate.Name, (GenderType)surrogate.Gender);
FamilySerializer.AddToCache(surrogate, obj);
return obj;
}
/// <summary>
/// Converts the object to the related surrogate during the serialization process.
/// </summary>
public static implicit operator PersonSurrogate(Person source)
{
if (source == null) return null;
if (FamilySerializer.GetCachedObjectWithReferenceId(source) is PersonSurrogate surrogate)
{
surrogate = new PersonSurrogate(surrogate.ReferenceId);
}
else
{
surrogate = new PersonSurrogate(source.Name, (byte)source.Gender);
FamilySerializer.AddToCache(source, surrogate);
}
return surrogate;
}
#endregion
}
public class FamilySurrogate
{
public FamilySurrogate(List<Person> people, Person familyHead)
{
People = people;
FamilyHead = familyHead;
}
public List<Person> People { get; set; }
public Person FamilyHead { get; set; }
#region Static Methods
public static implicit operator Family(FamilySurrogate surrogate)
{
if (surrogate == null) return null;
return new Family(surrogate.People, surrogate.FamilyHead);
}
public static implicit operator FamilySurrogate(Family source)
{
return source == null ? null : new FamilySurrogate(source.People, source.FamilyHead);
}
#endregion
}
Serializer
/// <summary>
/// Class with model for protobuf serialization
/// </summary>
public class FamilySerializer
{
public GenderType GenderToInclude;
public FamilySerializer(Family family, GenderType genderToInclude = GenderType.Both)
{
GenderToInclude = genderToInclude;
Family = family;
Init();
}
private void Init()
{
Model = RuntimeTypeModel.Create();
FillModel();
Model.CompileInPlace();
}
public FamilySerializer()
{
Init();
}
public Family Family { get; set; }
public RuntimeTypeModel Model { get; protected set; }
protected virtual void FillModel()
{
Model = RuntimeTypeModel.Create();
Model.Add(typeof(Family), false)
.SetSurrogate(typeof(FamilySurrogate));
MetaType mt = Model[typeof(FamilySurrogate)];
mt.Add(1, "People"); // This is a list of Person of course
//mt.AddField(2, "FamilyHead").AsReference = true; // Exception "A reference-tracked object changed reference during deserialization" - because using surrogate.
mt.Add(2, "FamilyHead");
mt.UseConstructor = false;
Model.Add(typeof(Person), false)
.SetSurrogate(typeof(PersonSurrogate));
mt = Model[typeof(PersonSurrogate)]
.Add(1, "Name")
.Add(2, "Gender")
.Add(3, "ReferenceId");
mt.UseConstructor = false; // Avoids to use the parameter-less constructor.
}
#region Cache
static FamilySerializer()
{
ResizeCache();
}
/// <summary>
/// Resizes the cache for object graph reference handling.
/// </summary>
/// <param name="size"></param>
public static void ResizeCache(int size = 500)
{
if (_cache != null)
{
foreach (var pair in _cache)
{
pair.Value.ResetCache();
}
}
_cache = new ConcurrentDictionary<int, FamilySerializerCache>();
for (var i = 0; i < size; i++)
_cache.TryAdd(i, new FamilySerializerCache());
}
private static ConcurrentDictionary<int, FamilySerializerCache> _cache;
/// <summary>
/// For internal use only. Adds the specified key and value to the serializer cache for the current thread during the serialization process.
/// </summary>
/// <param name="objKey">The the element to add as key.</param>
/// <param name="objValue">The value of the element to add.</param>
/// <remarks>The <see cref="ISurrogateWithReferenceId.ReferenceId"/> is updated for <see cref="objValue"/></remarks>
public static void AddToCache(object objKey, ISurrogateWithReferenceId objValue)
{
_cache[Thread.CurrentThread.ManagedThreadId].AddToCache(objKey, objValue);
}
/// <summary>
/// For internal use only. Adds the specified key and value to the serializer cache for the current thread during the serialization process.
/// </summary>
/// <param name="objKey">The the element to add as key.</param>
/// <param name="objValue">The value of the element to add.</param>
/// <remarks>The <see cref="ISurrogateWithReferenceId.ReferenceId"/> is updated for <see cref="objKey"/></remarks>
public static void AddToCache(ISurrogateWithReferenceId objKey, object objValue)
{
_cache[Thread.CurrentThread.ManagedThreadId].AddToCache(objKey, objValue);
}
/// <summary>
/// For internal use only. Resets the cache for the current thread.
/// </summary>
public static void ResetCache()
{
_cache[Thread.CurrentThread.ManagedThreadId].ResetCache();
}
/// <summary>
/// For internal use only. Gets the <see cref="ISurrogateWithReferenceId"/> associated with the specified object for the current thread.
/// </summary>
/// <param name="obj">The object corresponding to the value to get.</param>
/// <returns>The related ISurrogateWithReferenceId if presents, otherwise null.</returns>
public static ISurrogateWithReferenceId GetCachedObjectWithReferenceId(object obj)
{
return _cache[Thread.CurrentThread.ManagedThreadId].GetCachedObjectWithReferenceId(obj);
}
/// <summary>
/// For internal use only. Gets the object associated with the specified <see cref="ISurrogateWithReferenceId"/>.
/// </summary>
/// <param name="surrogateWithReferenceId">The <see cref="ISurrogateWithReferenceId"/> corresponding to the object to get.</param>
/// <returns>The related object if presents, otherwise null.</returns>
public static object GetCachedObject(ISurrogateWithReferenceId surrogateWithReferenceId)
{
return _cache[Thread.CurrentThread.ManagedThreadId].GetCachedObject(surrogateWithReferenceId);
}
#endregion Cache
public void Save(string fileName)
{
using (Stream s = File.Open(fileName, FileMode.Create, FileAccess.Write))
{
Model.Serialize(s, Family, new ProtoBuf.SerializationContext(){Context = this});
}
}
public void Open(string fileName)
{
using (Stream s = File.Open(fileName, FileMode.Open, FileAccess.Read))
{
Family = (Family)Model.Deserialize(s, null, typeof(Family), new ProtoBuf.SerializationContext(){Context = this});
}
}
}
Serializer cache
/// <summary>
/// Helper class to support object graph reference
/// </summary>
internal class FamilySerializerCache
{
// weak table for serialization
// ConditionalWeakTable uses ReferenceEquals() rather than GetHashCode() and Equals() methods to do equality checks, so I can use it as a cache during the writing process to overcome the issue with objects that have overridden the GetHashCode() and Equals() methods.
private ConditionalWeakTable<object, ISurrogateWithReferenceId> _writingTable = new ConditionalWeakTable<object, ISurrogateWithReferenceId>();
// dictionary for deserialization
private readonly Dictionary<ISurrogateWithReferenceId, object> _readingDictionary = new Dictionary<ISurrogateWithReferenceId, object>();
private int _referenceIdCounter = 1;
/// <summary>
/// Gets the value associated with the specified key during serialization process.
/// </summary>
/// <param name="key">The key of the value to get.</param>
/// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the <paramref name="value" /> parameter. This parameter is passed uninitialized.</param>
/// <returns>True if the internal dictionary contains an element with the specified key, otherwise False.</returns>
private bool TryGetCachedObject(object key, out ISurrogateWithReferenceId value)
{
return _writingTable.TryGetValue(key, out value);
}
/// <summary>
/// Gets the value associated with the specified key during deserialization process.
/// </summary>
/// <param name="key">The key of the value to get.</param>
/// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the <paramref name="value" /> parameter. This parameter is passed uninitialized.</param>
/// <returns>True if the internal dictionary contains an element with the specified key, otherwise False.</returns>
private bool TryGetCachedObject(ISurrogateWithReferenceId key, out object value)
{
return _readingDictionary.TryGetValue(key, out value);
}
/// <summary>
/// Resets the internal dictionaries and the counter;
/// </summary>
public void ResetCache()
{
_referenceIdCounter = 1;
_readingDictionary.Clear();
// ConditionalWeakTable automatically removes the key/value entry as soon as no other references to a key exist outside the table, but I want to clean it as well.
_writingTable = new ConditionalWeakTable<object, ISurrogateWithReferenceId>();
}
/// <summary>
/// Adds the specified key and value to the internal dictionary during serialization process.
/// </summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value of the element to add.</param>
/// <remarks>If the object implements <see cref="ISurrogateWithReferenceId"/> interface then <see cref="ISurrogateWithReferenceId.ReferenceId"/> is updated.</remarks>
public void AddToCache(object key, ISurrogateWithReferenceId value)
{
if (value.ReferenceId == -1)
value.ReferenceId = _referenceIdCounter++;
_writingTable.Add(key, value);
}
/// <summary>
/// Adds the specified key and value to the internal dictionary during deserialization process.
/// </summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value of the element to add.</param>
/// <remarks>If the object implements <see cref="ISurrogateWithReferenceId"/> interface then <see cref="ISurrogateWithReferenceId.ReferenceId"/> is updated.</remarks>
public void AddToCache(ISurrogateWithReferenceId key, object value)
{
if (key.ReferenceId == -1)
key.ReferenceId = _referenceIdCounter++;
_readingDictionary.Add(key, value);
}
/// <summary>
/// Gets the <see cref="ISurrogateWithReferenceId"/> associated with the specified object.
/// </summary>
/// <param name="obj">The object corresponding to the value to get.</param>
/// <returns>The related ISurrogateWithReferenceId if presents, otherwise null.</returns>
public ISurrogateWithReferenceId GetCachedObjectWithReferenceId(object obj)
{
if (TryGetCachedObject(obj, out ISurrogateWithReferenceId value))
return value;
return null;
}
/// <summary>
/// Gets the object associated with the specified <see cref="ISurrogateWithReferenceId"/>.
/// </summary>
/// <param name="surrogateWithReferenceId">The <see cref="ISurrogateWithReferenceId"/> corresponding to the object to get.</param>
/// <returns>The related object if presents, otherwise null.</returns>
public object GetCachedObject(ISurrogateWithReferenceId surrogateWithReferenceId)
{
if (TryGetCachedObject(surrogateWithReferenceId, out object value))
return value;
return null;
}
}
Test Case
private Family FamilyTestCase(string fileName, bool save)
{
if (save)
{
var people = new List<Person>()
{
new Person("Angus", GenderType.Male),
new Person("John", GenderType.Male),
new Person("Katrina", GenderType.Female),
};
var fam = new Family(people, people[0]);
var famSer = new FamilySerializer(fam);
famSer.Save(fileName);
return fam;
}
else
{
var famSer = new FamilySerializer();
famSer.Open(fileName);
if (Object.ReferenceEquals(fam.People[0], fam.FamilyHead))
{
Console.WriteLine("Family head is the same than People[0]!");
}
return famSer.Family;
}
}
I have following JSON string which is received from an external party.
{
"team":[
{
"v1":"",
"attributes":{
"eighty_min_score":"",
"home_or_away":"home",
"score":"22",
"team_id":"500"
}
},
{
"v1":"",
"attributes":{
"eighty_min_score":"",
"home_or_away":"away",
"score":"30",
"team_id":"600"
}
}
]
}
My mapping classes:
public class Attributes
{
public string eighty_min_score { get; set; }
public string home_or_away { get; set; }
public string score { get; set; }
public string team_id { get; set; }
}
public class Team
{
public string v1 { get; set; }
public Attributes attributes { get; set; }
}
public class RootObject
{
public List<Team> team { get; set; }
}
The question is that I don't like the Attributes class name and the attributes field names in the Team class. Instead, I want it to be named TeamScore and also to remove _ from the field names and give proper names.
JsonConvert.DeserializeObject<RootObject>(jsonText);
I can rename Attributes to TeamScore, but if I change the field name (attributes in the Team class), it won't deserialize properly and gives me null. How can I overcome this?
Json.NET - Newtonsoft has a JsonPropertyAttribute which allows you to specify the name of a JSON property, so your code should be:
public class TeamScore
{
[JsonProperty("eighty_min_score")]
public string EightyMinScore { get; set; }
[JsonProperty("home_or_away")]
public string HomeOrAway { get; set; }
[JsonProperty("score ")]
public string Score { get; set; }
[JsonProperty("team_id")]
public string TeamId { get; set; }
}
public class Team
{
public string v1 { get; set; }
[JsonProperty("attributes")]
public TeamScore TeamScores { get; set; }
}
public class RootObject
{
public List<Team> Team { get; set; }
}
Documentation: Serialization Attributes
If you'd like to use dynamic mapping, and don't want to clutter up your model with attributes, this approach worked for me
Usage:
var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new CustomContractResolver();
this.DataContext = JsonConvert.DeserializeObject<CountResponse>(jsonString, settings);
Logic:
public class CustomContractResolver : DefaultContractResolver
{
private Dictionary<string, string> PropertyMappings { get; set; }
public CustomContractResolver()
{
this.PropertyMappings = new Dictionary<string, string>
{
{"Meta", "meta"},
{"LastUpdated", "last_updated"},
{"Disclaimer", "disclaimer"},
{"License", "license"},
{"CountResults", "results"},
{"Term", "term"},
{"Count", "count"},
};
}
protected override string ResolvePropertyName(string propertyName)
{
string resolvedName = null;
var resolved = this.PropertyMappings.TryGetValue(propertyName, out resolvedName);
return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
}
}
Adding to Jacks solution. I need to Deserialize using the JsonProperty and Serialize while ignoring the JsonProperty (or vice versa). ReflectionHelper and Attribute Helper are just helper classes that get a list of properties or attributes for a property. I can include if anyone actually cares. Using the example below you can serialize the viewmodel and get "Amount" even though the JsonProperty is "RecurringPrice".
/// <summary>
/// Ignore the Json Property attribute. This is usefule when you want to serialize or deserialize differently and not
/// let the JsonProperty control everything.
/// </summary>
/// <typeparam name="T"></typeparam>
public class IgnoreJsonPropertyResolver<T> : DefaultContractResolver
{
private Dictionary<string, string> PropertyMappings { get; set; }
public IgnoreJsonPropertyResolver()
{
this.PropertyMappings = new Dictionary<string, string>();
var properties = ReflectionHelper<T>.GetGetProperties(false)();
foreach (var propertyInfo in properties)
{
var jsonProperty = AttributeHelper.GetAttribute<JsonPropertyAttribute>(propertyInfo);
if (jsonProperty != null)
{
PropertyMappings.Add(jsonProperty.PropertyName, propertyInfo.Name);
}
}
}
protected override string ResolvePropertyName(string propertyName)
{
string resolvedName = null;
var resolved = this.PropertyMappings.TryGetValue(propertyName, out resolvedName);
return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
}
}
Usage:
var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new IgnoreJsonPropertyResolver<PlanViewModel>();
var model = new PlanViewModel() {Amount = 100};
var strModel = JsonConvert.SerializeObject(model,settings);
Model:
public class PlanViewModel
{
/// <summary>
/// The customer is charged an amount over an interval for the subscription.
/// </summary>
[JsonProperty(PropertyName = "RecurringPrice")]
public double Amount { get; set; }
/// <summary>
/// Indicates the number of intervals between each billing. If interval=2, the customer would be billed every two
/// months or years depending on the value for interval_unit.
/// </summary>
public int Interval { get; set; } = 1;
/// <summary>
/// Number of free trial days that can be granted when a customer is subscribed to this plan.
/// </summary>
public int TrialPeriod { get; set; } = 30;
/// <summary>
/// This indicates a one-time fee charged upfront while creating a subscription for this plan.
/// </summary>
[JsonProperty(PropertyName = "SetupFee")]
public double SetupAmount { get; set; } = 0;
/// <summary>
/// String representing the type id, usually a lookup value, for the record.
/// </summary>
[JsonProperty(PropertyName = "TypeId")]
public string Type { get; set; }
/// <summary>
/// Billing Frequency
/// </summary>
[JsonProperty(PropertyName = "BillingFrequency")]
public string Period { get; set; }
/// <summary>
/// String representing the type id, usually a lookup value, for the record.
/// </summary>
[JsonProperty(PropertyName = "PlanUseType")]
public string Purpose { get; set; }
}
Expanding Rentering.com's answer, in scenarios where a whole graph of many types is to be taken care of, and you're looking for a strongly typed solution, this class can help, see usage (fluent) below. It operates as either a black-list or white-list per type. A type cannot be both (Gist - also contains global ignore list).
public class PropertyFilterResolver : DefaultContractResolver
{
const string _Err = "A type can be either in the include list or the ignore list.";
Dictionary<Type, IEnumerable<string>> _IgnorePropertiesMap = new Dictionary<Type, IEnumerable<string>>();
Dictionary<Type, IEnumerable<string>> _IncludePropertiesMap = new Dictionary<Type, IEnumerable<string>>();
public PropertyFilterResolver SetIgnoredProperties<T>(params Expression<Func<T, object>>[] propertyAccessors)
{
if (propertyAccessors == null) return this;
if (_IncludePropertiesMap.ContainsKey(typeof(T))) throw new ArgumentException(_Err);
var properties = propertyAccessors.Select(GetPropertyName);
_IgnorePropertiesMap[typeof(T)] = properties.ToArray();
return this;
}
public PropertyFilterResolver SetIncludedProperties<T>(params Expression<Func<T, object>>[] propertyAccessors)
{
if (propertyAccessors == null)
return this;
if (_IgnorePropertiesMap.ContainsKey(typeof(T))) throw new ArgumentException(_Err);
var properties = propertyAccessors.Select(GetPropertyName);
_IncludePropertiesMap[typeof(T)] = properties.ToArray();
return this;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
var isIgnoreList = _IgnorePropertiesMap.TryGetValue(type, out IEnumerable<string> map);
if (!isIgnoreList && !_IncludePropertiesMap.TryGetValue(type, out map))
return properties;
Func<JsonProperty, bool> predicate = jp => map.Contains(jp.PropertyName) == !isIgnoreList;
return properties.Where(predicate).ToArray();
}
string GetPropertyName<TSource, TProperty>(
Expression<Func<TSource, TProperty>> propertyLambda)
{
if (!(propertyLambda.Body is MemberExpression member))
throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property.");
if (!(member.Member is PropertyInfo propInfo))
throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property.");
var type = typeof(TSource);
if (!type.GetTypeInfo().IsAssignableFrom(propInfo.DeclaringType.GetTypeInfo()))
throw new ArgumentException($"Expresion '{propertyLambda}' refers to a property that is not from type '{type}'.");
return propInfo.Name;
}
}
Usage:
var resolver = new PropertyFilterResolver()
.SetIncludedProperties<User>(
u => u.Id,
u => u.UnitId)
.SetIgnoredProperties<Person>(
r => r.Responders)
.SetIncludedProperties<Blog>(
b => b.Id)
.Ignore(nameof(IChangeTracking.IsChanged)); //see gist
I am using JsonProperty attributes when serializing but ignoring them when deserializing using this ContractResolver:
public class IgnoreJsonPropertyContractResolver: DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
foreach (var p in properties) { p.PropertyName = p.UnderlyingName; }
return properties;
}
}
The ContractResolver just sets every property back to the class property name (simplified from Shimmy's solution). Usage:
var airplane= JsonConvert.DeserializeObject<Airplane>(json,
new JsonSerializerSettings { ContractResolver = new IgnoreJsonPropertyContractResolver() });
Also if you want to ignore something use this
[JsonIgnore]
public int Id { get; set; }
[JsonProperty("id")]
Public string real_id { get; set; }
How should I write this portion of code to serialize all descendant custom types?
The question regards NETJson ( https://github.com/rpgmaker/NetJSON ).
Below the code used to test it.
NETJson Serializer implementation:
class NETJsonFormatter
{
static bool Initialize()
{
NetJSON.NetJSON.IncludeFields = true;
NetJSON.NetJSON.IncludeTypeInformation = true;
return true;
}
static bool Initialized = Initialize();
/// <summary>
/// Serializza un oggetto in un array di byte.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
[System.Diagnostics.DebuggerStepThrough]
static public byte[] Serialize(object obj)
{
return Encoding.UTF8.GetBytes(NetJSON.NetJSON.Serialize(obj));
}
/// <summary>
/// Trasforma un array di byte nell'oggetto originario.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
[System.Diagnostics.DebuggerStepThrough]
static public object Deserialize(byte[] obj)
{
return NetJSON.NetJSON.Deserialize<object>(Encoding.UTF8.GetString(obj));
}
/// <summary>
/// Deserializza un array di byte nel Type desiderato.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
static public T Deserialize<T>(byte[] obj)
{
return NetJSON.NetJSON.Deserialize<T>(Encoding.UTF8.GetString(obj));
}
}
The first object to serialize:
[Serializable]
public class ComplexType
{
public ComplexType()
{
this.Numero = 100;
this.Stringa = "Contenuto";
}
public int Numero { get; set; }
public string Stringa { get; set; }
}
The second object to serialize:
[Serializable]
public class Message_v2 : IMessage
{
public Message_v2()
{
this.Options = new List<string>();
this.Arguments = new Dictionary<string, object>();
}
public bool IsEmpty { get; set; }
public MessageCommand Command { get; set; }
public List<string> Options { get; set; }
/// <summary>
/// Gli Arguments del parser sono sempre KeyValue. Qual'ora mancasse il Value viene inserito null.
/// </summary>
public Dictionary<string, object> Arguments { get; set; }
/*
* Public methods
*/
public void AddOptions(params string[] options)
{
foreach (string option in options)
this.Options.Add(option);
}
public void AddArgument(string key, object value)
{
this.Arguments.Add(key, value);
}
public byte[] ToArray()
{
return NETJsonFormatter.Serialize(this);
}
public string ToXML()
{
throw new NotImplementedException();
}
/// <summary>
/// For debugging purposes.
/// </summary>
/// <returns></returns>
public string ToJSON()
{
return Encoding.UTF8.GetString(NETJsonFormatter.Serialize(this));
}
/*
* Conversions
*/
public static explicit operator Message_v2(byte[] source)
{
try
{
return NETJsonFormatter.Deserialize<Message_v2>(source);
}
catch
{
return null;
}
}
}
And the unit test which fails.
The first test, the one on ComplexObject, passes.
To ensure the data are consistent I use DeepEqual ( https://github.com/jamesfoster/DeepEqual - 'DeepEqual' on NUGet), which provides the method .ShouldDeepEqual used for object comparison.
[TestMethod]
public void CreateAndRetrieveMessage()
{
ComplexType complexArgument = new ComplexType();
byte[] serializedComplexArgument = NETJsonFormatter.Serialize(complexArgument);
ComplexType deserializedComplexArgument = NETJsonFormatter.Deserialize<ComplexType>(serializedComplexArgument);
deserializedComplexArgument.ShouldDeepEqual(complexArgument);
/* ------------------------ */
IMessage message = ProtocolHelper.CreateMessage();
message.Command = MessageCommand.Set;
message.AddOptions("keys");
message.AddArgument("Key1", "Contenuto");
message.AddArgument("Key2", 100);
message.AddArgument("Key3", complexArgument);
// Send over the wire.
byte[] serialized = message.ToArray();
// Get the Message sent.
var deserialized = ProtocolHelper.CreateMessage(serialized);
deserialized.ShouldDeepEqual(message);
}
from the wiki
Dictionary
- Value(Object) supported Dictionary, IList, Primitive Types, and Enums
I think this is your problem, just try to serialize an object dicitionnary, it will fail, but a complexType dictionnary succeed...
ComplexType complexArgument = new ComplexType();
byte[] serializedComplexArgument = NETJsonFormatter.Serialize(complexArgument);
ComplexType deserializedComplexArgument = NETJsonFormatter.Deserialize<ComplexType>(serializedComplexArgument);
deserializedComplexArgument.ShouldDeepEqual(complexArgument);
/* ------------------------ */
var complexTypeDictionnary = new Dictionary<string, ComplexType>();
complexTypeDictionnary.Add("Key3", complexArgument);
byte[] serializedDic2 = NETJsonFormatter.Serialize(complexTypeDictionnary);
var deserializeDictionnary2 = NETJsonFormatter.Deserialize<Dictionary<string, ComplexType>>(serializedDic2);
deserializeDictionnary2.ShouldDeepEqual(complexTypeDictionnary); // works
/* ------------------------ */
var objectDictionnary = new Dictionary<string, object>();
objectDictionnary.Add("Key1", "Contenuto");
objectDictionnary.Add("Key2", 100);
objectDictionnary.Add("Key3", complexArgument);
byte[] serializedDic = NETJsonFormatter.Serialize(objectDictionnary);
var deserializeDictionnary = NETJsonFormatter.Deserialize<Dictionary<string, object>>(serializedDic);
deserializeDictionnary.ShouldDeepEqual(objectDictionnary); // doesn't work
Edit
I used to play with NewtonSoft for Json serialization, maybe your searching for performance improvement or other but it works well with it ;)
/* ------------------------ */
var objectDictionnary = new Dictionary<string, object>();
objectDictionnary.Add("Key1", "Contenuto");
objectDictionnary.Add("Key2", 100);
objectDictionnary.Add("Key3", complexArgument);
byte[] serializedDicNewton = Encoding.UTF8.GetBytes(Newtonsoft.Json.JsonConvert.SerializeObject( objectDictionnary));
var deserializeDictionnaryNewton = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(Encoding.UTF8.GetString(serializedDicNewton));
deserializeDictionnaryNewton.ShouldDeepEqual(objectDictionnary); // works too
NETJson doesn't properly serialize a Dictionary cause Object it's the base Type but it can serialize a lot of primitives (included byte array) so I found a solution (well, a workaround for now cause my eyes bleed looking at code - but it works great).
Class Helper for NETJson
class NETJsonFormatter
{
public NETJsonFormatter() { }
static bool Initialize()
{
NetJSON.NetJSON.IncludeFields = true;
NetJSON.NetJSON.IncludeTypeInformation = true;
return true;
}
static bool Initialized = Initialize();
/// <summary>
/// Serializza un oggetto in un array di byte.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
//[System.Diagnostics.DebuggerStepThrough]
static public byte[] SerializeWithType(object obj)
{
return Encoding.UTF8.GetBytes(NetJSON.NetJSON.Serialize(obj.GetType(), obj));
}
/// <summary>
/// Trasforma un array di byte nell'oggetto originario.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
//[System.Diagnostics.DebuggerStepThrough]
static public object DeserializeWithType(Type type, byte[] obj)
{
return NetJSON.NetJSON.Deserialize(type, Encoding.UTF8.GetString(obj));
}
static public byte[] Serialize<T>(T obj)
{
return Encoding.UTF8.GetBytes(NetJSON.NetJSON.Serialize(obj));
}
/// <summary>
/// Deserializza un array di byte nel Type desiderato.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
static public T Deserialize<T>(byte[] obj)
{
return NetJSON.NetJSON.Deserialize<T>(Encoding.UTF8.GetString(obj));
}
}
Second object to serialize (which didn't succeed on already unit test)
[Serializable]
public class Message_v2 //: IMessage
{
public Message_v2()
{
this.Options = new List<string>();
this.Arguments = new Dictionary<string, object>();
this.ValuesForNETJson = new Dictionary<string, byte[]>();
this.TypesForNETJson = new Dictionary<string, Type>();
}
public bool IsEmpty { get; set; }
public MessageCommand Command { get; set; }
public List<string> Options { get; set; }
/// <summary>
/// Gli Arguments del parser sono sempre KeyValue. Qual'ora mancasse il Value viene inserito null.
/// </summary>
public Dictionary<string, object> Arguments { get; set; }
/// <summary>
/// Serializzo gli Arguments in byte array.
/// string - Key di Arguments[n]
/// byte[] - contenuto serializzato di Arguments[n]
/// </summary>
public IDictionary<string, byte[]> ValuesForNETJson { get; set; }
public IDictionary<string, Type> TypesForNETJson { get; set; }
/*
* Public methods
*/
public void AddOptions(params string[] options)
{
foreach (string option in options)
this.Options.Add(option);
}
public void AddArgument(string key, object value)
{
this.Arguments.Add(key, value);
this.ValuesForNETJson.Add(key, NETJsonFormatter.SerializeWithType(value));
this.TypesForNETJson.Add(key, value.GetType());
}
public byte[] ToArray()
{
//this.Arguments.ToDictionary(x => x.Value == null);
return NETJsonFormatter.Serialize(this);
}
/*
* Conversions
*/
public static explicit operator Message_v2(byte[] source)
{
try
{
Message_v2 message = NETJsonFormatter.Deserialize<Message_v2>(source);
int count = message.ValuesForNETJson.Count;
for (int i = 0; i < count; i++)
{
string key = message.Arguments.ElementAt(i).Key;
Type type = message.TypesForNETJson.ElementAt(i).Value;
byte[] value = message.ValuesForNETJson[key];
message.Arguments[key] = NETJsonFormatter.DeserializeWithType(type, value);
}
return message;
}
catch (Exception ex)
{
return null;
}
}
}
For sure my solution will be reworked cause it's not optimal, far from that.
Adding two Dictionary to the class make it grow and be larger than same class serialized using ProtoBuf, and make it slower.