I have created a test console application that has a simple class named Other. As an exercise, I want to set the OtherString property to null when it is serialized. I know how I could do this with a custom ContractResolver. I need to be able to do this with a custom Converter too.
The first converter I wrote was simple and the way I thought it should be. However, it would throw a "Self referencing loop detected with type 'JsonContractandConvert.Models.Other'. Path ''." exception. After doing some reading, I made some changes and now I have a working converter. These changes are a lot more verbose, but work.
My question is why are these changes required and is there a better way to do this with a Converter?
Other Class:
[JsonConverter(typeof(OtherConverter))]
public class Other
{
public int Id { get; set; }
public string OtherString { get; set; }
public int OtherInt { get; set; }
public string OtherName
{
get
{
return "Other Name = " + this.OtherString;
}
}
}
First Attempt: (This one throws an exception)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
var entity = value as Other;
if (entity == null) return;
entity.OtherString = null;
serializer.Serialize(writer, entity);
}
Second Attempt: (This works as expected)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
var entity = value as Other;
if (entity == null) return;
entity.OtherString = null;
writer.WriteStartObject();
var props = entity.GetType().GetProperties();
foreach (var propertyInfo in props)
{
var ignorAttribute =
propertyInfo.CustomAttributes.FirstOrDefault(i => i.AttributeType == typeof(JsonIgnoreAttribute));
if (ignorAttribute != null) continue;
var tempVal = propertyInfo.GetValue(entity);
if (tempVal == null) continue;
writer.WritePropertyName(propertyInfo.Name);
serializer.Serialize(writer, tempVal);
}
}
writer.WriteEndObject();
}
Edit:
Here's the code from the console app that I'm using to test.
class Program
{
static void Main(string[] args)
{
var otherObj = new Other { Id = 123, OtherInt = 456, OtherString = "This is the other string"};
var json = JsonConvert.SerializeObject(otherObj, Formatting.Indented);
Console.WriteLine(json);
}
}
Answering my own questions... (I think I've finally got my head around Json Converters)
To answer my basic question as to why one way works and the other doesn't. I think the answer is just that the way that doesn't work is just wrong. From what I can tell, you need to use the writer object. If you don't it won't work. (There may be scenarios where this is not the case, but I never found one.)
If you merely want to remove a property from the Json, use [JsonIgnore]. (Simple right?)
If you want to change the value some how, you'll need to use a Converter. By using a Converter, you're taking responsibility for serializing what ever object the Converter handles. So if the object is a simple type like a String, or an Int it's really easy. If it's something more complex like an array or complex object, then it takes a little more planning.
Here's an example of a simple String Converter:
Class to serialize:
public class Account
{
public int Id { get; set; }
public string AccountName { get; set; }
[JsonIgnore]
public virtual Account DefaultAssignTo { get; set; }
public int? DefaultAssignToId { get; set; }
[JsonIgnore]
public virtual ICollection<Role> Roles { get; set; }
[JsonIgnore]
public virtual Other Other { get; set; }
public int? OtherId { get; set; }
[JsonConverter(typeof(StringConverter))]
public string OtherName
{
get
{
return "Name = " + this.AccountName;
}
}
}
Converter:
public class StringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(string).IsAssignableFrom(objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
writer.WriteValue("blah blah blah");
}
}
Run: (As you can see I'm using Entity Framework for my data)
using (var db = new Context())
{
var json = JsonConvert.SerializeObject(db.Accounts.FirstOrDefault(), Formatting.Indented,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
Console.WriteLine(json);
}
Returns:
{
"Id": 43,
"AccountName": "John",
"DefaultAssignToId": 43,
"OtherId": 19,
"OtherName": "blah blah blah"
}
One of the things that messed me up for a bit was when to use writer.WriteStartObject(). Basically if you are Converting a complex object, you need to use it. If you do, then you need to create all of the property names and values. In the OP you can see an example of how I did this. The biggest down side is any Json attributes that properties are decorated with don't automatically happen. So if you can, decorate the class and let the serializer deal with it. Here's an interesting problem you can run into if you use it wrong.
In the String converter replace:
writer.WriteValue("blah blah blah");
with:
writer.WriteStartObject();
writer.WritePropertyName("BlahProp");
serializer.Serialize(writer, "blah blah blah");
writer.WriteEndObject();
run it again and here's the output: (Notice how OtherName is an object now instead of a string)
{
"Id": 43,
"AccountName": "John",
"DefaultAssignToId": 43,
"OtherId": 19,
"OtherName": {
"BlahProp": "blah blah blah"
}
}
The Roles collection also needed a Converter. Two things to note are first, you don't need to specify the property name and second, any Json attributes that the Role class is decorated with will work as expected.
using BaseCollection = System.Collections.Generic.ICollection<JsonContractandConvert.Models.Role>;
public class RemoveAccountsFromRolesConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseCollection).IsAssignableFrom(objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
var entities = value as BaseCollection;
if (entities == null) return;
writer.WriteStartArray();
foreach (var entity in entities)
{
entity.Accounts = null;
serializer.Serialize(writer, entity);
}
writer.WriteEndArray();
}
}
Lastly, here's a couple of converters I created for my real project:
This one converts an object to a shallow copy. This is useful if you have a collection where what that collection contains could cause a self referencing loop.
public class ShallowCopyCollectionConverter<TCollectionType, TCopyType> : JsonConverter
where TCollectionType : IEnumerable<TbdEntity>
where TCopyType : TbdEntity, new()
{
public override bool CanConvert(Type objectType)
{
return typeof(TCollectionType).IsAssignableFrom(objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (this.CanConvert(value.GetType()) == false) return;
var entities = (TCollectionType)value;
writer.WriteStartArray();
foreach (var entity in entities)
{
serializer.Serialize(writer, entity.ShallowCopy<TCopyType>()); //ShallowCopy<> is a method in the base class that all of my classes extend.
}
writer.WriteEndArray();
}
}
This one will convert an abstract using the concrete type:
public class DataSnapInConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DataSnapIn) == (objectType);
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (this.CanConvert(objectType) == false) return null;
var jo = JObject.Load(reader);
var typeName = jo["snapInType"] ?? jo["SnapInType"]; //the abstract classes have this property to identify what concrete class they are.
var typeNameString = typeName.ToString();
var deserializeType = Type.GetType(typeNameString);
if(deserializeType == null)
throw new Exception("SnapInType is null or does not reference a valid class.");
var result = Activator.CreateInstance(deserializeType);
serializer.Populate(jo.CreateReader(), result);
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Related
Since I had to search forever for an answer, that would just work as is, I will provide an example here, when you serialize and deserialize an object with a composite pattern structure.
My problem was deserializing this kind of class structure (https://en.wikipedia.org/wiki/Composite_pattern):
abstract class BaseClass
{
public int Id { get; set; }
}
class Leaf : BaseClass
{
public string Foo { get; set; }
}
class Composite : BaseClass
{
public List<BaseClass> ClassList = new List<BaseClass>();
}
Serialization worked by using:
var composite = new Composite();
JsonConvert.SerializeObject(composite, Formatting.Indented);
Deserialziation didn't work out of box.
Solution for deserialization is to build a CustomConverter:
public class BaseClassJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseClass) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.Converters.Add(new BaseClassJsonConverter());
if (jObject.ContainsKey("Foo"))
return JsonConvert.DeserializeObject<Leaf>(jObject.ToString(), jsonSerializerSettings);
else if (jObject.ContainsKey("ClassList"))
return JsonConvert.DeserializeObject<Composite>(jObject.ToString(), jsonSerializerSettings);
else
throw new System.SystemException("Class not implemented");
return null;
}
catch (JsonReaderException)
{
return null;
}
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Key difference to other answers I found is that they were missing to put the settings in the ReadJson into the SeserialzieObject again, that way it couldn't handle the deserialization of a recursive structure (Deserializing JSON to abstract class).
Now you either decorate your BaseClass with: [JsonConverter(typeof(BaseConverter))]
Or you call the deserialization with the converter in the settings:
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.Converters.Add(new BaseClassJsonConverter());
JsonConvert.DeserializeObject<BaseClass>(jsonString, jsonSerializerSettings);
I am trying to solve backward compartibility when deserializing old json. Previously there was double property and now it's changed to a custom type.
My idea is to read double and simply convert it using custom json converter.
Before was:
public class A
{
[JsonProperty)]
string Name { get; set; }
[JsonProperty)]
double Value { get; set; }
}
Serialized as
{"Name":"test","Value":33.0}
New one:
public class A
{
[JsonProperty]
[JsonConverter(typeof(MyJsonConverter))]
public MyType Value { get; set; }
}
Serialized as
{"Value":{"Value":33.0,"Name":"test", ...}},
Converter:
public class MyJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => true;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value is double value)
return new MyType(value, ???); // here is the problem, I need Name value here
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
JToken.FromObject(value).WriteTo(writer);
}
but to construct MyType I need string parameter which is a value of another property Name
How to access Name value from converter for Value? How to access anything what has been deserialized? Is there some kind of tree? Tokens tree?
Another thing: in WriteJson method I want to do "nothing" special, is my implementation correct? Or is there an easy way to prevent converter doing anything "special" upon serialization?
You would need to apply a converter to your parent A class:
[JsonConverter(typeof(MyJsonConverter))]
public class A
{
public MyType Value { get; set; }
}
public class AConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(A);
public override bool CanWrite => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
// Check if the keys contains "Name"
string name = jObject["Name"]?.ToString();
var a = new A();
if (name != null)
{
a.Value = new MyType
{
Name = name,
Value = jObject["Value"].Value<double>()
};
}
else
{
a.Value = jObject["Value"].ToObject<MyType>();
}
return a;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use default serailisation, just override CanWrite with false.
This is basically a follow-on to the question Newtonsoft Object → Get JSON string .
I have an object that looks like this:
[JsonConverter(typeof(MessageConverter))]
public class Message
{
public Message(string original)
{
this.Original = original;
}
public string Type { get; set; }
public string Original { get; set; }
}
My requirement is to store the original JSON string as part of the object when I initialise it. I have been able to (partially) successfully achieve this using a custom JsonConverter and then basically doing something like this in the JsonConverter:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
return null;
JObject obj = JObject.Load(reader);
return new Message(obj.ToString(Formatting.None))
{
Type = obj["type"].ToString()
};
}
However the problem I run into is when I try to inherit from Message with something like
public class CustomMessage : Message
{
public string Prop1 { get; set; }
}
For obvious reasons my code fails because it tries to return a new Message() not a new CustomMessage().
So short of implementing a big if statement with all my sub-types and manually binding using something like JObject["prop"].ToObject<T>() how can I successfully populate the Original property while still binding all my sub-type values using the default deserialisation?
NOTE: The reason this is being done is because the original message might contain data that isn't actually being bound so I can't just add a property which serialises the object as it stands.
The following solution works
One thing you can do is to decorate each child class by generic JsonConverter attribute.
[JsonConverter(typeof(MessageConverter<Message>))]
public class Message
{
public Message(string original)
{
this.Original = original;
}
public string Type { get; set; }
public string Original { get; set; }
}
[JsonConverter(typeof(MessageConverter<CustomMessage>))]
public class CustomMessage : Message
{
public CustomMessage(string original) : base(original)
{
}
public string Prop1 { get; set; }
}
public class MessageConverter<T> : JsonConverter where T : Message
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
return null;
JObject obj = JObject.Load(reader);
var customObject = JsonConvert.DeserializeObject<T>(obj.ToString(), new JsonSerializerSettings
{
ContractResolver = new CustomContractResolver()
});
customObject.Original = obj.ToString();
return customObject;
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
//This will remove our declared Converter
public class CustomContractResolver : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter (Type objectType)
{
return null;
}
}
And then you can use the same serializer for all of the child classes
CustomMessage x = JsonConvert.DeserializeObject<CustomMessage>("{\"type\":\"Test\",\"Prop1\":\"Prop1\"}");
Message y = JsonConvert.DeserializeObject<Message>("{\"type\":\"Test\"}");
Here is the output screen shot
I'm leaving the question open in the hope that someone comes up with a better answer but I've temporarily used the following solution to resolve my problem.
public static class MessageExtensions
{
public static T Deserialize<T>(this string message) where T : Message
{
T instance = Activator.CreateInstance<T>();
instance.Original = message;
JsonConvert.PopulateObject(message, instance);
return instance;
}
}
How to Ignoring Fields and Properties Conditionally During Serialization Using JSON.Net?
I can't inherit from JsonIgnoreAttribute because it's a sealed class. What should I do?
You can use JSON.NET's ShouldSerialize-syntax. There's a good example on JSON.NET site:
http://www.newtonsoft.com/json/help/html/ConditionalProperties.htm
public class Employee
{
public string Name { get; set; }
public Employee Manager { get; set; }
public bool ShouldSerializeManager()
{
// don't serialize the Manager property if an employee is their own manager
return (Manager != this);
}
}
If ShouldSerialize doesn't fit your needs, you can take full control of the serialization with the ContractResolvers: http://www.newtonsoft.com/json/help/html/ContractResolver.htm
I found the answer. I inherit from JsonConverter and create a new convertor.
public class CustomJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var list = (IList)value;
JArray s = new JArray();
foreach (var item in list)
{
JToken token = JToken.FromObject(item);
JObject obj = new JObject();
foreach (JProperty prop in token)
{
if (prop.Name != "Title") // your logic here
obj.Add(prop);
}
s.Add(obj);
}
s.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType != typeof(IList);
}
}
I always had the impression that the JSON serializer actually traverses your entire object's tree, and executes the custom JsonConverter's WriteJson function on each interface-typed object that it comes across - not so.
I have the following classes and interfaces:
public interface IAnimal
{
string Name { get; set; }
string Speak();
List<IAnimal> Children { get; set; }
}
public class Cat : IAnimal
{
public string Name { get; set; }
public List<IAnimal> Children { get; set; }
public Cat()
{
Children = new List<IAnimal>();
}
public Cat(string name="") : this()
{
Name = name;
}
public string Speak()
{
return "Meow";
}
}
public class Dog : IAnimal
{
public string Name { get; set; }
public List<IAnimal> Children { get; set; }
public Dog()
{
Children = new List<IAnimal>();
}
public Dog(string name="") : this()
{
Name = name;
}
public string Speak()
{
return "Arf";
}
}
To avoid the $type property in the JSON, I've written a custom JsonConverter class, whose WriteJson is
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
IAnimal animal = value as IAnimal;
JObject o = (JObject)t;
if (animal != null)
{
if (animal is Dog)
{
o.AddFirst(new JProperty("type", "Dog"));
//o.Find
}
else if (animal is Cat)
{
o.AddFirst(new JProperty("type", "Cat"));
}
foreach(IAnimal childAnimal in animal.Children)
{
// ???
}
o.WriteTo(writer);
}
}
}
In this example, yes, a dog can have cats for children and vice-versa. In the converter, I want to insert the "type" property so that it saves that to the serialization. I have the following setup. (Zoo has only a name and a list of IAnimals. I didn't include it here for brevity and laziness ;))
Zoo hardcodedZoo = new Zoo()
{ Name = "My Zoo",
Animals = new List<IAnimal> { new Dog("Ruff"), new Cat("Cleo"),
new Dog("Rover"){
Children = new List<IAnimal>{ new Dog("Fido"), new Dog("Fluffy")}
} }
};
JsonSerializerSettings settings = new JsonSerializerSettings(){
ContractResolver = new CamelCasePropertyNamesContractResolver() ,
Formatting = Formatting.Indented
};
settings.Converters.Add(new AnimalsConverter());
string serializedHardCodedZoo = JsonConvert.SerializeObject(hardcodedZoo, settings);
serializedHardCodedZoo has the following output after serialization:
{
"name": "My Zoo",
"animals": [
{
"type": "Dog",
"Name": "Ruff",
"Children": []
},
{
"type": "Cat",
"Name": "Cleo",
"Children": []
},
{
"type": "Dog",
"Name": "Rover",
"Children": [
{
"Name": "Fido",
"Children": []
},
{
"Name": "Fluffy",
"Children": []
}
]
}
]
}
The type property shows up on Ruff, Cleo, and Rover, but not for Fido and Fluffy. I guess the WriteJson isn't called recursively. How do I get that type property there?
As an aside, why does it not camel-case IAnimals like I expect it to?
The reason that your converter is not getting applied to your child objects is because JToken.FromObject() uses a new instance of the serializer internally, which does not know about your converter. There is an overload that allows you to pass in the serializer, but if you do so here you will have another problem: since you are inside a converter and you are using JToken.FromObject() to try to serialize the parent object, you will get into an infinite recursive loop. (JToken.FromObject() calls the serializer, which calls your converter, which calls JToken.FromObject(), etc.)
To get around this problem, you must handle the parent object manually. You can do this without much trouble using a bit of reflection to enumerate the parent properties:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jo = new JObject();
Type type = value.GetType();
jo.Add("type", type.Name);
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.CanRead)
{
object propVal = prop.GetValue(value, null);
if (propVal != null)
{
jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
}
}
}
jo.WriteTo(writer);
}
Fiddle: https://dotnetfiddle.net/sVWsE4
Here's an idea, instead of doing the reflection on every property, iterate through the normally serialized JObject and then changed the token of properties you're interested in.
That way you can still leverage all the ''JsonIgnore'' attributes and other attractive features built-in.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken jToken = JToken.FromObject(value);
if (jToken.Type == JTokenType.Object)
{
JObject jObject = (JObject)jToken;
...
AddRemoveSerializedProperties(jObject, val);
...
}
...
}
And then
private void AddRemoveSerializedProperties(JObject jObject, MahMan baseContract)
{
jObject.AddFirst(....);
foreach (KeyValuePair<string, JToken> propertyJToken in jObject)
{
if (propertyJToken.Value.Type != JTokenType.Object)
continue;
JToken nestedJObject = propertyJToken.Value;
PropertyInfo clrProperty = baseContract.GetType().GetProperty(propertyJToken.Key);
MahMan nestedObjectValue = clrProperty.GetValue(baseContract) as MahMan;
if(nestedObj != null)
AddRemoveSerializedProperties((JObject)nestedJObject, nestedObjectValue);
}
}
I had this issue using two custom converters for a parent and child type. A simpler method I found is that since an overload of JToken.FromObject() takes a serializer as a parameter, you can pass along the serializer you were given in WriteJson(). However you need to remove your converter from the serializer to avoid a recursive call to it (but add it back in after):
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Converters.Remove(this);
JToken jToken = JToken.FromObject(value, serializer);
serializer.Converters.Add(this);
// Perform any necessary conversions on the object returned
}
Here is a hacky solution to your problem that gets the work done and looks tidy.
public class MyJsonConverter : JsonConverter
{
public const string TypePropertyName = "type";
private bool _dormant = false;
/// <summary>
/// A hack is involved:
/// " JToken.FromObject(value, serializer); " creates amn infinite loop in normal circumstances
/// for that reason before calling it "_dormant = true;" is called.
/// the result is that this JsonConverter will reply false to exactly one "CanConvert()" call.
/// this gap will allow to generate a a basic version without any extra properties, and then add them on the call with " JToken.FromObject(value, serializer); ".
/// </summary>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
_dormant = true;
JToken t = JToken.FromObject(value, serializer);
if (t.Type == JTokenType.Object && value is IContent)
{
JObject o = (JObject)t;
o.AddFirst(new JProperty(TypePropertyName, value.GetType().Name));
o.WriteTo(writer);
}
else
{
t.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
if (_dormant)
{
_dormant = false;
return false;
}
return true;
}
}