TL;DR: I was trying to create a class which would hold nested JSON data.
I eventually solved my own problem, but #dbc was very helpful and they have a solution which may be slightly faster if you want to implement it their way. I have fully documented my solution, with example usage, and marked it as answered below.
I'm creating a project in which I intend to store lots of nested JSON data.
Instead of creating a hundred classes, each with their own variables/attributes, and then having to modify them every time I want to change something, I'd like to create a simple "dynamic object".
This object holds the root of all data, as well as all the children's data. In JSON, this is represented by:
{
"name":"foo",
"id":0,
"attributes":
{
"slippery":true,
"dangerous":true
},
"costs":
{
"move":1,
"place":2,
"destroy":3
}
}
where the root structure holds the data "name" and "id", as well as children "attributes" and "costs" each containing their own data.
I'm using the json.net library for this, and my current class looks like this:
public class Data : JObject
{
public void CreateChildUnderParent(string parent, string child)
{
Data obj = GetValueOfKey<Data>(parent);
if(obj != null)
obj.CreateChild(child);
}
public void CreateChild(string child)
{
AddKey(child, new Data());
}
public void AddKeyToParent(string parent, string key, JToken value)
{
Data parentObject = GetValueOfKey<Data>(parent);
if(parentObject != null)
parentObject.AddKey(key, value);
}
public void AddKey(string key, JToken value)
{
Add(key, value);
}
public void RemoveKeyFromParent(string parent, string key)
{
Data parentObject = GetValueOfKey<Data>(parent);
if(parentObject != null)
parentObject.RemoveKey(key);
}
public void RemoveKey(string key)
{
Remove(key);
}
public T GetValueFromParent<T>(string parent, string key)
{
Data parentObject = GetValueOfKey<Data>(parent);
if(parentObject != null)
return parentObject.GetValue(key).ToObject<T>();
return default;
}
public T GetValueOfKey<T>(string key)
{
foreach (var kvp in this)
if (kvp.Value is Data)
{
T value = ((Data)kvp.Value).GetValueOfKey<T>(key);
if (value != null)
return value;
}
JToken token = GetValue(key);
if(token != null)
return token.ToObject<T>(); //throws exception
return default;
}
}
I can add children just fine, but my issue comes when I try to access them. An InvalidCastException is thrown within my
public T GetValueOfKey<T>(string key)
method whenever I call it using
Data
as the generic type.
For example:
Data data = GetValueOfKey<Data>("attributes");
throws an exception. I'm not sure why this is happening, so any help would be greatly appreciated!
EDIT:
Here is the complete error log thrown:
InvalidCastException: Specified cast is not valid.
(wrapper castclass) System.Object.__castclass_with_cache(object,intptr,intptr)
Newtonsoft.Json.Linq.JToken.ToObject[T] () (at <97722d3abc9f4cf69f9e21e6770081b3>:0)
Data.GetValueOfKey[T] (System.String key) (at Assets/Scripts/Attributes/Object/Data.cs:74)
Data.AddKeyToParent (System.String parent, System.String key, Newtonsoft.Json.Linq.JToken value) (at Assets/Scripts/Attributes/Object/Data.cs:23)
DataController.Awake () (at Assets/Scripts/Controllers/DataController.cs:35)
and an example of instantiation which causes this exception:
public class DataController
{
void Awake()
{
Data data = new Data();
data.AddKey("name", "foo");
data.CreateChild("attributes");
data.AddKeyToParent("attributes", "slippery", true); //throws exception (line 35)
}
}
UPDATE (10/20/18):
Ok so I went through my code this afternoon and rewrote it as a wrapper class, now the root JObject is stored within a variable in my Data, and accessor methods adjust its properties.
However, I ran into a problem. Here's the updated class (minified to the problem):
public class Data
{
public JObject data;
public Data()
{
data = new JObject();
}
public void AddChild(string child)
{
data.Add(child, new JObject());
}
public void AddKeyWithValueToParent(string parent, string key, JToken value)
{
JObject parentObject = GetValueOfKey<JObject>(parent);
if(parentObject != null)
parentObject.Add(key, value);
}
public void AddKeyWithValue(string key, JToken value)
{
data.Add(key, value);
}
public T GetValueOfKey<T>(string key)
{
return GetValueOfKey<T>(key, data);
}
private T GetValueOfKey<T>(string key, JObject index)
{
foreach (var kvp in index)
if (kvp.Value is JObject)
{
T value = GetValueOfKey<T>(key, kvp.Value.ToObject<JObject>());
if (value != null)
return value;
}
JToken token = index.GetValue(key);
if (token != null)
return token.ToObject<T>();
return default;
}
}
And here is an example of how to construct a Data object, and use its methods:
public class DataController
{
void Awake() {
Data data = new Data();
data.AddKeyWithValue("name", "foo");
data.AddChild("attributes");
data.AddKeyWithValueToParent("attributes", "slippery", true);
}
}
So everything in terms of adding key-value pairs, and creating children works wonderfully! No InvalidCastException at all, yay! However, when I try to serialize the object through JsonConvert.SerializeObject(data), it doesn't fully serialize it.
I have the program output to the console to show the serialization, and it looks like this:
{"data":{"name":"foo","attributes":{}}}
I've already checked to make sure that when I call data.AddKeyWithValueToParent("attributes", "slippery", true), it does indeed find the JObject value with the key attributes and even appears to successfully add the new key-value pair "slippery":true under it. But for some reason, serializing the root object data does not seem to identify that anything lies within the attributes object. Thoughts?
What I think may be happening, is that the value returned from GetValueOfKey is not acting as a reference object, but rather an entirely new object, so changes to that are not reflected within the original object.
I figured it out! I was right, the value returned from my GetValueOfKey method was returning a completely new object, and not a reference to the instance it found. Looking through my code, that should have been immediately obvious, but I'm tired and I was hoping for everything to be easy haha.
Anyway, for anyone who ever has the same question, and is just looking for a simple way to store and read some nested key-value pairs using the Json.NET library, here is the finished class that will do that (also serializable, and deserializable using JsonConvert):
public class Data
{
[JsonProperty]
private JObject data;
public Data()
{
data = new JObject();
}
public void AddChildUnderParent(string parent, string child)
{
JObject parentObject = GetValueOfKey<JObject>(parent);
if (parentObject != null)
{
parentObject.Add(child, new JObject());
ReplaceObject(parent, parentObject);
}
}
public void AddChild(string child)
{
data.Add(child, new JObject());
}
public void AddKeyWithValueToParent(string parent, string key, JToken value)
{
JObject parentObject = GetValueOfKey<JObject>(parent);
if(parentObject != null)
{
parentObject.Add(key, value);
ReplaceObject(parent, parentObject);
}
}
public void AddKeyWithValue(string key, JToken value)
{
data.Add(key, value);
}
public void RemoveKeyFromParent(string parent, string key)
{
JObject parentObject = GetValueOfKey<JObject>(parent);
if (parentObject != null)
{
parentObject.Remove(key);
ReplaceObject(parent, parentObject);
}
}
public void RemoveKey(string key)
{
data.Remove(key);
}
public T GetValueFromParent<T>(string parent, string key)
{
JObject parentObject = GetValueOfKey<JObject>(parent);
if (parentObject != null)
return parentObject.GetValue(key).ToObject<T>();
return default;
}
public T GetValueOfKey<T>(string key)
{
return GetValueOfKey<T>(key, data);
}
private T GetValueOfKey<T>(string key, JObject index)
{
foreach (var kvp in index)
if (kvp.Value is JObject)
{
T value = GetValueOfKey<T>(key, (JObject)kvp.Value);
if (value != null)
return value;
}
JToken token = index.GetValue(key);
if (token != null)
{
data = token.Root.ToObject<JObject>();
return token.ToObject<T>();
}
return default;
}
public void ReplaceObject(string key, JObject replacement)
{
ReplaceObject(key, data, replacement);
}
private void ReplaceObject(string key, JObject index, JObject replacement)
{
foreach (var kvp in index)
if (kvp.Value is JObject)
ReplaceObject(key, (JObject)kvp.Value, replacement);
JToken token = index.GetValue(key);
if (token != null)
{
JToken root = token.Root;
token.Replace(replacement);
data = (JObject)root;
}
}
}
That should get anyone a good head start. I plan on updating my code with params modifiers in some places to allow for multiple calls, but for now I'm just happy that I got it working. You'll notice that I had to create a ReplaceObject method, because without it, the original private JObject data was never actually updated to account for the changes made to the variable returned from GetValueOfKey.
Anyway, a big thanks to #dbc for all their help during this whole thing, and I hope this post helps someone in the future!
-ShermanZero
EDIT:
So I spent a little more time developing the class, and I think I have it pinned down to a universal point where anyone could simply copy-paste and easily implement it into their own program. Although, I personally think that #dbc has a faster solution if you care about nanosecond-millisecond differences in speed. For my own personal use though, I don't think it will make much of a difference.
Here is my full implementation, complete with documentation and error logging:
public class Data
{
[JsonExtensionData]
private JObject root;
private Texture2D texture;
private char delimiter = ',';
/// <summary>
/// Creates a new Data class with the default delimiter.
/// </summary>
public Data()
{
root = new JObject();
}
/// <summary>
/// Creates a new Data class with a specified delimiter.
/// </summary>
/// <param name="delimiter"></param>
public Data(char delimiter) : this()
{
this.delimiter = delimiter;
}
/// <summary>
/// Adds a child node to the specified parent(s) structure, which is split by the delimiter, with the specified name.
/// </summary>
/// <param name="name"></param>
/// <param name="parents"></param>
public void AddChild(string name, string parents)
{
AddChild(name, parents.Split(delimiter));
}
/// <summary>
/// Adds a child node to the specified parent(s) structure with the specified name.
/// </summary>
/// <param name="name"></param>
/// <param name="parents"></param>
public void AddChild(string name, params string[] parents)
{
string lastParent;
JObject parentObject = ReturnParentObject(out lastParent, parents);
if (parentObject != null)
{
parentObject.Add(name, new JObject());
ReplaceObject(lastParent, parentObject, parents);
} else
{
string message = "";
foreach (string parent in parents)
message += parent + " -> ";
throw new ParentNotFoundException($"The parent '{ message.Substring(0, message.LastIndexOf("->")) }' was not found.");
}
}
/// <summary>
/// Adds a child node to the root structure with the specified name.
/// </summary>
/// <param name="name"></param>
public void AddChild(string name)
{
root.Add(name, new JObject());
}
/// <summary>
/// Adds the specified key-value pair to the specified parent(s) structure, which is split by the delimiter.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="parents"></param>
public void AddKeyWithValue(string key, JToken value, string parents)
{
AddKeyWithValue(key, value, parents.Split(delimiter));
}
/// <summary>
/// Adds the specified key-value pair to the specified parent(s) structure.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="parents"></param>
public void AddKeyWithValue(string key, JToken value, params string[] parents)
{
string lastParent;
JObject parentObject = ReturnParentObject(out lastParent, parents);
if (parentObject != null)
{
parentObject.Add(key, value);
ReplaceObject(lastParent, parentObject, parents);
} else
{
string message = "";
foreach (string parent in parents)
message += parent + " -> ";
throw new ParentNotFoundException($"The parent '{ message.Substring(0, message.LastIndexOf("->")) }' was not found.");
}
}
/// <summary>
/// Adds the specified key-value pair to the root structure.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void AddKeyWithValue(string key, JToken value)
{
root.Add(key, value);
}
/// <summary>
/// Removes the specified key from the specified parent(s) structure, which is split by the delimiter.
/// </summary>
/// <param name="key"></param>
/// <param name="parents"></param>
public void RemoveKey(string key, string parents)
{
RemoveKey(key, parents.Split(delimiter));
}
/// <summary>
/// Removes the specified key from the specified parent(s) structure.
/// </summary>
/// <param name="key"></param>
/// <param name="parents"></param>
public void RemoveKey(string key, params string[] parents)
{
string lastParent;
JObject parentObject = ReturnParentObject(out lastParent, parents);
if (parentObject != null)
{
parentObject.Remove(key);
ReplaceObject(lastParent, parentObject, parents);
} else
{
string message = "";
foreach (string parent in parents)
message += parent + " -> ";
throw new ParentNotFoundException($"The parent '{ message.Substring(0, message.LastIndexOf("->")) }' was not found.");
}
}
/// <summary>
/// Removes the specified key from the root structure.
/// </summary>
/// <param name="key"></param>
public void RemoveKey(string key)
{
root.Remove(key);
}
/// <summary>
/// Returns if the specified key is contained within the parent(s) structure, which is split by the delimiter.
/// </summary>
/// <param name="key"></param>
/// <param name="parents"></param>
/// <returns></returns>
public bool HasValue(string key, string parents)
{
return HasValue(key, parents.Split(delimiter));
}
/// <summary>
/// Returns if the specified key is contained within the parent(s) structure.
/// </summary>
/// <param name="key"></param>
/// <param name="parents"></param>
/// <returns></returns>
public bool HasValue(string key, params string[] parents)
{
//string lastParent = parents[parents.Length - 1];
//Array.Resize(ref parents, parents.Length - 1);
string lastParent;
JObject parentObject = ReturnParentObject(out lastParent, parents);
if (parentObject == null)
return false;
else if (parentObject == root && parents.Length > 0)
return false;
IDictionary<string, JToken> dictionary = parentObject;
return dictionary.ContainsKey(key);
}
/// <summary>
/// Returns the deepest parent object referenced by the parent(s).
/// </summary>
/// <param name="lastParent"></param>
/// <param name="parents"></param>
/// <returns></returns>
private JObject ReturnParentObject(out string lastParent, string[] parents)
{
lastParent = null;
if(parents.Length > 0)
{
lastParent = parents[parents.Length - 1];
Array.Resize(ref parents, parents.Length - 1);
return GetValueOfKey<JObject>(lastParent, parents);
}
return root;
}
/// <summary>
/// Returns the value of the specified key from the specified parent(s) structure, which is split by the delimiter.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="parents"></param>
/// <returns></returns>
public T GetValueOfKey<T>(string key, string parents)
{
return GetValueOfKey<T>(key, parents.Split(delimiter));
}
/// <summary>
/// Returns the value of the specified key from the specified parent(s) structure.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="parents"></param>
/// <returns></returns>
public T GetValueOfKey<T>(string key, params string[] parents)
{
JObject parentObject = null;
for(int i = 0; i < parents.Length; i++)
parentObject = GetValueOfKey<JObject>(parents[i].Trim(), parentObject == null ? root : parentObject);
return GetValueOfKey<T>(key, parentObject == null ? root : parentObject);
}
/// <summary>
/// Returns the value of the specified key from the root structure.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public T GetValueOfKey<T>(string key)
{
return GetValueOfKey<T>(key, root);
}
/// <summary>
/// Returns the value of the specified key from a given index in the structure.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="index"></param>
/// <returns></returns>
private T GetValueOfKey<T>(string key, JObject index)
{
JToken token = index.GetValue(key);
if (token != null)
return token.ToObject<T>();
foreach (var kvp in index)
if (kvp.Value is JObject)
{
T value = GetValueOfKey<T>(key, (JObject)kvp.Value);
if (value != null)
return value;
}
return default(T);
}
/// <summary>
/// Replaces an object specified by the given key and ensures object is replaced within the correct parent(s), which is split by the delimiter.
/// </summary>
/// <param name="key"></param>
/// <param name="replacement"></param>
/// <param name="parents"></param>
public void ReplaceObject(string key, JObject replacement, string parents)
{
ReplaceObject(key, root, replacement, parents.Split(delimiter));
}
/// <summary>
/// Replaces an object specified by the given key and ensures object is replaced within the correct parent(s).
/// </summary>
/// <param name="key"></param>
/// <param name="replacement"></param>
/// <param name="parents"></param>
public void ReplaceObject(string key, JObject replacement, params string[] parents)
{
ReplaceObject(key, root, replacement, parents);
}
/// <summary>
/// Replaces an object specified by the given key.
/// </summary>
/// <param name="key"></param>
/// <param name="replacement"></param>
public void ReplaceObject(string key, JObject replacement)
{
ReplaceObject(key, root, replacement);
}
/// <summary>
/// Replaces an object specified by the given key within the structure and updates changes to the root node.
/// </summary>
/// <param name="key"></param>
/// <param name="index"></param>
/// <param name="replacement"></param>
private void ReplaceObject(string key, JObject index, JObject replacement)
{
foreach (var kvp in index)
if (kvp.Value is JObject)
ReplaceObject(key, (JObject)kvp.Value, replacement);
JToken token = index.GetValue(key);
if (token != null)
{
JToken root = token.Root;
token.Replace(replacement);
this.root = (JObject)root;
}
}
/// <summary>
/// Replaces an object specified by the given key within the structure, ensuring object is replaced within the correct parent, and updates changes to the root node.
/// </summary>
/// <param name="key"></param>
/// <param name="index"></param>
/// <param name="replacement"></param>
/// <param name="parents"></param>
private void ReplaceObject(string key, JObject index, JObject replacement, params string[] parents)
{
foreach (var kvp in index)
if (kvp.Value is JObject)
{
bool valid = false;
foreach (string str in parents)
if (str.Trim() == kvp.Key)
valid = true;
if(valid)
ReplaceObject(key, (JObject)kvp.Value, replacement);
}
JToken token = index.GetValue(key);
if (token != null)
{
JToken root = token.Root;
token.Replace(replacement);
this.root = (JObject)root;
}
}
/// <summary>
/// Returns the root structure as JSON.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return root.ToString();
}
/// <summary>
/// A ParentNotFoundException details that the supplied parent was not found within the structure.
/// </summary>
private class ParentNotFoundException : Exception
{
public ParentNotFoundException() { }
public ParentNotFoundException(string message) : base(message) { }
public ParentNotFoundException(string message, Exception inner) : base(message, inner) { }
}
}
Usage example:
Data data = new Data();
data.AddKeyWithValue("name", "foo");
data.AddChild("costs");
data.AddChild("attributes");
data.AddKeyWithValue("move", 1, "costs");
data.AddKeyWithValue("place", 2, "costs");
data.AddKeyWithValue("destroy", 3, "costs");
data.AddChild("movement", "costs");
data.AddKeyWithValue("slippery", false, "costs", "movement");
data.AddChild("movement", "attributes");
data.AddKeyWithValue("slippery", true, "attributes", "movement");
if(data.HasValue("move", "costs")) {
Debug.Log(data.GetValueOfKey<int>("move", "costs")
Debug.Log(data);
}
And its output:
1
{
"name": "foo",
"costs": {
"move": 1,
"place": 2,
"destroy": 3,
"movement": {
"slippery": false
}
},
"attributes": {
"movement": {
"slippery": true
}
}
}
I defined two JsonConverter classes. One I attach to the class, the other I attach to a property of that class. If I only attach the converter to the property, it works fine. As soon as I attach a separate converter to the class, it ignores the one attached to the property. How can I make it not skip such JsonConverterAttributes?
Here is the class-level converter (which I adapted from this: Alternate property name while deserializing). I attach it to the test class like so:
[JsonConverter(typeof(FuzzyMatchingJsonConverter<JsonTestData>))]
Then here is the FuzzyMatchingJsonConverter itself:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Optimizer.models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Optimizer.Serialization
{
/// <summary>
/// Permit the property names in the Json to be deserialized to have spelling variations and not exactly match the
/// property name in the object. Thus puntuation, capitalization and whitespace differences can be ignored.
///
/// NOTE: As implemented, this can only deserialize objects from a string, not serialize from objects to a string.
/// </summary>
/// <seealso cref="https://stackoverflow.com/questions/19792274/alternate-property-name-while-deserializing"/>
public class FuzzyMatchingJsonConverter<T> : JsonConverter
{
/// <summary>
/// Map the json property names to the object properties.
/// </summary>
private static DictionaryToObjectMapper<T> Mapper { get; set; } = null;
private static object SyncToken { get; set; } = new object();
static void InitMapper(IEnumerable<string> jsonPropertyNames)
{
if (Mapper == null)
lock(SyncToken)
{
if (Mapper == null)
{
Mapper = new DictionaryToObjectMapper<T>(
jsonPropertyNames,
EnumHelper.StandardAbbreviations,
ModelBase.ACCEPTABLE_RELATIVE_EDIT_DISTANCE,
ModelBase.ABBREVIATION_SCORE
);
}
}
else
{
lock(SyncToken)
{
// Incremental mapping of additional attributes not seen the first time for the second and subsequent objects.
// (Some records may have more attributes than others.)
foreach (var jsonPropertyName in jsonPropertyNames)
{
if (!Mapper.CanMatchKeyToProperty(jsonPropertyName))
throw new MatchingAttributeNotFoundException(jsonPropertyName, typeof(T).Name);
}
}
}
}
public override bool CanConvert(Type objectType) => objectType.IsClass;
/// <summary>
/// If false, this class cannot serialize (write) objects.
/// </summary>
public override bool CanWrite { get => false; }
/// <summary>
/// Call the default constructor for the object and then set all its properties,
/// matching the json property names to the object attribute names.
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType">This should match the type parameter T.</param>
/// <param name="existingValue"></param>
/// <param name="serializer"></param>
/// <returns>The deserialized object of type T.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Note: This assumes that there is a default (parameter-less) constructor and not a constructor tagged with the JsonCOnstructorAttribute.
// It would be better if it supported those cases.
object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
JObject jo = JObject.Load(reader);
InitMapper(jo.Properties().Select(jp => jp.Name));
foreach (JProperty jp in jo.Properties())
{
var prop = Mapper.KeyToProperty[jp.Name];
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
Do not get bogged down with DictionaryToObjectMapper (it is proprietary, but uses fuzzy matching logic to deal with spelling variations). Here is the next JsonConverter, that will change "Y", "Yes", "T", "True", etc into Boolean true values. I adapted it from this source: https://gist.github.com/randyburden/5924981
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Optimizer.Serialization
{
/// <summary>
/// Handles converting JSON string values into a C# boolean data type.
/// </summary>
/// <see cref="https://gist.github.com/randyburden/5924981"/>
public class BooleanJsonConverter : JsonConverter
{
private static readonly string[] Truthy = new[] { "t", "true", "y", "yes", "1" };
private static readonly string[] Falsey = new[] { "f", "false", "n", "no", "0" };
/// <summary>
/// Parse a Boolean from a string where alternative spellings are permitted, such as 1, t, T, true or True for true.
///
/// All values that are not true are considered false, so no parse error will occur.
/// </summary>
public static Func<object, bool> ParseBoolean
= (obj) => { var b = (obj ?? "").ToString().ToLower().Trim(); return Truthy.Any(t => t.Equals(b)); };
public static bool ParseBooleanWithValidation(object obj)
{
var b = (obj ?? "").ToString().ToLower().Trim();
if (Truthy.Any(t => t.Equals(b)))
return true;
if (Falsey.Any(t => t.Equals(b)))
return false;
throw new ArgumentException($"Unable to convert ${obj}into a Boolean attribute.");
}
#region Overrides of JsonConverter
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
// Handle only boolean types.
return objectType == typeof(bool);
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>
/// The object value.
/// </returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> ParseBooleanWithValidation(reader.Value);
/// <summary>
/// Specifies that this converter will not participate in writing results.
/// </summary>
public override bool CanWrite { get { return false; } }
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter"/> to write to.</param><param name="value">The value.</param><param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//TODO: Implement for serialization
//throw new NotImplementedException("Serialization of Boolean");
// I have no idea if this is correct:
var b = (bool)value;
JToken valueToken;
valueToken = JToken.FromObject(b);
valueToken.WriteTo(writer);
}
#endregion Overrides of JsonConverter
}
}
And here is how I created the test class used in my unit tests:
[JsonConverter(typeof(FuzzyMatchingJsonConverter<JsonTestData>))]
public class JsonTestData: IEquatable<JsonTestData>
{
public string TestId { get; set; }
public double MinimumDistance { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool TaxIncluded { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool IsMetsFan { get; set; }
[JsonConstructor]
public JsonTestData()
{
TestId = null;
MinimumDistance = double.NaN;
TaxIncluded = false;
IsMetsFan = false;
}
public JsonTestData(string testId, double minimumDistance, bool taxIncluded, bool isMetsFan)
{
TestId = testId;
MinimumDistance = minimumDistance;
TaxIncluded = taxIncluded;
IsMetsFan = isMetsFan;
}
public override bool Equals(object obj) => Equals(obj as JsonTestData);
public bool Equals(JsonTestData other)
{
if (other == null) return false;
return ((TestId ?? "") == other.TestId)
&& (MinimumDistance == other.MinimumDistance)
&& (TaxIncluded == other.TaxIncluded)
&& (IsMetsFan == other.IsMetsFan);
}
public override string ToString() => $"TestId: {TestId}, MinimumDistance: {MinimumDistance}, TaxIncluded: {TaxIncluded}, IsMetsFan: {IsMetsFan}";
public override int GetHashCode()
{
return -1448189120 + EqualityComparer<string>.Default.GetHashCode(TestId);
}
}
The reason that [JsonConverter(typeof(BooleanJsonConverter))] as applied to your properties is not working is that you have supplied a JsonConverter for the containing type, and are not calling the applied converter(s) for its members inside your ReadJson() method.
When a converter is not applied to a type, prior to (de)serialization Json.NET uses reflection to create a JsonContract for the type that specifies how to map the type from and to JSON. In the case of an object with properties, a JsonObjectContract is generated that includes methods to construct and populate the type and lists all the members of the type to be serialized, including their names and any applied converters. Once the contract is built, the method JsonSerializerInternalReader.PopulateObject() uses it to actually deserialize an object.
When a converter is applied to a type, all the logic described above is skipped. Instead JsonConverter.ReadJson() must do everything including deserializing and setting all member values. If those members happen to have converters applied, ReadJson() will need to notice this fact and manually invoke the converter. This is what your converter needs to be doing around here:
foreach (JProperty jp in jo.Properties())
{
var prop = Mapper.KeyToProperty[jp.Name];
// Check for and use [JsonConverter(typeof(...))] if applied to the member.
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
So, how to do this? One way would be to use c# reflection tools to check for the appropriate attribute(s). Luckily, Json.NET has already done this for you in constructing its JsonObjectContract; you can get access to it from within ReadJson() simply by calling:
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
Having done that you can use the already-constructed contract to guide your deserialization.
Since you don't provide a working example of your FuzzyMatchingJsonConverter I've created something similar that will allow both snake case and pascal case properties to be deserialized into an object with camel case naming:
public abstract class FuzzyMatchingJsonConverterBase : JsonConverter
{
protected abstract JsonProperty FindProperty(JsonObjectContract contract, string propertyName);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
if (contract == null)
throw new JsonSerializationException(string.Format("Contract for type {0} is not a JsonObjectContract", objectType));
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
existingValue = existingValue ?? contract.DefaultCreator();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
case JsonToken.PropertyName:
{
var propertyName = (string)reader.Value;
reader.ReadAndAssert();
var jsonProperty = FindProperty(contract, propertyName);
if (jsonProperty == null)
continue;
object itemValue;
if (jsonProperty.Converter != null && jsonProperty.Converter.CanRead)
{
itemValue = jsonProperty.Converter.ReadJson(reader, jsonProperty.PropertyType, jsonProperty.ValueProvider.GetValue(existingValue), serializer);
}
else
{
itemValue = serializer.Deserialize(reader, jsonProperty.PropertyType);
}
jsonProperty.ValueProvider.SetValue(existingValue, itemValue);
}
break;
case JsonToken.EndObject:
return existingValue;
default:
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
}
}
throw new JsonReaderException("Unexpected EOF!");
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public abstract class FuzzySnakeCaseMatchingJsonConverterBase : FuzzyMatchingJsonConverterBase
{
protected override JsonProperty FindProperty(JsonObjectContract contract, string propertyName)
{
// Remove snake-case underscore.
propertyName = propertyName.Replace("_", "");
// And do a case-insensitive match.
return contract.Properties.GetClosestMatchProperty(propertyName);
}
}
// This type should be applied via attributes.
public class FuzzySnakeCaseMatchingJsonConverter : FuzzySnakeCaseMatchingJsonConverterBase
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
// This type can be used in JsonSerializerSettings.Converters
public class GlobalFuzzySnakeCaseMatchingJsonConverter : FuzzySnakeCaseMatchingJsonConverterBase
{
readonly IContractResolver contractResolver;
public GlobalFuzzySnakeCaseMatchingJsonConverter(IContractResolver contractResolver)
{
this.contractResolver = contractResolver;
}
public override bool CanConvert(Type objectType)
{
if (objectType.IsPrimitive || objectType == typeof(string))
return false;
var contract = contractResolver.ResolveContract(objectType);
return contract is JsonObjectContract;
}
}
public static class JsonReaderExtensions
{
public static void ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected EOF!");
}
}
Then you would apply it as follows:
[JsonConverter(typeof(FuzzySnakeCaseMatchingJsonConverter))]
public class JsonTestData
{
public string TestId { get; set; }
public double MinimumDistance { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool TaxIncluded { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool IsMetsFan { get; set; }
}
Notes:
I avoided pre-loading the JSON into an intermediate JToken hierarchy, since there was no need to do so.
You don't provide a working example of your own converter so I can't fix it for you in this answer, but you would want to make it inherit from FuzzyMatchingJsonConverterBase and then write your on version of protected abstract JsonProperty FindProperty(JsonObjectContract contract, string propertyName);.
You might also need to check and use other properties of JsonProperty such as JsonProperty.ItemConverter, JsonProperty.Ignored, JsonProperty.ShouldDeserialize and so on. But if you do, you may eventually end up duplicating the entire logic of JsonSerializerInternalReader.PopulateObject().
A null JSON value should be checked for near the beginning of ReadJson().
Sample working .Net fiddle that shows that the following JSON can be deserialized successfully, and thus that both the type and member converters are getting invoked:
{
"test_id": "hello",
"minimum_distance": 10101.1,
"tax_included": "yes",
"is_mets_fan": "no"
}
I am trying to serialize a class to a JSON string. First, my actual code:
// Note that this class is inside a PCL
public class CommunicationMessage {
public String Key { get; set; }
public String Value { get; set; }
public List<CommunicationMessage> Childs { get; set; }
}
This is a template which can be converted to xml which could look like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myproject>
<communicationmessage>
<header>
<participantid>1</participantid>
<deviceid>54325</deviceid>
<devicetype>Smartphone Samsung 4500</devicetype>
<timestamp>3456453445</timestamp>
<location>343-5343-64353</location>
<networkid>32</networkid>
<messageid>4003</messageid>
</header>
<data>
</data>
</communicationmessage>
</myproject>
As you can see, the variable Key is an Xml-Element which is named e.g communicationmessage.
Now U want the template beeing converted to a JSON string, but for sure I get instead of a element communicationmessage the element "Key":"communicationmessage". Is there a way to get something for an element like "mymessage":"This is a test" where "mymessage" is the Key and "This is a test" the value?
Thanks for any help
Solution
I solved it with this code
public class CommunicationMessageJSONSerializer : JsonConverter {
/// <summary>
/// Used to
/// </summary>
/// <param name="objectType"></param>
/// <returns></returns>
public override bool CanConvert(Type objectType) {
return typeof(CommunicationMessage).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
/// <summary>
/// Deserializes the JSON string
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
// Load the JSON object from the reader
JObject jsonObject = JObject.Load(reader);
// Get the First Token
JToken token = jsonObject.Children().First();
// The deserialized message
CommunicationMessage msg = ReadJSON(token);
return msg;
}
/// <summary>
/// This is the base method when deserializing a JSON string
/// </summary>
/// <param name="token"></param>
/// <returns>The root CommunicationMessage</returns>
private CommunicationMessage ReadJSON(JToken token) {
CommunicationMessage root = new CommunicationMessage();
if (token is JProperty) {
if (token.First is JValue) {
root.Key = ((JProperty)token).Name;
root.Value = (string)((JProperty)token).Value;
} else {
root.Key = ((JProperty)token).Name;
foreach (JToken child in token.Children()) {
ReadRecursive(child, ref root);
}
}
} else {
foreach (JToken child in token.Children()) {
ReadRecursive(child, ref root);
}
}
return root;
}
/// <summary>
/// This is the recursive method when deserializing a JSON string
/// </summary>
/// <param name="token"></param>
/// <param name="root">The root of the coming messages</param>
private void ReadRecursive(JToken token, ref CommunicationMessage root) {
if (token is JProperty) {
CommunicationMessage msg = new CommunicationMessage();
if (token.First is JValue) {
msg.Key = ((JProperty)token).Name;
msg.Value = (string)((JProperty)token).Value;
} else {
msg.Key = ((JProperty)token).Name;
foreach (JToken child in token.Children()) {
ReadRecursive(child, ref msg);
}
}
root.Childs.Add(msg);
} else {
foreach (JToken child in token.Children()) {
ReadRecursive(child, ref root);
}
}
}
/// <summary>
/// Serializes a CommuicationMessage to a JSON string
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
var msg = value as CommunicationMessage;
WriteRecoursive(writer, msg, serializer);
}
/// <summary>
/// This is the recursive method for serializing
/// </summary>
/// <param name="writer"></param>
/// <param name="msg"></param>
/// <param name="serializer"></param>
private void WriteRecoursive(JsonWriter writer, CommunicationMessage msg, JsonSerializer serializer) {
writer.WriteStartObject();
writer.Formatting = Formatting.Indented;
writer.WritePropertyName(msg.Key);
if (msg.Childs.Count > 0) {
writer.WriteStartArray();
foreach (CommunicationMessage child in msg.Childs) {
WriteRecoursive(writer, child, serializer);
}
writer.WriteEndArray();
} else {
writer.WriteValue(msg.Value);
}
writer.WriteEndObject();
}
}
If you have any ideas to improve the code, let me know it. I will keep the solution updated.
Thanks everyone for your help
Other than using custom serialisation or implementing ISerializable, you can do the following. But please note that there is a drawback to this method whereby all properties need to be wrapped like this otherwise the the properties will not get serialised. This quick and dirty method is good for small classes but if you are creating more complicated classes, it will be better to implement ISerializable
public class CommunicationMessage : Dictionary<string, object>
{
//this "hack" exposes the "Child" as a List
public List<CommunicationMessage> Childs
{
get {
return (List<CommunicationMessage>)this["Childs"];
}
set
{
this["Childs"] = value;
}
}
public CommunicationMessage()
{
this["Childs"] = new List<CommunicationMessage>();
}
}
Usage:
var m = new CommunicationMessage();
m["mymessage"] = "This is a test";
And the output should look like
{
"Childs": [],
"mymessage": "This is a test"
}
The alternative ISerializable implementation:
public class CommunicationMessage:ISerializable
{
public String Key { get; set; }
public String Value { get; set; }
public List<CommunicationMessage> Childs { get; set; }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(Key, Value);
PropertyInfo[] pi=this.GetType().GetProperties();
foreach(var p in pi)
{
if (p.Name == "Key" || p.Name == "Value")
continue;
info.AddValue(p.Name, p.GetValue(this));
}
}
}
I am using Newtonsoft.Json to serialize a class and all of its members. There is one particular class that many of its members are an instance of, I'd simply like to tell a class to not be serialized at all, so if any member that is an instance of that type is skipped.
Is this possible in C# by appending some sort of attribute to a class to mark it as non-serializable?
I do not think this can be done using an attribute on the class. However you should be able to do it by implementing a custom JsonConverter which always serializes and deserializes any instance of this class to null. This code implements such behavior:
class IgnoringConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteNull();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return null;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ClassToIgnore);
}
}
In this example, ClassToIgnore is the class you wish to ignore during serialization. Such classes should be decorated with the JsonConverter attribute:
[JsonConverter(typeof(IgnoringConverter))]
class ClassToIgnore
You can also register the converter as a default converter which is useful if you're using ASP.NET Web API.
I have included a Console application sample to demonstrate the functionality:
using System;
using Newtonsoft.Json;
/// <summary>
/// Class we want to serialize.
/// </summary>
class ClassToSerialize
{
public string MyString { get; set; } = "Hello, serializer!";
public int MyInt { get; set; } = 9;
/// <summary>
/// This will be null after serializing or deserializing.
/// </summary>
public ClassToIgnore IgnoredMember { get; set; } = new ClassToIgnore();
}
/// <summary>
/// Ignore instances of this class.
/// </summary>
[JsonConverter(typeof(IgnoringConverter))]
class ClassToIgnore
{
public string NonSerializedString { get; set; } = "This should not be serialized.";
}
class IgnoringConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteNull();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return null;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ClassToIgnore);
}
}
class Program
{
static void Main(string[] args)
{
var obj = new ClassToSerialize();
var json = JsonConvert.SerializeObject(obj);
Console.WriteLine(json);
obj = JsonConvert.DeserializeObject<ClassToSerialize>(json);
// note that obj.IgnoredMember == null at this point
Console.ReadKey();
}
}
I have a DataAnnotationValidator that I created. I am currently trying to test it with a Required Field attribute and I can't get the IsValid property to fail when my property is null. It does work correctly when I create a number with a Range attribute that is outside of the specified Range.
public class TestEntityWithDataAnnotations
{
public Guid Id { get; set; }
[Required(ErrorMessage = "Required")]
public string Name { get; set; }
}
[TestFixture]
public class DataAnnotationValidatorTest
{
[Test]
public void Validate_ReturnsFailure_WhenPropertyValidationIsNotValid()
{
var validator = new DataAnnotationValidator();
var invalidEntity = new TestEntityWithDataAnnotations
{
Id = Guid.NewGuid()
};
var validationResult = validator.Validate(invalidEntity);
Assert.IsFalse(validationResult.IsValid);
}
}
public class DataAnnotationValidator
{
public ValidationResult Validate(object obj)
{
Type objType = obj.GetType();
var typeDescriptor = GetTypeDescriptor(obj, objType);
var validationResult = new ValidationResult();
var classValidationResult = CheckClassIsValid(obj, typeDescriptor);
if (!classValidationResult.IsValid)
{
validationResult.AddErrors(classValidationResult.Errors);
}
foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.GetProperties())
{
// Loop over all of the properties on our object that have Validation Attributes
var propValidationResult = CheckPropertyIsValid(obj, propertyDescriptor);
if(!propValidationResult.IsValid)
{
validationResult.AddErrors(propValidationResult.Errors);
}
}
return validationResult;
}
/// <summary>
/// Checks to see if there are any class level validation attributes and runs them
/// </summary>
/// <returns></returns>
private static ValidationResult CheckClassIsValid(object obj, ICustomTypeDescriptor typeDescriptor)
{
var errors = typeDescriptor.GetAttributes().OfType<ValidationAttribute>()
.Where(x => !x.IsValid(obj))
.Select(x => new ValidationError(typeDescriptor.GetClassName(), x.ErrorMessage));
return new ValidationResult(errors.ToList());
}
/// <summary>
/// Checks to see if a property has any DataAnnotations that it has violated
/// </summary>
private static ValidationResult CheckPropertyIsValid(object obj, PropertyDescriptor propertyDescriptor)
{
var errors = propertyDescriptor.Attributes.OfType<ValidationAttribute>()
.Where(x => !x.IsValid(obj))
.Select(x => new ValidationError(propertyDescriptor.Name, x.ErrorMessage));
return new ValidationResult(errors.ToList());
}
/// <summary>
/// Gets the model's type descriptor. In order to support the buddy class metadata model
/// for LINQ to SQL and Entity Framework, it uses
/// <see cref="AssociatedMetadataTypeTypeDescriptionProvider"/>.
/// </summary>
/// <param name="obj">The model object</param>
/// <param name="objType">The type of the model object</param>
/// <returns>The model's type descriptor</returns>
private static ICustomTypeDescriptor GetTypeDescriptor(object obj, Type objType)
{
var provider = new AssociatedMetadataTypeTypeDescriptionProvider(objType);
return provider.GetTypeDescriptor(objType, obj);
}
}
A bit of stupidity on my part. I needed to pass the value of the property into IsValid inside of CheckPropertyIsValid instead of the whole object.
private static ValidationResult CheckPropertyIsValid(object obj, PropertyDescriptor propertyDescriptor)
{
var errors = propertyDescriptor.Attributes.OfType<ValidationAttribute>()
.Where(x => !x.IsValid(propertyDescriptor.GetValue(obj)))
.Select(x => new ValidationError(propertyDescriptor.Name, x.ErrorMessage));
return new ValidationResult(errors.ToList());
}