JSON.Net Throws Stackoverflowexception When Using [JSONconvert()]

JSON.Net throws StackOverflowException when using [JsonConvert()]

Json.NET does not have convenient support for converters that call JToken.FromObject to generate a "default" serialization and then modify the resulting JToken for output - precisely because the StackOverflowException due to recursive calls to JsonConverter.WriteJson() that you have observed will occur.

One workaround is to temporarily disable the converter in recursive calls using a thread static Boolean. A thread static is used because, in some situations including asp.net-web-api, instances of JSON converters will be shared between threads. In such situations disabling the converter via an instance property will not be thread-safe.

public class FJson : JsonConverter
{
[ThreadStatic]
static bool disabled;

// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }

public override bool CanWrite { get { return !Disabled; } }

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t;
using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
{
t = JToken.FromObject(value, serializer);
}

if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}

JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}

private void WriteJson(JsonWriter writer, JObject value)
{
foreach (var p in value.Properties())
{
if (p.Value is JObject)
WriteJson(writer, (JObject)p.Value);
else
p.WriteTo(writer);
}
}

public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}

public override bool CanConvert(Type objectType)
{
return true; // works for any type
}
}

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
}

Having done this, you can restore the [JsonConverter(typeof(FJson))] to your class A:

[JsonConverter(typeof(FJson))]
public class A
{
}

Demo fiddle #1 here.

A second, simpler workaround for generating a default JToken representation for a type with a JsonConverter applied takes advantage fact that a converter applied to a member supersedes converters applied to the type, or in settings. From the docs:

The priority of which JsonConverter is used is the JsonConverter defined by attribute on a member, then the JsonConverter defined by an attribute on a class, and finally any converters passed to the JsonSerializer.

Thus it is possible to generate a default serialization for your type by nesting it inside a DTO with a single member whose value is an instance of your type and has a dummy converter applied which does nothing but fall back to to default serialization for both reading and writing.

The following extension method and converter do the job:

public static partial class JsonExtensions
{
public static JToken DefaultFromObject(this JsonSerializer serializer, object value)
{
if (value == null)
return JValue.CreateNull();
var dto = Activator.CreateInstance(typeof(DefaultSerializationDTO<>).MakeGenericType(value.GetType()), value);
var root = JObject.FromObject(dto, serializer);
return root["Value"].RemoveFromLowestPossibleParent() ?? JValue.CreateNull();
}

public static object DefaultToObject(this JToken token, Type type, JsonSerializer serializer = null)
{
var oldParent = token.Parent;

var dtoToken = new JObject(new JProperty("Value", token));
var dtoType = typeof(DefaultSerializationDTO<>).MakeGenericType(type);
var dto = (IHasValue)(serializer ?? JsonSerializer.CreateDefault()).Deserialize(dtoToken.CreateReader(), dtoType);

if (oldParent == null)
token.RemoveFromLowestPossibleParent();

return dto == null ? null : dto.GetValue();
}

public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
// If the parent is a JProperty, remove that instead of the token itself.
var contained = node.Parent is JProperty ? node.Parent : node;
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (contained is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}

interface IHasValue
{
object GetValue();
}

[JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy), IsReference = false)]
class DefaultSerializationDTO<T> : IHasValue
{
public DefaultSerializationDTO(T value) { this.Value = value; }

public DefaultSerializationDTO() { }

[JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)]
public T Value { get; set; }

object IHasValue.GetValue() { return Value; }
}
}

public class NoConverter : JsonConverter
{
// NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
// To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
// By https://stackoverflow.com/users/3744182/dbc
public override bool CanConvert(Type objectType) { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }

public override bool CanRead { get { return false; } }

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); }

public override bool CanWrite { get { return false; } }

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
}

And then use it in FJson.WriteJson() as follows:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = serializer.DefaultFromObject(value);

// Remainder as before
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}

JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}

The advantages and disadvantages of this approach are that:

  1. It doesn't rely on recursively disabling the converter, and so works correctly with recursive data models.

  2. It doesn't require re-implementing the entire logic of serializing an object from its properties.

  3. It serializes to and deserializes from an intermediate JToken representation. It is not appropriate for use when attempt to stream a default serialization directly to and from a the incoming JsonReader or JsonWriter.

Demo fiddle #2 here.

Notes

  • Both converter versions only handle writing; reading is not implemented.

    To solve the equivalent problem during deserialization, see e.g. Json.NET custom serialization with JsonConverter - how to get the "default" behavior.

  • Your converter as written creates JSON with duplicated names:

    {
    "id": 1,
    "name": null,
    "name": "value"
    }

    This, while not strictly illegal, is generally considered to be bad practice and so should probably be avoided.

