Newtonsoft inline formatting for subelement while serializing
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"
}
Serializing List of Json Objects with Newtonsoft results in incorrect formatting with many \r\n
This is the problem:
string cfgStr = JsonConvert.SerializeObject(ListViewContents, Formatting.Indented);
jsonObject[JoystickName]["input_bindings"] = cfgStr;
You're setting the value of input_bindings
to "the result of serializing JSON" (i.e. a string). You're then serializing the whole object (with jsonObject.ToString()
), so that serializes that string, including escaping everything. You only want to serialize once.
I suspect you just want:
jsonObject[JoystickName]["input_bindings"] = JArray.FromObject(ListViewContents);
In other words, convert ListViewContents
into the LINQ to JSON model, but don't serialize it as a string yet.
How to apply indenting serialization only to some properties?
One possibility would be to write a custom Json converter for the specific types you need special handling and switch the formatting for them:
class Program
{
static void Main()
{
var root = new Root
{
Array = new[] { "element 1", "element 2", "element 3" },
Object = new Obj
{
Property1 = "value1",
Property2 = "value2",
},
};
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
};
settings.Converters.Add(new MyConverter());
string json = JsonConvert.SerializeObject(root, settings);
Console.WriteLine(json);
}
}
public class Root
{
public string[] Array { get; set; }
public Obj Object { get; set; }
}
public class Obj
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string[]) || objectType == typeof(Obj);
}
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)
{
writer.WriteRawValue(JsonConvert.SerializeObject(value, Formatting.None));
}
}
This will output:
{
"Array": ["element 1","element 2","element 3"],
"Object": {"Property1":"value1","Property2":"value2"}
}
Creating JSON without array indentation
You can get the indenting you want with Json.Net (a.k.a. Newtonsoft Json) if you subclass the JsonTextWriter
class and override the WriteIndent
method like this:
public class CustomJsonTextWriter : JsonTextWriter
{
public CustomJsonTextWriter(TextWriter writer) : base(writer)
{
}
protected override void WriteIndent()
{
if (WriteState != WriteState.Array)
base.WriteIndent();
else
WriteIndentSpace();
}
}
Then create a small helper method to make it easy to use the custom writer:
public static class JsonHelper
{
public static string SerializeWithCustomIndenting(object obj)
{
using (StringWriter sw = new StringWriter())
using (JsonWriter jw = new CustomJsonTextWriter(sw))
{
jw.Formatting = Formatting.Indented;
JsonSerializer ser = new JsonSerializer();
ser.Serialize(jw, obj);
return sw.ToString();
}
}
}
Here is a working demo: https://dotnetfiddle.net/RusBGI
In System.Text.Json is it possible to specify custom indentation rules?
This is not possible currently with System.Text.Json
(as of .NET 5). Let's consider the possibilities:
JsonSerializerOptions
has no method to control indentation other than the Boolean propertyWriteIndented
:Gets or sets a value that defines whether JSON should use pretty printing.
Utf8JsonWriter
has no method to modify or control indentation, asOptions
is a get-onlystruct
-valued property.In .Net Core 3.1, if I create a custom
JsonConverter<T>
for yourTEXTURE_BOUNDS
andSCREEN_BOUNDS
lists and attempt setoptions.WriteIndented = false;
during serialization, a System.InvalidOperationException: Serializer options cannot be changed once serialization or deserialization has occurred exception will be thrown.Specifically, if I create the following converter:
class CollectionFormattingConverter<TCollection, TItem> : JsonConverter<TCollection> where TCollection : class, ICollection<TItem>, new()
{
public override TCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> JsonSerializer.Deserialize<CollectionSurrogate<TCollection, TItem>>(ref reader, options)?.BaseCollection;
public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
{
var old = options.WriteIndented;
try
{
options.WriteIndented = false;
JsonSerializer.Serialize(writer, new CollectionSurrogate<TCollection, TItem>(value), options);
}
finally
{
options.WriteIndented = old;
}
}
}
public class CollectionSurrogate<TCollection, TItem> : ICollection<TItem> where TCollection : ICollection<TItem>, new()
{
public TCollection BaseCollection { get; }
public CollectionSurrogate() { this.BaseCollection = new TCollection(); }
public CollectionSurrogate(TCollection baseCollection) { this.BaseCollection = baseCollection ?? throw new ArgumentNullException(); }
public void Add(TItem item) => BaseCollection.Add(item);
public void Clear() => BaseCollection.Clear();
public bool Contains(TItem item) => BaseCollection.Contains(item);
public void CopyTo(TItem[] array, int arrayIndex) => BaseCollection.CopyTo(array, arrayIndex);
public int Count => BaseCollection.Count;
public bool IsReadOnly => BaseCollection.IsReadOnly;
public bool Remove(TItem item) => BaseCollection.Remove(item);
public IEnumerator<TItem> GetEnumerator() => BaseCollection.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => ((IEnumerable)BaseCollection).GetEnumerator();
}And the following data model:
public partial class Root
{
[JsonPropertyName("TILESET")]
public string Tileset { get; set; }
[JsonPropertyName("TILES")]
public Tiles Tiles { get; set; }
}
public partial class Tiles
{
[JsonPropertyName("TILE_1")]
public Tile1 Tile1 { get; set; }
}
public partial class Tile1
{
[JsonPropertyName("NAME")]
public string Name { get; set; }
[JsonPropertyName("TEXTURE_BOUNDS")]
[JsonConverter(typeof(CollectionFormattingConverter<List<long>, long>))]
public List<long> TextureBounds { get; set; }
[JsonPropertyName("SCREEN_BOUNDS")]
[JsonConverter(typeof(CollectionFormattingConverter<List<long>, long>))]
public List<long> ScreenBounds { get; set; }
}Then serializing
Root
throws the following exception:Failed with unhandled exception:
System.InvalidOperationException: Serializer options cannot be changed once serialization or deserialization has occurred.
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerOptionsImmutable()
at System.Text.Json.JsonSerializerOptions.set_WriteIndented(Boolean value)
at CollectionFormattingConverter`2.Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
at System.Text.Json.JsonPropertyInfoNotNullable`4.OnWrite(WriteStackFrame& current, Utf8JsonWriter writer)
at System.Text.Json.JsonPropertyInfo.Write(WriteStack& state, Utf8JsonWriter writer)
at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.JsonSerializer.WriteCore(Utf8JsonWriter writer, Object value, Type type, JsonSerializerOptions options)
at System.Text.Json.JsonSerializer.WriteCore(PooledByteBufferWriter output, Object value, Type type, JsonSerializerOptions options)
at System.Text.Json.JsonSerializer.WriteCoreString(Object value, Type type, JsonSerializerOptions options)
at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)Demo fiddle #1 here.
In .Net Core 3.1, if I create a custom
JsonConverter<T>
that creates a pre-formattedJsonDocument
and then writes that out, the document will be reformatted as it is written.I.e. if I create the following converter:
class CollectionFormattingConverter<TCollection, TItem> : JsonConverter<TCollection> where TCollection : class, ICollection<TItem>, new()
{
public override TCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> JsonSerializer.Deserialize<CollectionSurrogate<TCollection, TItem>>(ref reader, options)?.BaseCollection;
public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
{
var copy = options.Clone();
copy.WriteIndented = false;
using var doc = JsonExtensions.JsonDocumentFromObject(new CollectionSurrogate<TCollection, TItem>(value), copy);
Debug.WriteLine("Preformatted JsonDocument: {0}", doc.RootElement);
doc.WriteTo(writer);
}
}
public static partial class JsonExtensions
{
public static JsonSerializerOptions Clone(this JsonSerializerOptions options)
{
if (options == null)
return new JsonSerializerOptions();
//In .Net 5 a copy constructor will be introduced for JsonSerializerOptions. Use the following in that version.
//return new JsonSerializerOptions(options);
//In the meantime copy manually.
var clone = new JsonSerializerOptions
{
AllowTrailingCommas = options.AllowTrailingCommas,
DefaultBufferSize = options.DefaultBufferSize,
DictionaryKeyPolicy = options.DictionaryKeyPolicy,
Encoder = options.Encoder,
IgnoreNullValues = options.IgnoreNullValues,
IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties,
MaxDepth = options.MaxDepth,
PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive,
PropertyNamingPolicy = options.PropertyNamingPolicy,
ReadCommentHandling= options.ReadCommentHandling,
WriteIndented = options.WriteIndented,
};
foreach (var converter in options.Converters)
clone.Converters.Add(converter);
return clone;
}
// Copied from this answer https://stackoverflow.com/a/62998253/3744182
// To https://stackoverflow.com/questions/62996999/convert-object-to-system-text-json-jsonelement
// By https://stackoverflow.com/users/3744182/dbc
public static JsonDocument JsonDocumentFromObject<TValue>(TValue value, JsonSerializerOptions options = default)
=> JsonDocumentFromObject(value, typeof(TValue), options);
public static JsonDocument JsonDocumentFromObject(object value, Type type, JsonSerializerOptions options = default)
{
var bytes = JsonSerializer.SerializeToUtf8Bytes(value, options);
return JsonDocument.Parse(bytes);
}
}Fully indented JSON is generated despite the fact that the intermediate
JsonDocument doc
was serialized without indentation:{
"TILESET": "tilesets/HOW_TO_GET_TILESET_NAME_?",
"TILES": {
"TILE_1": {
"NAME": "auto_tile_18",
"TEXTURE_BOUNDS": [
304,
16,
16,
16
],
"SCREEN_BOUNDS": [
485,
159,
64,
64
]
}
}
}Demo fiddle #2 here.
And finally, in .Net Core 3.1, if I create a custom
JsonConverter<T>
that clones the incomingJsonSerializerOptions
, modifiesWriteIndented
on the copy, then recursively serializes using the copied settings -- the modified value forWriteIndented
is ignored.Demo fiddle #3 here.
Apparently the
JsonConverter
architecture is going to be extensively enhanced in .Net 5 so you might re-test this option when it is released.
You might want to open an issue requesting this functionality, as there are multiple popular questions about how to do this with Json.NET (where it can be done with a converter):
- How to apply indenting serialization only to some properties?
- Newtonsoft inline formatting for subelement while serializing
- Creating JSON without array indentation
Related Topics
How to Count the Number of Elements That Match a Condition with Linq
Proper Datagrid Search from Textbox in Wpf Using Mvvm
What Does Linq Return When the Results Are Empty
Total Number of Items Defined in an Enum
How to Maximize the Browser Window in Selenium Webdriver (Selenium 2) Using C#
Using Ssl and Sslstream for Peer to Peer Authentication
Linq Syntax - Selecting Multiple Columns
How Is Gethashcode() Implemented for Int32
How to Know the Selected Checkboxes from Within the Httppost Create Action Method
String.Format - How It Works and How to Implement Custom Formatstrings
How to Use C# 6 with Web Site Project Type
Project a Can't Reference a Class in Project B
Best Practices: Throwing Exceptions from Properties
Ienumerable VS Ireadonlycollection VS Readonlycollection for Exposing a List Member