Json.net serialize/deserialize derived types?
If you are storing the type in your text
(as you should be in this scenario), you can use the JsonSerializerSettings
.
See: how to deserialize JSON into IEnumerable<BaseType> with Newtonsoft JSON.NET
Be careful, though. Using anything other than TypeNameHandling = TypeNameHandling.None
could open yourself up to a security vulnerability.
Json.Net deserialize into C# derived class
It looks like the problem might be due to the fact that the $type
metadata property is not the first property in the JSON for your control. Normally, Json.Net needs this property to be first in order to recognize it. Try adding
MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead
to your settings when deserializing. That should allow Json.Net to find the $type
property later in the JSON.
Deserialize JSON into multiple inherited classes
You could try serializing your objects yourself with Json.NET and then posting the serialized content into DocumentDb. Then, when you need the data, read it back as a json string and use Json.NET again to deserialize.
Json.NET can handle inheritance, so you just have to configure it to be aware of your type hierarchy. Use the TypeNameHandling setting:
http://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
How do I deserialize a derived class with json.net
Here is yours example: http://dotnetbyexample.blogspot.ru/2012/02/json-deserialization-with-jsonnet-class.html?m=1
You just have to use overloaded method
JsonConvert.DeserializeObject<List<Item>>
(r.EventArgs.Result, params Newtonsoft.Json.JsonConverter[] converters)
And write your converters
JSON.Net Serializing Derived Classes
The issue here is that your Group<K, T>
is a collection that also has properties. Since a JSON container can either be an array (with no properties) or an object (with named key/value pairs), a collection with custom properties cannot be mapped automatically to either without data loss. Json.NET (and all other serializers AFAIK) choose to map the items not the custom properties.
You have a couple ways to deal with this:
Write your own custom
JsonConverter
. You can determine the generic arguments using reflection along the lines of Json.Net returns Empty Brackets.Mark your
Group<K, T>
with[JsonObject]
.
The second option seems simplest, and would look like:
[JsonObject(MemberSerialization = MemberSerialization.OptIn)] // OptIn to omit the properties of the base class, e.g. Count
class Group<K, T> : ObservableCollection<T>
{
[JsonProperty("Header")]
public K Key { get; set; }
[JsonProperty("Items")]
IEnumerable<T> Values
{
get
{
foreach (var item in this)
yield return item;
}
set
{
if (value != null)
foreach (var item in value)
Add(item);
}
}
public Group(K Header, IEnumerable<T> Items) // Since there is no default constructor, argument names should match JSON property names.
: base(Items)
{
Key = Header;
}
}
Incidentally, you have another problem -- your Ingredient
class does not have a default constructor, and its single parameterized throws a NullReferenceException
if the line
argument is null. In the absence of a default constructor Json.NET will call the single parameterized constructor, mapping JSON object values to constructor arguments by name. Thus, deserialization throws an exception.
You have a few ways to deal with this:
Add a public default constructor.
Add a private default constructor and mark it with
[JsonConstructor]
:[JsonConstructor]
Ingredient() { }Add a private default constructor and deserialize with
ConstructorHandling.AllowNonPublicDefaultConstructor
:var settings = new JsonSerializerSettings { ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor };
var recipe = JsonConvert.DeserializeObject<Recipe>(json, settings);Add an
if (line != null)
check in the constructor. (Not really recommended. Instead your constructor should explicitly throw anArgumentNullException
.)
Having done this, you will gt JSON that looks like:
{
"IngredientsWithHeaders": [
{
"Header": "BlankHeader",
"Items": [
{
"Quantity": "3",
"Modifier": null,
"Unit": "tbsp",
"IngredientName": "butter",
"Preparation": null
}
]
}
],
}
Your proposed JSON has an extra level of nesting with
{
"IngredientsWithHeaders": [
{
"Group": {
"Header": "BlankHeader",
This extra "Group"
object is unnecessary.
Json.NET - handle unknown derived types when using TypeNameHandling
Here's a custom JsonConverter
that seems to solve the problem:
public class DefaultToUnknownConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Animal).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
JObject jObject = JObject.Load(reader);
try
{
// attempt to deserialize to known type
using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
{
// create new serializer, as opposed to using the serializer parm, to avoid infinite recursion
JsonSerializer tempSerializer = new JsonSerializer()
{
TypeNameHandling = TypeNameHandling.Objects
};
return tempSerializer.Deserialize<Animal>(jObjectReader);
}
}
catch (JsonSerializationException)
{
// default to Unknown type when deserialization fails
return jObject.ToObject<Unknown>();
}
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static JsonReader CopyReaderForObject(JsonReader reader, JToken jToken)
{
// create reader and copy over settings
JsonReader jTokenReader = jToken.CreateReader();
jTokenReader.Culture = reader.Culture;
jTokenReader.DateFormatString = reader.DateFormatString;
jTokenReader.DateParseHandling = reader.DateParseHandling;
jTokenReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jTokenReader.FloatParseHandling = reader.FloatParseHandling;
jTokenReader.MaxDepth = reader.MaxDepth;
jTokenReader.SupportMultipleContent = reader.SupportMultipleContent;
return jTokenReader;
}
Add the converter to the deserializer settings:
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects,
Converters = { new DefaultToUnknownConverter() }
};
The DefaultToUnknownConverter
class could be made more generic by passing in the json settings, using generics, etc..
Reference for implementing a custom JsonConverter: https://stackoverflow.com/a/21632292/674237
Deserialization with inheritance Newtonsoft JSON.NET not working properly
Json.Net normally expects the $type
metadata to be the first property of each object for best efficiency in deserialization. If the $type
does not appear first, then Json.Net assumes it isn't there. That is why you are getting different results when the properties are reordered.
Fortunately, Json.Net provides a MetadataPropertyHandling
setting to allow it to cope with this situation. If you set MetadataPropertyHandling
to ReadAhead
it should solve your problem. Note that this will have an impact on performance.
Here is a code sample from the documentation.
string json = @"{
'Name': 'James',
'Password': 'Password1',
'$type': 'MyNamespace.User, MyAssembly'
}";
object o = JsonConvert.DeserializeObject(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
// $type no longer needs to be first
MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead
});
User u = (User)o;
Console.WriteLine(u.Name);
// James
Related Topics
How to Cast Object of Type 'System.Dbnull' to Type 'System.String'
Visual Studio Publish Project into One Simple Installer
Fluent and Query Expression - Is There Any Benefit(S) of One Over Other
Can You Overload Controller Methods in ASP.NET MVC
Escape Double Quotes in a String
How to Post Data to Specific Url Using Webclient in C#
Filtering Datagridview Without Changing Datasource
How to Get Output from a Command to Appear in a Control on a Form in Real-Time
Splitting a String into Chunks of a Certain Size
What's the Main Difference Between Int.Parse() and Convert.Toint32
Using Transactions or Savechanges(False) and Acceptallchanges()
Creating a Dpi-Aware Application
Change Default App.Config At Runtime
The Order of Elements in Dictionary