Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
199 views
in Technique[技术] by (71.8m points)

c# - deserialize string that was serialized with TypeNameHandling.All

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
vovoDesktopasdfaa.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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:

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"
    }
  ]
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...