Reputation: 142
A class was serialized using the following sample
using Newtonsoft.Json;
using System;
namespace ConsoleAppCompare
{
class Program
{
static void Main(string[] args)
{
Movie movie = new Movie()
{
Name = "Avengers",
Language = "En",
Actors = new Character[] { new Character(){Name="Phil Coulson"},new Character(){Name="Tony Stark"}
}};
Console.WriteLine(JsonConvert.SerializeObject(movie, Formatting.Indented, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }));
Console.ReadLine();
}
}
class Movie
{
public string Name { get; set; }
public string Language { get; set; }
public Character[] Actors { get; set; }
}
class Character
{
public string Name { get; set; }
}
}
The above sample produces the following json
{
"$type": "ConsoleAppCompare.Movie, ConsoleAppCompare",
"Name": "Avengers",
"Language": "En",
"Actors": {
"$type": "ConsoleAppCompare.Character[], ConsoleAppCompare",
"$values": [
{
"$type": "ConsoleAppCompare.Character, ConsoleAppCompare",
"Name": "Phil Coulson"
},
{
"$type": "ConsoleAppCompare.Character, ConsoleAppCompare",
"Name": "Tony Stark"
}
]
}
}
Now,on another program ,that doesn't have access to the above models,
I have to deserialize the string to an object but nothing I've tried seems to be working...
To create my new models I copied the json on my clipboard and used Visual Studio's "Paste Special" functionality
using Newtonsoft.Json;
using System;
using System.IO;
namespace ConsoleAppCompare
{
class Program
{
static void Main(string[] args)
{
var s = File.ReadAllText(@"C:\Users\nvovo\Desktop\asdf\aa.txt");
Rootobject movie = null;
// nothing Works :(
//movie =JsonConvert.DeserializeObject<Rootobject>(s, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
//movie = JsonConvert.DeserializeObject<Rootobject>(s, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None });
//movie = JsonConvert.DeserializeObject<Rootobject>(s);
Console.ReadLine();
}
}
public class Rootobject
{
public string type { get; set; }
public string Name { get; set; }
public string Language { get; set; }
public Actors Actors { get; set; }
}
public class Actors
{
public string type { get; set; }
public Values[] values { get; set; }
}
public class Values
{
public string type { get; set; }
public string Name { get; set; }
}
}
Can I do anything about this or I should try to find the original classes?
Update
I don't care about the "$type" property. It's not even on the original models. I just want to deserialize the JSON to a strongly typed model, including the collections (my real classes have more nested levels) but the auto generated types (using Paste Json) don't work.
Upvotes: 3
Views: 1885
Reputation: 117086
If all you want to do is to ignore the type information, then:
If you deserialize with TypeNameHandling.None
, then "$type"
properties on objects are simply ignored will cause no problem during deserialization.
But even with TypeNameHandling.None
, "$type"
properties for collection values cause problems because the type metadata generated for collection contains forces an extra level of nesting in the JSON:
With "type"
:
{
"Actors": {
"$type": "ConsoleAppCompare.Character[], ConsoleAppCompare",
"$values": []
}
}
And without:
{
"Actors": []
}
When deserializing JSON with TypeNameHandling.None
, if a serialized collection with the extra nesting level is encountered, an exception gets thrown.
So you need some way to strip off the extra level of nesting during deserialization, e.g. with a custom JsonConverter
. And in this answer to the question Strategies for migrating serialized Json.NET document between versions/formats there is one already written and available for use: IgnoreCollectionTypeConverter
.
Thus you can define your models as follows:
public class Rootobject
{
public string Name { get; set; }
public string Language { get; set; }
public List<Actor> Actors { get; set; }
}
public class Actor
{
public string Name { get; set; }
}
And deserialize as follows:
var settings = new JsonSerializerSettings
{
Converters = { new IgnoreCollectionTypeConverter() },
};
var movie = JsonConvert.DeserializeObject<Rootobject>(s, settings);
Sample fiddle.
Notes:
IgnoreCollectionTypeConverter
is designed to work with read/write collections, which is why I changed Actors
from an array to a List<T>
.
If you need to process the type information rather than ignore it, you will need to create a custom ISerializationBinder
. See Custom SerializationBinder for details. The question How to create a SerializationBinder for the Binary Formatter that handles the moving of types from one assembly and namespace to another gives a solution for creating a custom serialization binder that handles nesting of generics.
Update
You asked, I just want to deserialize the json to a strongly typed model ,including the collections (my real classes have more nested levels) but the auto generated types (using Paste Json) don't work.
During your development process, you can use LINQ to JSON to load your JSON into memory, remove all "$type"
metadata, and write out to a new JSON string. Then, you can take that cleaned string and use it for "Paste Json as Classes".
The following extension method will do the necessary work:
public static class JsonExtensions
{
const string valuesName = "$values";
const string typeName = "$type";
public static JToken RemoveTypeMetadata(this JToken root)
{
if (root == null)
throw new ArgumentNullException();
var types = root.SelectTokens(".." + typeName).Select(v => (JProperty)v.Parent).ToList();
foreach (var typeProperty in types)
{
var parent = (JObject)typeProperty.Parent;
typeProperty.Remove();
var valueProperty = parent.Property(valuesName);
if (valueProperty != null && parent.Count == 1)
{
// Bubble the $values collection up removing the synthetic container object.
var value = valueProperty.Value;
if (parent == root)
{
root = value;
}
// Remove the $values property, detach the value, then replace it in the parent's parent.
valueProperty.Remove();
valueProperty.Value = null;
if (parent.Parent != null)
{
parent.Replace(value);
}
}
}
return root;
}
}
Sample working .Net fiddle which takes your input JSON string and returns:
{
"Name": "Avengers",
"Language": "En",
"Actors": [
{
"Name": "Phil Coulson"
},
{
"Name": "Tony Stark"
}
]
}
Upvotes: 3