StackOverflowException running SerializeObject

OK, so the resolution for this was somewhat of a compromise. We now have no customer JsonConverter types in the system and instead, use ISerializationBinder.

public class OptionsSerializationBinder : ISerializationBinder
{
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
typeName = serializedType.Name;
}

public Type BindToType(string assemblyName, string typeName)
{
Type type = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic)
.SelectMany(a => a.GetTypes())
.FirstOrDefault(t => t.Name.Equals(typeName) || t.FullName.Equals(typeName));
if (type is null || type.IsAbstract || type.IsInterface)
{
Log.Warning("Unable to load Processor: {typename}", typeName);
throw new InvalidOperationException();
}
return type;
}
}

Setting the Assembly option to null is critical to maintain the friendly names that we want.

    private static JsonSerializerSettings GetSerializerSettings(TypeNameHandling typeHandling = TypeNameHandling.Auto)
{
return new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = typeHandling,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
SerializationBinder = new OptionsSerializationBinder(),
Formatting = Formatting.Indented
};
}

We can then set SerializationBinder and the default the TypeNameHandling to auto. We found when setting this to Object that it was too greedy and tried to write a $type for generic lists and such, creating a nasty look that did not serilise. Auto provided the right level for us.

If you need to use Object which will also apply to the root of your object map then you may need to create a customer wrapper class around any List<> or Dictionary<> object that you want to serialise to make sure it gets a friendly name.

StackOverflow exception when using custom JsonConverter and custom ContractResolver

I think you have an XY Problem going on here.

I think the real problem you are trying to solve is that you have some deeply nested JSON which you want to deserialize into a simpler, flattened model, and then you want to be able to serialize that simpler model to JSON.

You've found a possible solution for the deserialization part, a converter which overloads the [JsonProperty] attribute to accept a path for each property, making it easier to flatten to a simpler model. You've used a [JsonConverter] attribute to apply the converter to your class because you don't want to modify the global settings for Web API.

But now, when you serialize, the [JsonProperty] names (paths) are getting picked up by the serializer, which you don't want. So you needed a way to ignore them. You then found a possible solution for that, involving using a custom contract resolver to revert the [JsonProperty] names back to their original values. You are trying to apply the resolver inside the converter's WriteJson method, but when you try to serialize your object inside the converter, the converter gets called recursively due to the fact that the model class has a [JsonConverter] attribute on it. So now you're stuck, and you're asking how to get around this latest problem. Am I right so far?

OK, so let's back up a few steps and solve the real problem. You want to deserialize a deeply nested JSON into a simple model and then serialize that simple model to JSON. I think you are on the right track using a converter to flatten the JSON down on deserialization, but there's no reason that the converter has to hijack the [JsonProperty] attribute to do it. It could instead use its own custom attribute for the JSON paths so as not to interfere with the normal operation of the serializer. If you do that, then you can make the converter's CanWrite method return false, which will cause the serializer to ignore the converter and use the default property names, which is what you wanted in the first place.

So, here is what you need to do:

First, make a custom attribute class to use for the property paths:

public class JsonPathAttribute : Attribute
{
public JsonPathAttribute(string jsonPath)
{
JsonPath = jsonPath;
}

public string JsonPath { get; set; }
}

Next, change the ReadJson method of your converter to look for this new attribute instead of [JsonProperty], and make the CanWrite method return false. You can also get rid of the implementation for the WriteJson method, as it will never be called. The custom resolver class is not needed either.

public class JsonPathConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
var jo = JObject.Load(reader);
object targetObj = Activator.CreateInstance(objectType);

foreach (var prop in objectType.GetProperties()
.Where(p => p.CanRead && p.CanWrite))
{
var att = prop.GetCustomAttributes(true)
.OfType<JsonPathAttribute>()
.FirstOrDefault();
var jsonPath = (att != null ? att.JsonPath : prop.Name);
var token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null)
{
var value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}

return targetObj;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is not called when CanWrite returns false
}

public override bool CanWrite
{
get { return false; }
}

public override bool CanConvert(Type objectType)
{
// CanConvert is not called when [JsonConverter] attribute is used
return false;
}
}

Finally, change your model classes to use the new attribute. Note that you will also need to mark both classes with the [JsonConverter] attribute; in your question you only marked the first one.

