Question41828014 json-net-stackoverflowexception-while-serialization

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.Collections.Specialized;
using System.Web.Routing;
using System.Net.Mail;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Runtime.Serialization.Formatters;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;

    class Chacha
    {
        public Chacha NextChacha { get; set; }

        public long Data { get; set; }

        public override string ToString()
        {
            return base.ToString() + ": " + Data.ToString();
        }
    }

    class ChachaConverter : LinkedListItemConverter<Chacha>
    {
        protected override bool IsNextItemProperty(JsonProperty member)
        {
            return member.UnderlyingName == "NextChacha"; // Use nameof(Chacha.NextChacha) in latest c#
        }
    }

    public abstract class LinkedListItemConverter<T> : JsonConverter where T : class
    {
        const string refProperty = "$ref";
        const string idProperty = "$id";
        const string NextItemListProperty = "nextItemList";

        [ThreadStatic]
        static int level;

        // Increments the nesting level in a thread-safe manner.
        int Level { get { return level; } set { level = value; } }

        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }

        protected abstract bool IsNextItemProperty(JsonProperty member);

        List<T> GetNextItemList(object value, JsonObjectContract contract)
        {
            var property = contract.Properties.Where(p => IsNextItemProperty(p)).Single();
            List<T> list = null;
            for (var item = (T)property.ValueProvider.GetValue(value); item != null; item = (T)property.ValueProvider.GetValue(item))
            {
                if (list == null)
                    list = new List<T>();
                list.Add(item);
            }
            return list;
        }

        void SetNextItemLinks(object value, List<T> list, JsonObjectContract contract)
        {
            var property = contract.Properties.Where(p => IsNextItemProperty(p)).Single();
            if (list == null || list.Count == 0)
                return;
            var previous = value;
            foreach (var next in list)
            {
                if (next == null)
                    continue;
                property.ValueProvider.SetValue(previous, next);
                previous = next;
            }
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            using (new PushValue<int>(Level + 1, () => Level, (old) => Level = old))
            {
                writer.WriteStartObject();

                if (serializer.ReferenceResolver.IsReferenced(serializer, value))
                {
                    writer.WritePropertyName(refProperty);
                    writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
                }
                else
                {
                    writer.WritePropertyName(idProperty);
                    writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));

                    var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());

                    // Write the data properties (if any).
                    foreach (var property in contract.Properties
                        .Where(p => p.Readable && !p.Ignored && (p.ShouldSerialize == null || p.ShouldSerialize(value))))
                    {
                        if (IsNextItemProperty(property))
                            continue;
                        var propertyValue = property.ValueProvider.GetValue(value);
                        if (propertyValue == null && serializer.NullValueHandling == NullValueHandling.Ignore)
                            continue;
                        writer.WritePropertyName(property.PropertyName);
                        serializer.Serialize(writer, propertyValue);
                    }

                    if (Level == 1)
                    {
                        // Write the NextItemList ONLY AT THE TOP LEVEL
                        var nextItems = GetNextItemList(value, contract);
                        if (nextItems != null)
                        {
                            writer.WritePropertyName(NextItemListProperty);
                            serializer.Serialize(writer, nextItems);
                        }
                    }
                }
                writer.WriteEndObject();
            }
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
            var jObject = JObject.Load(reader);

            // Detach and process $ref
            var refValue = (string)jObject[refProperty].RemoveFromLowestPossibleParent();
            if (refValue != null)
            {
                var reference = serializer.ReferenceResolver.ResolveReference(serializer, refValue);
                if (reference != null)
                    return reference;
            }

            // Construct the value
            var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(existingValue == null ? typeof(T) : existingValue.GetType());
            T value = (existingValue as T ?? (T)contract.DefaultCreator());

            // Detach and process $id
            var idValue = (string)jObject[idProperty].RemoveFromLowestPossibleParent();
            if (idValue != null)
            {
                serializer.ReferenceResolver.AddReference(serializer, idValue, value);
            }

            // Detach the (possibly large) list of next items.
            var nextItemList = jObject[NextItemListProperty].RemoveFromLowestPossibleParent();

            // populate the data properties (if any)
            serializer.Populate(jObject.CreateReader(), value);

            // Set the next item references
            if (nextItemList != null)
            {
                var list = nextItemList.ToObject<List<T>>(serializer);
                SetNextItemLinks(value, list, contract);
            }

            return value;
        }
    }

    public struct PushValue<T> : IDisposable
    {
        Action<T> setValue;
        T oldValue;

        public PushValue(T value, Func<T> getValue, Action<T> setValue)
        {
            if (getValue == null || setValue == null)
                throw new ArgumentNullException();
            this.setValue = setValue;
            this.oldValue = getValue();
            setValue(value);
        }

        #region IDisposable Members

        // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
        public void Dispose()
        {
            if (setValue != null)
                setValue(oldValue);
        }

        #endregion
    }

    public static class JsonExtensions
    {
        public static JToken RemoveFromLowestPossibleParent(this JToken node)
        {
            if (node == null)
                return null;
            var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
            if (contained != null)
                contained.Remove();
            // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
            if (node.Parent is JProperty)
                ((JProperty)node.Parent).Value = null;
            return node;
        }
    }

    class TestClass
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.All,
            PreserveReferencesHandling = PreserveReferencesHandling.Objects,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            Converters = { new ChachaConverter() },
			TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full
        };

        public static void Test()
        {
            int count = 2000; // Using the full 15000 exceeds memory limitations on dotnetfiddle.net

            Test(3, Formatting.Indented);

            Test(count, Formatting.None);
        }

        private static void Test(int count, Formatting formatting)
        {
            Console.WriteLine("Testing an array of {0} items of type {1}", count, typeof(Chacha));

            Chacha[] steps = new Chacha[count];
            steps[0] = new Chacha { Data = 0 };

            for (int i = 1; i < count; i++)
            {
                steps[i] = new Chacha { Data = i };
                steps[i - 1].NextChacha = steps[i];
            }

            AssertValidSteps(steps, count);

            string serSteps = JsonConvert.SerializeObject(steps, formatting, Settings);

            Console.WriteLine("Beginning of serialized JSON: ");
            Console.WriteLine(serSteps.Substring(0, Math.Min(2000, serSteps.Length)));

            var newSteps = JsonConvert.DeserializeObject<Chacha[]>(serSteps, Settings);

            AssertValidSteps(newSteps, count);

            Console.WriteLine("Json deserialized successfully.");
        }

        static void AssertValidSteps(Chacha[] steps, int count)
        {
            Assert.That(steps.Length == count);
            Assert.That(steps[0].Data == 0);
            for (int i = 1; i < count; i++)
            {
                Assert.That(steps[i].Data == i);
                Assert.That(steps[i - 1].NextChacha == steps[i]);
            }
        }
    }

    public class AssertionFailedException : System.Exception
    {
        public AssertionFailedException() : base() { }

        public AssertionFailedException(string s) : base(s) { }
    }

    public static class Assert
    {
        public static void That(bool value)
        {
            if (value == false)
                throw new AssertionFailedException("failed");
        }
    }

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