Question40978662 circular-reference-exception-when-serializing-an-object-containing-a-jtoken-to-x

CSharp · Console

/Program.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Runtime.Serialization;
using System.Data;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Specialized;
using System.Web.Routing;
using System.Net.Mail;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Net;
using System.Runtime.Serialization.Formatters;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System.Web.SessionState;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Xml;

    // https://stackoverflow.com/questions/40978662/circular-reference-exception-when-serializing-an-object-containing-a-jtoken-to-x

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }

        [XmlIgnore]
        [JsonProperty]
        public object ExtraData { get; set; }

        [XmlAnyElement("ExtraData")]
        [JsonIgnore]
        public XElement ExtraDataXml
        {
            get
            {
                return JsonExtensions.SerializeExtraDataXElement("ExtraData", ExtraData);
            }
            set
            {
                ExtraData = JsonExtensions.DeserializeExtraDataXElement("ExtraData", value);
            }
        }
    }

    public static class JsonExtensions
    {
        public static XElement SerializeExtraDataXElement(string name, object extraData)
        {
            if (extraData == null)
                return null;
            var token = JToken.FromObject(extraData);
            if (token is JValue)
            {
                return new XElement(name, (string)token);
            }
            else if (token is JArray)
            {
                return new JObject(new JProperty(name, token)).ToXElement(false, name, true);
            }
            else
            {
                return token.ToXElement(false, name, true);
            }
        }

        public static object DeserializeExtraDataXElement(string name, XElement element)
        {
            object extraData;
            if (element == null)
                extraData = null;
            else
            {
                extraData = element.ToJToken(true, name, true);
                if (extraData is JObject)
                {
                    var obj = (JObject)extraData;
                    if (obj.Count == 1 && obj.Properties().First().Name == name)
                        extraData = obj.Properties().First().Value;
                }
                if (extraData is JValue)
                {
                    extraData = ((JValue)extraData).Value;
                }
            }
            return extraData;
        }

        public static XElement ToXElement(this JToken obj, bool omitRootObject, string deserializeRootElementName, bool writeArrayAttribute)
        {
            if (obj == null)
                return null;
            using (var reader = obj.CreateReader())
            {
                var converter = new Newtonsoft.Json.Converters.XmlNodeConverter() { OmitRootObject = omitRootObject, DeserializeRootElementName = deserializeRootElementName, WriteArrayAttribute = writeArrayAttribute };
                var jsonSerializer = JsonSerializer.CreateDefault(new JsonSerializerSettings { Converters = { converter } });
                return jsonSerializer.Deserialize<XElement>(reader);
            }
        }

        public static JToken ToJToken(this XElement xElement, bool omitRootObject, string deserializeRootElementName, bool writeArrayAttribute)
        {
            // Convert to Linq to XML JObject
            var settings = new JsonSerializerSettings { Converters = { new XmlNodeConverter { OmitRootObject = omitRootObject, DeserializeRootElementName = deserializeRootElementName, WriteArrayAttribute = writeArrayAttribute } } };
            var root = JToken.FromObject(xElement, JsonSerializer.CreateDefault(settings));
            return root;
        }
    }

    public class TestClass
    {
        static string GetJson1()
        {
            var json = @"{
  ""Name"": ""Bob"",
  ""Age"": 31,
  ""ExtraData"": {
    ""c1"": 1,
    ""c2"": 2
  }
}";
            return json;
        }

        static string GetJson2()
        {
            var json = @"{
  ""Name"": ""Bob"",
  ""Age"": 31,
  ""ExtraData"": ""hello""
}";
            return json;
        }

        static string GetJson3()
        {
            var json = @"{
  ""Name"": ""Bob"",
  ""Age"": 31,
  ""ExtraData"": null
}";
            return json;
        }

        static string GetJson4()
        {
            var json = @"{
  ""Name"": ""Bob"",
  ""Age"": 31,
  ""ExtraData"": [ ""hello"", ""there"" ]
}";
            return json;
        }

        public static void Test()
        {
            foreach (var json in new[] { GetJson1(), GetJson2(), GetJson3(), GetJson4() })
            {
                Test(json);
            }
        }

        private static void Test(string json)
        {
            Console.WriteLine("Original JSON: ");
            Console.WriteLine(json);

            var person = JsonConvert.DeserializeObject<Person>(json);

            var xml = person.GetXml(true);

            Console.WriteLine("Deserialized and reserialized as XML:");
            Console.WriteLine(xml);

            var person2 = xml.LoadFromXml<Person>();

            var json2 = JsonConvert.SerializeObject(person2, Newtonsoft.Json.Formatting.Indented);

            Console.WriteLine("And then deserialized and reserialized as JSON:");
            Console.WriteLine(json2);

            if (JTokenComparer.Instance.Compare(Newtonsoft.Json.Linq.JToken.Parse(json), Newtonsoft.Json.Linq.JToken.Parse(json2)) != 0)
            {
                throw new InvalidOperationException("JTokenComparer.Instance.Compare(Newtonsoft.Json.Linq.JToken.Parse(json), Newtonsoft.Json.Linq.JToken.Parse(json2)) != 0");
            }
            else
            {
                Console.WriteLine("Original and re-serialized JSON are equivalent.");
            }

            Console.WriteLine();
        }
    }

					
	public class Program
	{
		public static void Main()
		{
            Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName + "\n");
			
			TestClass.Test();
		}
	}

    public static class XmlSerializationHelper
    {
        public static T LoadFromXml<T>(this string xmlString)
        {
            using (StringReader reader = new StringReader(xmlString))
            {
                return (T)new XmlSerializer(typeof(T)).Deserialize(reader);
            }
        }

        public static string GetXml<T>(this T obj, bool omitStandardNamespaces = false)
        {
            XmlSerializerNamespaces ns = null;
            if (omitStandardNamespaces)
            {
                ns = new XmlSerializerNamespaces();
                ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
            }			
            using (var textWriter = new StringWriter())
            {
                var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
                using (var xmlWriter = XmlWriter.Create(textWriter, settings))
                    new XmlSerializer(obj.GetType()).Serialize(xmlWriter, obj, ns);
                return textWriter.ToString();
            }
        }
    }

    public class JTokenComparer : IComparer<JToken>
    {
        public static JTokenComparer Instance { get { return instance; } }

        static JTokenComparer instance;

        static JTokenComparer()
        {
            instance = new JTokenComparer();
        }

        readonly Dictionary<Type, KeyValuePair<int, IComparer<JToken>>> dict;

        JTokenComparer()
        {
            dict = new Dictionary<Type, KeyValuePair<int, IComparer<JToken>>>
            {
                // Order chosen semi-arbitrarily.  Putting values first seems reasonable though.
                {typeof(JValue), new KeyValuePair<int, IComparer<JToken>>(0, new JValueComparer()) },
                {typeof(JProperty), new KeyValuePair<int, IComparer<JToken>>(1, new JPropertyComparer()) },
                {typeof(JArray), new KeyValuePair<int, IComparer<JToken>>(2, new JArrayComparer()) },
                {typeof(JObject), new KeyValuePair<int, IComparer<JToken>>(3, new JObjectComparer()) },
                {typeof(JConstructor), new KeyValuePair<int, IComparer<JToken>>(4, new JConstructorComparer()) },
            };
        }

        #region IComparer<JToken> Members

        public int Compare(JToken x, JToken y)
        {
            if (x is JRaw || y is JRaw)
                throw new InvalidOperationException("Tokens of type JRaw cannot be sorted");
            if (object.ReferenceEquals(x, y))
                return 0;
            else if (x == null)
                return -1;
            else if (y == null)
                return 1;

            var typeData1 = dict[x.GetType()];
            var typeData2 = dict[y.GetType()];

            int comp;
            if ((comp = typeData1.Key.CompareTo(typeData2.Key)) != 0)
                return JTokenComparerBase.ReturnComparison(comp);
            if (typeData1.Value != typeData2.Value)
                throw new InvalidOperationException("inconsistent dictionary values"); // Internal error
            return typeData2.Value.Compare(x, y);
        }

        #endregion
    }

    abstract class JTokenComparerBase
    {
        [Conditional("DEBUG")]
        static void OnDifference(int comparison)
        {
            if (comparison != 0)
            {
                // Just something to set a breakpoint at in the debugger.
                GC.KeepAlive(string.Empty);
            }
        }

        protected internal static int ReturnComparison(int comparison)
        {
            OnDifference(comparison);
            return comparison;
        }
    }

    abstract class JTokenComparerBase<TJToken> : JTokenComparerBase, IComparer<JToken> where TJToken : JToken
    {
        protected TJToken CheckType(JToken item)
        {
            if (item != null && item.GetType() != typeof(TJToken))
                throw new ArgumentException(string.Format("Actual type {0} of token \"{1}\" does not match expected type {2}", item.GetType(), item, typeof(TJToken)));
            return (TJToken)item;
        }

        protected bool TryBaseCompare(TJToken x, TJToken y, out int comparison)
        {
            CheckType(x);
            CheckType(y);
            if (object.ReferenceEquals(x, y))
            {
                comparison = 0;
                return true;
            }
            else if (x == null)
            {
                comparison = -1;
                return true;
            }
            else if (y == null)
            {
                comparison = 1;
                return true;
            }
            comparison = 0;
            return false;
        }

        protected abstract int CompareDerived(TJToken x, TJToken y);

        protected int TokenCompare(JToken x, JToken y)
        {
            var tx = CheckType(x);
            var ty = CheckType(y);
            int comp;
            if (TryBaseCompare(tx, ty, out comp))
                return ReturnComparison(comp);
            return CompareDerived(tx, ty);
        }

        #region IComparer<JToken> Members

        int IComparer<JToken>.Compare(JToken x, JToken y)
        {
            return TokenCompare(x, y);
        }

        #endregion
    }

    abstract class JContainerOrderedComparerBase<TJToken> : JTokenComparerBase<TJToken> where TJToken : JContainer
    {
        protected int CompareItemsInOrder(TJToken x, TJToken y)
        {
            int comp;
            // Dictionary order: sort on items before number of items.
            for (int i = 0, n = Math.Min(x.Count, y.Count); i < n; i++)
                if ((comp = JTokenComparer.Instance.Compare(x[i], y[i])) != 0)
                    return ReturnComparison(comp);
            if ((comp = x.Count.CompareTo(y.Count)) != 0)
                return ReturnComparison(comp);
            return ReturnComparison(0);
        }
    }

    class JPropertyComparer : JTokenComparerBase<JProperty>
    {
        protected override int CompareDerived(JProperty x, JProperty y)
        {
            int comp;
            if ((comp = x.Name.CompareTo(y.Name)) != 0)
                return ReturnComparison(comp);
            return JTokenComparer.Instance.Compare(x.Value, y.Value);
        }
    }

    class JObjectComparer : JTokenComparerBase<JObject>
    {
        protected override int CompareDerived(JObject x, JObject y)
        {
            int comp;
            // Dictionary order: sort on items before number of items.
            // Order both property sequences to preserve reflexivity.
            foreach (var propertyComp in x.Properties().OrderBy(p => p.Name).Zip(y.Properties().OrderBy(p => p.Name), (xp, yp) => JTokenComparer.Instance.Compare(xp, yp)))
                if (propertyComp != 0)
                    return propertyComp;
            if ((comp = x.Count.CompareTo(y.Count)) != 0)
                return ReturnComparison(comp);
            return ReturnComparison(0);
        }
    }

    class JArrayComparer : JContainerOrderedComparerBase<JArray>
    {
        protected override int CompareDerived(JArray x, JArray y)
        {
            int comp;
            if ((comp = CompareItemsInOrder(x, y)) != 0)
                return ReturnComparison(comp);
            return ReturnComparison(0);
        }
    }

    class JConstructorComparer : JContainerOrderedComparerBase<JConstructor>
    {
        protected override int CompareDerived(JConstructor x, JConstructor y)
        {
            int comp;
            if ((comp = x.Name.CompareTo(y.Name)) != 0)
                return ReturnComparison(comp);
            if ((comp = CompareItemsInOrder(x, y)) != 0)
                return ReturnComparison(comp);
            return ReturnComparison(0);
        }
    }

    class JValueComparer : JTokenComparerBase<JValue>
    {
        protected override int CompareDerived(JValue x, JValue y)
        {
            return Comparer<JToken>.Default.Compare(x, y); // JValue implements IComparable<JValue>
        }
    }
;