[JsonConverter(typeof(JsonPathConverter))]
public class FacebookFeed
{
public FacebookFeed()
{
Posts = new List<FacebookFeedPost>();
}

[JsonPath("name")]
public string Name { get; set; }

[JsonPath("fan_count")]
public int Likes { get; set; }

[JsonPath("feed.data")]
public List<FacebookFeedPost> Posts { get; set; }
}

[JsonConverter(typeof(JsonPathConverter))]
public class FacebookFeedPost
{
[JsonPath("id")]
public string Id { get; set; }

[JsonPath("message")]
public string Message { get; set; }

[JsonPath("created_time")]
public DateTime Date { get; set; }

[JsonPath("comments.summary.total_count")]
public int Comments { get; set; }
}

And that's it. It should now work the way you want.

Demo fiddle: https://dotnetfiddle.net/LPPAmH

StackOverflowException in JsonConvert.DeserializeXmlNode

Update

And promptly fixed in change set 822c3f0. Should be in the next release after 10.0.2.

Original Answer

It looks like a change to JsonTextReader in version 8.0.1 may have uncovered a bug in XmlNodeConverter.

In 7.0.1, when the unexpected end of file is reached, JsonReader.TokenType becomes JsonToken.None after the next attempt to Read(), which causes DeserializeNode() to throw an Unexpected JsonToken when deserializing node: None exception. But in 8.0.1 and later the TokenType appears to stay stuck at the type of the last encountered token, namely JsonToken.PropertyName, which causes the infinite recursion.

The correct fix would be, in XmlNodeConverter.DeserializeNode() around line 2171, to check the return from reader.Read():

case JsonToken.PropertyName:
if (currentNode.NodeType == XmlNodeType.Document && document.DocumentElement != null)
{
throw JsonSerializationException.Create(reader, "JSON root object has multiple properties. The root object must have a single property in order to create a valid XML document. Consider specifying a DeserializeRootElementName.");
}

string propertyName = reader.Value.ToString();
// Need to check the return from reader.Read() here:
if (!reader.Read())
{
throw JsonSerializationException.Create(reader, "Unexpected end of file when deserializing property: " + propertyName );
}

... And it appears there are a few more places in XmlNodeConverter.cs where the return from reader.Read() needs to be checked, for instance in ReadAttributeElements(JsonReader reader, XmlNamespaceManager manager) around line 1942.

You could report an issue if you want.

In the meantime, your options for a workaround would be:

  • Corrupt the JSON in a different manner, for instance like so:

    string json = @"{'Row' : }";

    And check for the more general exception JsonException.

  • Pre-parse the JSON into a JToken:

    Assert.Throws<JsonException>(()=>JsonConvert.DeserializeXmlNode(JToken.Parse(json).ToString(), "ROOT"));

Custom JsonConvertor that also serializes it's value minus one of it's properties

Your problem is that, inside JsonConverter.ReadJson(), you are attempting to recursively serialize your value object, but since the converter is applied directly to the type using [JsonConverter(typeof(TConverter))], you are getting a stack overflow.

There are several options to disable a converter for recursive serialization; JSON.Net throws StackOverflowException when using [JsonConvert()] details some of them. However, since you are already using a custom contract resolver IgnorePropertiesResolver to ignore properties named "id", you might enhance the resolver to also ignore converters of type FieldTypeConvertor. The following should do the trick:

public class IgnorePropertiesResolver : DefaultContractResolver
{
readonly HashSet<string> propertiesToIgnore;
readonly HashSet<Type> converterTypesToIgnore;

public IgnorePropertiesResolver(IEnumerable<string> propertiesToIgnore, IEnumerable<Type> converterTypesToIgnore) : base() =>
(this.propertiesToIgnore, this.converterTypesToIgnore) =
((propertiesToIgnore ?? throw new ArgumentNullException()).ToHashSet(StringComparer.OrdinalIgnoreCase),
(converterTypesToIgnore ?? throw new ArgumentNullException()).ToHashSet());

protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (propertiesToIgnore.Contains(member.Name))
property.Ignored = true;
if (property.Converter != null && converterTypesToIgnore.Contains(property.Converter.GetType()))
property.Converter = null;
return property;
}

protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
if (contract.Converter != null && converterTypesToIgnore.Contains(contract.Converter.GetType()))
contract.Converter = null;
return contract;
}
};

Then modify FieldTypeConvertor as follows:

