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;
}
}
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);
Hi Request your help in Below Scenario.
I have a Requirement where in I want to return Response JSON from DotNet WEB API As below.
{ "Status":"OK", "Series":[["GREEN",40.5],["RED",12.8],["YELLOW",12.8]] }
Below are my Code Sample and Output.
public class Response
{
public string Status {get;set;}
public List<SampleData> Series {get;set;}
}
public class SampleData
{
public string ColorCode{get;set;}
public double ColorVal{get;set;}
}
API Get Method
public Response Get()
{
Response Objresponse = new Response();
Objresponse.Status ="OK";
List<SampleData> _sampleList = new List<SampleData>();
_sampleList.Add(new SampleData{ ColorCode="GREEN", ColorVal=40.5});
_sampleList.Add(new SampleData{ ColorCode="RED",ColorVal=12.8});
_sampleList.Add(new SampleData{ ColorCode="YELLOW",ColorVal=12.8});
Objresponse.Series = _sampleList;
return Objresponse;
}
JSON Output which is returned
{
"Status":"OK",
"Series":[
{"ColorCode":"GREEN","ColorVal":40.5},
{"ColorCode":"RED","ColorVal":12.8},
{"ColorCode":"YELLOW","ColorVal":12.8}
]
}
Request your help to return the output as per my requirement.
Thank you.......
Probably the easiest way would be to create a custom JsonConverter and transform your response inside to your required format:
public class ResponseConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(Response);
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)
{
Response originalResponse = (Response)value;
writer.WriteStartObject();
writer.WritePropertyName(nameof(originalResponse.Status));
writer.WriteValue(originalResponse.Status);
writer.WritePropertyName(nameof(originalResponse.Series));
writer.WriteStartArray();
foreach (var val in originalResponse.Series)
{
writer.WriteStartArray();
writer.WriteValue(val.ColorCode);
writer.WriteValue(val.ColorVal);
writer.WriteEndArray();
}
writer.WriteEndArray();
writer.WriteEndObject();
}
}
Then, you can decorate your Response class with a special attribute to denote that it should be serialized with this converter:
[JsonConverter(typeof(ResponseConverter))]
public class Response
{
public string Status { get; set; }
public List<SampleData> Series { get; set; }
}
I am trying to write a converter for a class using JSON.net
When I serialize the object of type JsonObject, I get the following output
{"DataObject":{"id":"1","name":"data name"}}
How can I move the DataObject one level up to get the following output:
{"id":"1","name":"data name"}
You can find the relevant code below.
My class has the following format:
public class JsonObject
{
public JsonObject(IDataObject dataObject)
{
this.DataObject= dataObject;
}
[JsonConverter(typeof(JsonDataObjectConverter))]
public IDataObject DataObject;
}
The DataObject has some properties:
public class MyDataObject : IDataObject
{
[JsonProperty(PropertyName = "id", Required = Required.Always)]
public string Id { get; set; }
[JsonProperty(PropertyName = "name", Required = Required.Always)]
public string Name { get; set; }
}
I referred this page from the documentation and wrote the following converter:
public class JsonDataObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IDataObject).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
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 (value is MyDataObject dataObject)
{
writer.WriteStartObject();
writer.WritePropertyName("id");
writer.WriteValue(dataObject.Id);
writer.WritePropertyName("name");
writer.WriteValue(dataObject.Name);
writer.WriteEndObject();
}
}
}
Any help is appreciated. Thank you.
I was able to get the desired output by moving the converter to the class instead of the property and ignoring the property using [JsonIgnore]. The property needs to be ignored since the converter for the class will generate the JSON for the property as shown below.
So the JsonObject class will be:
[JsonConverter(typeof(JsonObjectConverter))]
public class JsonObject
{
public JsonObject(IDataObject dataObject)
{
this.DataObject= dataObject;
}
[JsonIgnore]
public IDataObject DataObject;
}
Then create the converter like this:
public class JsonObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IDataObject).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
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)
{
JsonObject jsonObject = (JsonObject)value;
if (jsonObject.DataObject.GetType() == typeof(MyDataObject))
{
MyDataObject dataObject = (MyDataObject) jsonObject.DataObject;
writer.WriteStartObject();
writer.WritePropertyName("id");
writer.WriteValue(dataObject.Id);
writer.WritePropertyName("name");
writer.WriteValue(dataObject.Name);
writer.WriteEndObject();
}
}
}
This gives the desired output of:
{"id":"1","name":"data name"}
I have a class that has a collection which is readonly and has no setter.
Is there a way to de-serialize some json and get it call a method on the instance instead of the property setter?
For example:
public class User
{
private ObservableCollection<Movie> _movies;
public string Name { get; set; }
public ReadOnlyCollection<Movie> FavouriteMovies { get; set; }
public void AddMovie(Movie movie) { .. }
//-or-
public void AddMovies(IEnumerable<Movie> movies){ .. }
}
The only way to get things into the _movies backing field is via the method AddMovies. So when trying to deserialize some valid json which has an array of Movies in the json, it will call AddMovie or AddMovies...
Ninja update by PK:
I've forked the fiddle below using a collection of classes instead of simple strings, for a more complex example that now works, based on the answer below.
Use a JsonConverter to do custom conversion of your json. Here's a simple example of how that might work. Given a class like this:
public class MyClass
{
private List<string> backingField;
public string Name { get; set; }
public IReadOnlyCollection<string> MyStrings { get; private set; }
public MyClass()
{
backingField = new List<string>();
MyStrings = backingField.AsReadOnly();
}
public void AddString(string item)
{
backingField.Add(item);
}
}
And JSON like this:
{
"MyStrings": [
"Foo",
"Bar"
],
"Name":"My stuff"
}
You could create a converter to read that json and call AddString to populate the backingField like this:
public class MyClassConverter : JsonConverter
{
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)
{
var obj = new MyClass();
var jObj = JObject.Load(reader);
JsonConvert.PopulateObject(jObj.ToString(), obj); // populate fields we don't need any special handling for
var stringsProp = jObj["MyStrings"];
if (stringsProp != null)
{
var strings = stringsProp.ToObject<List<string>>();
foreach (var s in strings)
{
obj.AddString(s);
}
}
return obj;
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MyClass);
}
}
Now to use it, and keep MyClass ignorant of how it get deserialized, you can simply do something like this:
var m = JsonConvert.DeserializeObject(json, typeof(MyClass), new MyClassConverter()) as MyClass;
Here's a fiddle
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();
}
}