Using Newtonsoft we had a custom resolver for ignoring empty collections. Is there any equivalent configuration for the new system.text.json in .Net core 3.1
-
I don't believe so. `System.Text.Json` doesn't even support conditional serialization, which is what your contract resolver uses to ignore empty collections, as shown e.g. [here](https://stackoverflow.com/a/18486790). See [ShouldSerialize method is not triggered in .NET Core 3](https://stackoverflow.com/q/59507818). and [Equivalent of DefaultContractResolver in System.Text.Json #42001](https://github.com/dotnet/corefx/issues/42001#issuecomment-553156539). You would need to create a full `JsonConverter
` for each class that contains a collection. – dbc Jan 13 '20 at 16:44 -
2Frankly, System.Text.Json isn't ready for primetime, not supporting a vast number of common scenarios like this. The .NET Core team jumped the gun on releasing it as the default. Thankfully, you can still use JSON.NET, and I'd encourage you to keep doing so until System.Text.Json grows up and gets feature parity. See: https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#jsonnet-support – Chris Pratt Jan 13 '20 at 16:49
-
Newtonsoft is still preferred for many of us in 3.1 because the built-in JSON implementation is still quite lacking. – Jan 13 '20 at 16:56
-
1still not possible in .net 5.0 – rethabile Dec 22 '20 at 07:33
-
It won't be possible for a long time. Possibly ever. – speciesUnknown Feb 24 '22 at 15:46
3 Answers
TypeInfoResolver
added in .NET 7(accessible in older runtimes via the system.text.json nuget package, pre-release as of time of this answer) allows this.
Example:
using System.Collections;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
public class TestObject {
public List<int> Ints { get; } = new() {3, 4, 5};
public List<int> EmptyInts { get; } = new();
public List<int> NullInts { get; }
}
public static class Program {
public static void Main() {
var options = new JsonSerializerOptions {
TypeInfoResolver = new DefaultJsonTypeInfoResolver {
Modifiers = {DefaultValueModifier}
},
};
var obj = new TestObject();
var text = JsonSerializer.Serialize(obj, options);
Console.WriteLine(text);
}
private static void DefaultValueModifier(JsonTypeInfo type_info) {
foreach (var property in type_info.Properties) {
if (typeof(ICollection).IsAssignableFrom(property.PropertyType)) {
property.ShouldSerialize = (_, val) => val is ICollection collection && collection.Count > 0;
}
}
}
}
output:
{"Ints":[3,4,5]}

- 801
- 9
- 13
Conditionally ignore a property from .NET5's official How to migrate from Newtonsoft.Json to System.Text.Json from 2020 Dec 14 states:
System.Text.Json provides the following ways to ignore properties or fields while serializing:
- The [JsonIgnore] attribute (...).
- The IgnoreReadOnlyProperties global option (...).
- (...) JsonSerializerOptions.IgnoreReadOnlyFields global (...)
- The DefaultIgnoreCondition global option lets you ignore all value type properties that have default values, or ignore all reference type properties that have null values.
These options don't let you:
- Ignore selected properties based on arbitrary criteria evaluated at run time.
For that functionality, you can write a custom converter. Here's a sample POCO and a custom converter for it that illustrates this approach:
(An example of a custom converter for a type follows)
Please note that this converter needs to be a converter for the type that contains the property that is a collection, it's not a converter for the collection type (for that see my 2nd answer).

- 31,798
- 8
- 86
- 126
An attempt
I know that this is not what you're after but maybe someone could build on this or there is a very very slim chance that it may suit some scenarios.
What I managed to do
An object new A()
of
public class A
{
public List<int> NullList {get;set;}
public List<int> EmptyList {get;set;} = new List<int>();
};
becomes
{
"EmptyList": null
}
Documentation
How to write custom converters for JSON serialization (marshalling) in .NET from 2021 Feb 25 in Custom converter patterns says:
There are two patterns for creating a custom converter: the basic pattern and the factory pattern. The factory pattern is for converters that handle type Enum or open generics. The basic pattern is for non-generic and closed generic types. For example, converters for the following types require the factory pattern:
- Dictionary<TKey,TValue>
- Enum
- List
Some examples of types that can be handled by the basic pattern include:
- Dictionary<int, string>
- WeekdaysEnum
- List
- DateTime
- Int32
The basic pattern creates a class that can handle one type. The factory pattern creates a class that determines, at run time, which specific type is required and dynamically creates the appropriate converter.
What I did
Demo
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
#nullable disable
namespace Sandbox4
{
public class A
{
public List<int> NullList {get;set;}
public List<int> EmptyList {get;set;} = new List<int>();
};
public class Program
{
public static void Main()
{
A a = new ();
JsonSerializerOptions options = new ()
{
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault,
WriteIndented = true,
Converters =
{
new IEnumerableTConverter()
}
};
string aJson =
JsonSerializer.Serialize<A>(a, options);
Console.WriteLine(aJson);
}
}
}
Converter
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Sandbox4
{
// Modified DictionaryTEnumTValueConverter
// from https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0#custom-converter-patterns
public class IEnumerableTConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
if (!typeToConvert.IsGenericType)
{
return false;
}
var realType = typeToConvert.GetGenericTypeDefinition();
if (realType.IsAssignableTo(typeof(IEnumerable<>)))
{
return false;
}
return true;
}
public override JsonConverter CreateConverter(
Type type,
JsonSerializerOptions options)
{
Type generictype = type.GetGenericArguments()[0];
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
typeof(ICollectionTConverterInner<,>).MakeGenericType(
new Type[] { type, generictype }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { type, options },
culture: null);
return converter;
}
private class ICollectionTConverterInner<T,U> :
JsonConverter<T> where T: IEnumerable<U>
{
private readonly JsonConverter<T> _normalConverter;
public ICollectionTConverterInner(Type type,JsonSerializerOptions options)
{
// For performance, use the existing converter if available.
var existing = new JsonSerializerOptions().GetConverter(type);
if( existing == null ) throw new ApplicationException($"Standard converter for {type} not found.");
_normalConverter = (JsonConverter<T>) existing;
}
public override T Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
// Untested
return _normalConverter.Read(ref reader, typeToConvert, options);
}
public override void Write(
Utf8JsonWriter writer,
T collection,
JsonSerializerOptions options)
{
if(!collection.Any())
{
writer.WriteNullValue();
return;
}
_normalConverter.Write(writer, collection, options);
}
}
}
}

- 31,798
- 8
- 86
- 126
-
So you managed to output `null` for an empty list. Could this be combined with `[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`? – Kjara Oct 24 '22 at 08:23