public sealed class FieldTypeConvertor : JsonConverter<UmbracoFormFieldDto>
{
static readonly IContractResolver innerResolver = new IgnorePropertiesResolver(new [] { "id" }, new [] { typeof(FieldTypeConvertor) })
{
NamingStrategy = new CamelCaseNamingStrategy(),
};

public override void WriteJson(JsonWriter writer, UmbracoFormFieldDto value, JsonSerializer serializer)
{
var props = value.GetType().GetProperties();
var idProp = props.FirstOrDefault(p => p.Name.Equals("id", StringComparison.OrdinalIgnoreCase));
var key = idProp.GetValue(value, null).ToString();

writer.WriteStartObject();
writer.WritePropertyName(key);
JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = innerResolver }).Serialize(writer, value);
writer.WriteEndObject();
}

public override UmbracoFormFieldDto ReadJson(JsonReader reader, Type objectType, UmbracoFormFieldDto existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException();
}

And your model will be serialized as required:

{
"idValueFromOriginalObj": {
"condition": "propValue",
"fieldType": "propValue",
"label": "propValue",
"options": "propValue"
}
}

Notes:

  • Newtonsoft recommends you cache the contract resolver for best performance.

  • You should inherit from DefaultContractResolver instead of CamelCasePropertyNamesContractResolver for reasons explained in Json.Net: Html Helper Method not regenerating.

  • For performance reasons, I eliminated the intermediate serialization to JObject and instead serialized directly to the incoming JsonWriter.

Demo fiddle here.

Json.Net Self Recurse on Custom Converter

I made a deeper exploration into the Newtonsoft.Json framework and found out how it serialize and deserialize objects without converters.

Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal

Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue

Because these classes and methods are internal, I have to use Reflection to invoke them. Thus I encapsulate this into two extension methods:

public static class NewtonsoftExtensions{
private static Type JsonSerializerInternalReader { get; } = typeof(JsonSerializer).Assembly.GetType("Newtonsoft.Json.Serialization.JsonSerializerInternalReader");

private static Type JsonSerializerInternalWriter { get; } = typeof(JsonSerializer).Assembly.GetType("Newtonsoft.Json.Serialization.JsonSerializerInternalWriter");

private static MethodInfo CreateValueInternal { get; } = JsonSerializerInternalReader.GetMethod("CreateValueInternal", BindingFlags.NonPublic | BindingFlags.Instance);

private static MethodInfo SerializeValue { get; } = JsonSerializerInternalWriter.GetMethod("SerializeValue", BindingFlags.NonPublic | BindingFlags.Instance);

public object DeserializeWithoutContractConverter(this JsonSerializer serializer, JsonReader reader, Type objectType) {
var contract = serializer.ContractResolver.ResolveContract(objectType);
var converter = contract.Converter;
contract.Converter = null;
object internalReader = Activator.CreateInstance(JsonSerializerInternalReader, serializer);
object result = CreateValueInternal.Invoke(internalReader, reader, objectType, contract, null, null, null, null);
contract.Converter = converter; //DefaultContractResolver caches the contract of each type, thus we need to restore the original converter for future use
return result;
}

public void SerializeWithoutContractConverter(this JsonSerializer serializer, JsonWriter writer, object value) {
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
var converter = contract.Converter;
contract.Converter = null;
object internalWriter = Activator.CreateInstance(JsonSerializerInternalWriter, serializer);
SerializeValue.Invoke(internalWriter, writer, value, contract, null, null, null);
contract.Converter = converter;
}
}

Using reflection to call internal methods is risky and should not be recommended, but compared with other answers in JSON.Net throws StackOverflowException when using [JsonConvert()], such approach would make full use of serializer settings. If the converter is general-purpose, like the ObjectWrapperConverter I'm trying to implement, this will cause least the unexpected results, as Newtonsoft.Json has tons of settings for users to customize the behaviors.

JSON.NET StackOverflowException while serialization

The reason you are getting the stackoverflow exception is that Json.NET is a recursive, single-pass tree or graph serializer that, when PreserveReferencesHandling.Objects is enabled, always serializes the first occurrence of each object. You have constructed your 15,000 element Chacha [] array so that the first entry is the head of a linked list containing all the other items linked sequentially. Json.NET will try to serialize that to nested JSON objects 15,000 levels deep via 15,000 levels of recursion, overflowing the stack in the process.

Thus what you need to do is write the entire table of linkages only at the head of the list, as a JSON array. Unfortunately, however, Json.NET is also a contract-based serializer which means it will try to write the same properties whenever it encounters an object of a given type, no matter what the nesting depth is. Thus adding a Chacha[] NextChachaList property to your Chacha object doesn't help since it will get written at each level. Instead it will be necessary to create a fairly complex custom JsonConverter that tracks the serialization depth in a thread-safe manner and only writes the linkage list only at the top level. The following does the trick:

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;


Related Topics



Leave a reply



Submit