Dynamically Cross-Join Multiple Different-Size Collections Together in Linq (C#)

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"}
}

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:

  1. JsonSerializerOptions has no method to control indentation other than the Boolean property WriteIndented:

    Gets or sets a value that defines whether JSON should use pretty printing.

  2. Utf8JsonWriter has no method to modify or control indentation, as Options is a get-only struct-valued property.

  3. In .Net Core 3.1, if I create a custom JsonConverter<T> for your TEXTURE_BOUNDS and SCREEN_BOUNDS lists and attempt set options.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.

  4. In .Net Core 3.1, if I create a custom JsonConverter<T> that creates a pre-formatted JsonDocument 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.

  5. And finally, in .Net Core 3.1, if I create a custom JsonConverter<T> that clones the incoming JsonSerializerOptions, modifies WriteIndented on the copy, then recursively serializes using the copied settings -- the modified value for WriteIndented 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

Partial serialization of model based on property attribute

You can solve this in two parts:

  • Create a custom JsonConverter that can accept a list of names of properties to serialize.
  • Create a custom ContractResolver that looks for properties that have at least one [SerializeOnly] attribute applied, and apply the custom converter to those properties, passing the list of child property names gathered from the applied attributes.

Here is what the resolver might look like:

class CustomResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
foreach (JsonProperty prop in props)
{
if (!prop.PropertyType.IsPrimitive && prop.PropertyType != typeof(string))
{
PropertyInfo pi = type.GetProperty(prop.UnderlyingName);
if (pi != null && pi.CanRead)
{
var childPropertiesToSerialize = pi.GetCustomAttributes<SerializeOnly>()
.Select(att => att.PropertyName);
if (childPropertiesToSerialize.Any())
{
prop.Converter = new CustomConverter(childPropertiesToSerialize);
}
}
}
}
return props;
}
}

And here is the converter:

class CustomConverter : JsonConverter
{
private HashSet<string> propertiesToSerialize;

public CustomConverter(IEnumerable<string> propertiesToSerialize)
{
this.propertiesToSerialize = new HashSet<string>(propertiesToSerialize);
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (PropertyInfo prop in value.GetType().GetProperties())
{
if (prop.CanRead && propertiesToSerialize.Contains(prop.Name))
{
writer.WritePropertyName(prop.Name);
serializer.Serialize(writer, prop.GetValue(value));
}
}
writer.WriteEndObject();
}

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 CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}

Demo:

class Program
{
static void Main(string[] args)
{
var test = new MyTestObject();
var settings = new JsonSerializerSettings();
settings.ContractResolver = new CustomResolver();
settings.Formatting = Formatting.Indented;
var json = JsonConvert.SerializeObject(test, settings);
Console.WriteLine(json);
}

class MyTestObject
{
[SerializeOnly("TestValue1")]
[SerializeOnly("TestValue3")]
public ComplexTestObject Property1 { get; set; }

[SerializeOnly("TestValue2")]
public ComplexTestObject Property2 { get; set; }

public MyTestObject()
{
Property1 = new ComplexTestObject();
Property2 = new ComplexTestObject();
}
}

class ComplexTestObject
{
public string TestValue1 { get; set; }
public string TestValue2 { get; set; }
public string TestValue3 { get; set; }
public ComplexTestObject()
{
TestValue1 = "value1";
TestValue2 = "value2";
TestValue3 = "value3";
}
}
}

Output:

{
"Property1": {
"TestValue1": "value1",
"TestValue3": "value3"
},
"Property2": {
"TestValue2": "value2"
}
}

Fiddle: https://dotnetfiddle.net/Fj7QcW

how to serialize a property property only not the whole property using xml attributes?

If you are looking just for the ClassId to be serialized with the Student object it makes more sense to just have a ClassId property rather than the entire Class object.

public class GenerateXml
{
public static void Create()
{
Class c = new Class();
c.Teacher = new Teacher() {Name = "Mr. Henry"};
var s = new Student() { Age = 14, Name = "Suzy", Teacher = c.Teacher };
c.Students.Add(s);
s = new Student() {Age = 13, Name = "Adam", Teacher = c.Teacher};
c.Students.Add(s);

var serializer = new XmlSerializer(c.GetType());
XmlTextWriter writer = new XmlTextWriter("class.xml", Encoding.ASCII);
writer.Formatting = Formatting.Indented;
writer.Indentation = 4;
serializer.Serialize(writer, c);
}
}

[Serializable]
public class Class
{
public Class()
{
}

[XmlAttribute]
public string ClassId { get; set; }

[XmlElement]
public Teacher Teacher { get; set; }

[XmlArray("Students")]
[XmlArrayItem("Student", Type = typeof(Student))]
public List<Student> Students { get; } = new List<Student>();
}

[Serializable]
public class Student
{
public Student()
{

}

[XmlElement]
public Teacher Teacher { get; set; }

[XmlAttribute]
public string ClassId { get; set; }

[XmlAttribute]
public string Name { get; set; } = "New Student";

[XmlAttribute]
public int Age { get; set; } = 10;

}

[Serializable]
public class Teacher
{
public Teacher()
{

}

[XmlAttribute]
public string Name { get; set; } = "New Teacher";

[XmlAttribute]
public int Age { get; set; } = 30;

}

How do I get formatted and indented JSON in .NET using C#?

Set the JSON writer Formatting property to Formatting.Indented:

jsonWriter.Formatting = Formatting.Indented;

The JsonConvert.Serialize* methods also have overloads that take a Formatting enum (thanks John Flatness).

Documentation: Serialize an Object



Related Topics



Leave a reply



Submit