2

Is it possible to create an attribute to serialize some subelements inline (Formatting.None) with newtonsoft json.net?

I have a very huge set of data and I want to keep it readeable. Some subelements are not very important and can be writen inline.

{
    "name": "xxx",
    "desc": "xxx",
    "subelem": [
        {"val1": 1, "val2": 2, ...}, //inline,
        {"val1": 1, "val2": 2, ...},
        ...
    ]
    "subelem2": {
        "val1": 1,
        "val2": 2,
        ...
    }
}

I want to force the inline serialization for some sub objects of my models. In this case, "subelem" items will be written inline. Thanks

dbc
  • 104,963
  • 20
  • 228
  • 340
Demonia
  • 332
  • 3
  • 14
  • I asked this same question as while ago and got a good answer: https://stackoverflow.com/questions/28655996/how-to-apply-indenting-serialization-only-to-some-properties – Wormbo Jun 14 '15 at 18:41
  • Extending the linked answer, if you want the indenting to be based purely on depth, you can get that from [`JsonWriter.Top`](http://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_JsonWriter_Top.htm) (Though it's protected so you might need to subclass `JsonTextWriter` and make a public method.) – dbc Jun 14 '15 at 18:53
  • Unfortunately, it's not based on the depth in my current case, but I have another case that will fit with your answer, thanks I'll try with a custom converter. Do you know if it's possible to use the custom converter with a custom attribute? I will have many different objects that need this, then it would be great if I can just create a custom attribute and not a converter for each different objects. in any case, I can use your solution. – Demonia Jun 14 '15 at 19:00

1 Answers1

4

Adding the converter as a JsonConverterAttribute on a class is trickier because the simplest implementation will lead to an infinite recursion as the converter calls itself. Thus it's necessary to disable the converter for recursive calls in a thread-safe manner, like so:

public class NoFormattingConverter : JsonConverter
{
    [ThreadStatic]
    static bool cannotWrite;

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

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

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

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException(); // Should be applied as a property rather than included in the JsonSerializerSettings.Converters list.
    }

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        using (new PushValue<bool>(true, () => CannotWrite, val => CannotWrite = val))
        using (new PushValue<Formatting>(Formatting.None, () => writer.Formatting, val => writer.Formatting = val))
        {
            serializer.Serialize(writer, 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
}

And then apply it to a class (or property) like so:

[JsonConverter(typeof(NoFormattingConverter))]
public class NestedClass
{
    public string[] Values { get; set; }
}

public class TestClass
{
    public string AValue { get; set; }

    public NestedClass NestedClass { get; set; }

    public string ZValue { get; set; }

    public static void Test()
    {
        var test = new TestClass { AValue = "A Value", NestedClass = new NestedClass { Values = new[] { "one", "two", "three" } }, ZValue = "Z Value" };
        Debug.WriteLine(JsonConvert.SerializeObject(test, Formatting.Indented));
    }
}

The output of the Test() method above is:

{
  "AValue": "A Value",
  "NestedClass":{"Values":["one","two","three"]},
  "ZValue": "Z Value"
}
dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    It's exactly what I need. thank you very much! I tried to make something like that but I had exactly the problem you mentioned. – Demonia Jun 14 '15 at 20:43