Reputation: 185
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
Upvotes: 16
Views: 8433
Reputation: 891
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]}
Upvotes: 13
Reputation: 35017
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.
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
}
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.
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);
}
}
}
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);
}
}
}
}
Upvotes: 0
Reputation: 35017
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).
Upvotes: 2