Runtime: Equivalent of DefaultContractResolver in System.Text.Json

Created on 22 Oct 2019  ·  18Comments  ·  Source: dotnet/runtime

I used to DefaultContractResolver in Json.NET for ignoring empty collections and sometimes for changing json values. Currently I switched to System.Text.Json and haven't any idea about how to implement equivalent process in new JSON APIs. Also tried JsonConverter but it doesn't act as DefaultContractResolver.

_Ignoring empty collections_

```c#
public class IgnoreEmptyCollectionsContract : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);

    if (property.PropertyType.GetInterface(nameof(ICollection)) != null)
    {
        property.ShouldSerialize = o =>
        {
            var value = o?.GetType().GetProperty(property.PropertyName)?.GetValue(o) as ICollection;

            if (value == null)
            {
                return false;
            }

            return value.Count > 0;
        };
    }

    return property;
}

}

```

area-System.Text.Json

Most helpful comment

I was honestly very excited to see a standardized version of JSON serialization finally making its way into .NET. Wrote a bunch of code, started testing, and then I came across this issue.

I MINIMALLY need an EASY option for preventing serialization of properties with any of the following characteristics: null value, default value, empty string (zero length), and empty collection (zero count).

While more would be better, the above would cover the majority of code I've ever written. Without this minimal support, this package is simply unusable. I'm forced to return to using XML serialization or Newtonsoft's JSON.NET.

Why not support the "ShouldSerializeXXX" convention? It is supported by pretty much every other serializer out there.

Is its absence simply to differentiate this product? Or, are you concerned about performance issues?

If its the latter, perhaps provide a parameter/option to the serializer that selectively enables/disables "ShouldSerializeXXX" support? I suggest this anyway to lessen the burden on developers refactoring pre-existing code (to use this package).

I'm really bummed out now :( I was so happy to see this package. Now, I need to eliminate dependencies on this package and revert to a different solution.

All 18 comments

@xsoheilalizadeh if you find any workaround then please do share.
Thanks

@KamranShahid, I'm trying to ignore empty collection with Utf8JsonWriter but still have some problem with rewriting properties.
```c#
// { "numbers":[1, 2] }

var document = await JsonSerializer.DeserializeAsync(body);

var numbers = document.GetProperty("numbers"); // [1, 2]

if (numbers.GetArrayLength() == 0)
{
using var memoryStream = new MemoryStream();

var utf8Json = new Utf8JsonWriter(memoryStream);

utf8Json.WriteNull("orders");

// or

utf8Json.WriteNullValue();

// or ...

numbers.WriteTo(utf8Json);

}

```

@KamranShahid, I'm trying to ignore empty collection with Utf8JsonWriter but still have some problem with rewriting properties.

// { "numbers":[1, 2] }

var document = await JsonSerializer.DeserializeAsync<JsonElement>(body);

var numbers = document.GetProperty("numbers"); // [1, 2]

if (numbers.GetArrayLength() == 0)
{
    using var memoryStream = new MemoryStream();

    var utf8Json = new Utf8JsonWriter(memoryStream);

    utf8Json.WriteNull("orders");

    // or

    utf8Json.WriteNullValue();

    // or ...

    numbers.WriteTo(utf8Json);
}

For the time being what i have done is created another smaller class and assigned only required fields there. Let's see if there can be something added in .net core 3.1

/cc @steveharter

Currently there is no mechanism for a per-property callback to control (de)serialization based on custom logic. We are considering adding a more flexible model in 5.0 however.

If you just want to ignore null properties, you can use JsonSerializerOptions.IgnoreNullValues.

To ignore empty collections, or to add other logic, you would need to author a custom converter by authoring a class that derives from JsonConverter<T>. It is intended primarily for data types, not full\complex objects since it is low-level, so using it for a full object likely entails forwarding serialization of non-trivial types to built-in converters (obtaining by calling JsonSerializerOptions.GetConverter()) or by calling back into the serializer.

One the custom converter class is authored, it can be registered in the following ways:

  • Add [JsonConverter] attribute to a specific property.
  • Add [JsonConverter] attribute to a custom type.
  • Call JsonSerializerOptions.AddConverter().

From @KamranShahid in https://github.com/dotnet/corefx/issues/42043:

I am migrating my .net core 2.1 application to .net core 3.0 and getting rid of newtonsoft
I am using following code

public class PropertyRenameAndIgnoreSerializerContractResolver : DefaultContractResolver
    {
        private readonly Dictionary<Type, HashSet<string>> _ignores;

        public PropertyRenameAndIgnoreSerializerContractResolver()
        {
            _ignores = new Dictionary<Type, HashSet<string>>();
        }

        public void IgnoreProperty(Type type, params string[] jsonPropertyNames)
        {
            if (!_ignores.ContainsKey(type))
                _ignores[type] = new HashSet<string>();

            foreach (var prop in jsonPropertyNames)
                _ignores[type].Add(prop);
        }
    }

  then in calling program i have some thing like
  if(condition)
  {
              var jsonResolver = new PropertyRenameAndIgnoreSerializerContractResolver();
                jsonResolver.IgnoreProperty(typeof(ResponseJson), "Log_id");
                jsonResolver.IgnoreProperty(typeof(ResponseJson), "Log_status");
                var json = JsonConvert.SerializeObject(response, Formatting.None,
                    new JsonSerializerSettings { ContractResolver = jsonResolver });
...                   
                  }
                  else
                  {
  var json = JsonConvert.SerializeObject(response, Formatting.None);
...   
                  }

what should i do to achieve same without newtonsoft

@xsoheilalizadeh @layomia Any updates on this ticket or a solution to be able to replicate complex logic implemented in contract resolvers in System.Text.Json?

I'm not sure how this could be achieved in a generic way with converters since they're only called after the name of their property has been written. No-opping inside one will simply leave you with the invalid JSON propname: so custom behaviors for the serialization of a specific type are for lack of a better term a gigantic pain to implement.

From being unable to deserialize Nullable<T> without writing your own converter to a fairly basic feature like being able to ignore properties at runtime, there are many relatively basic usecases that this library still doesn't support.

Is there any chance that we'll see features like this made accessible in previews before November? I'd love to use these APIs in projects but keep running into roadblocks that look less like performance considerations and more like missing features.

I really need to equivalent of DefaultContractResolver in System.Text.Json.

I am getting stuck at multiple places due to this.

https://stackoverflow.com/questions/59792850/system-text-json-serialize-null-strings-into-empty-strings-globally

It's not possible to treat all null strings as empty in using converter.

Similarly any custom advance logic is not possible in converters.

Any new traction/workarounds on this issue ?

For 5.0 we are likely going to add a virtual property "HandleNullValue" (pending naming). Then you will be able to write a System.String converter, override that property and return true, and then implement Read\Write to treat nulls as empty strings.

So you're essentially saying stj will not be supporting a way to ignore a value at runtime?

e.g. a ShouldSerialize(T value) method available to override in converters that does the equivalent to annotating the property with [JsonIgnore].

An easy use-case to demonstrate this being necessary would be an Option<T?> implementation where you only want to serialize the inner value if the option has a value. Often there is a difference between an API explicitly returning null or omitting a field entirely, and this is something that can't currently be achieved with stj in its present form.

I was honestly very excited to see a standardized version of JSON serialization finally making its way into .NET. Wrote a bunch of code, started testing, and then I came across this issue.

I MINIMALLY need an EASY option for preventing serialization of properties with any of the following characteristics: null value, default value, empty string (zero length), and empty collection (zero count).

While more would be better, the above would cover the majority of code I've ever written. Without this minimal support, this package is simply unusable. I'm forced to return to using XML serialization or Newtonsoft's JSON.NET.

Why not support the "ShouldSerializeXXX" convention? It is supported by pretty much every other serializer out there.

Is its absence simply to differentiate this product? Or, are you concerned about performance issues?

If its the latter, perhaps provide a parameter/option to the serializer that selectively enables/disables "ShouldSerializeXXX" support? I suggest this anyway to lessen the burden on developers refactoring pre-existing code (to use this package).

I'm really bummed out now :( I was so happy to see this package. Now, I need to eliminate dependencies on this package and revert to a different solution.

I have different use case for ShouldSerialize (configured in CreateProperty). I use it to hide GDPR sensitive data depending on requestor permissions.

I again needed similar thing for converting one implementation as mentioned in my stackoverflow post
https://stackoverflow.com/questions/64729381/defaultcontractresolver-equivalent-in-system-text-json

Basically trying equivalent of

public class CustomDataContractResolver : DefaultContractResolver
    {
        public Dictionary<string, string> FieldNameChanges { get; set; }
        public List<FieldValueReplica> FieldValueReplica { get; set; }

        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var property = base.CreateProperty(member, memberSerialization);
            if (property.DeclaringType != typeof(logEvent)) return property;

            if (FieldNameChanges.Count > 0 && FieldNameChanges.TryGetValue(property.PropertyName, out var newValue))
                property.PropertyName = newValue;

            return property;
        }
   }

Any help in it?

@KamranShahid same is done here: https://github.com/RicoSuter/NJsonSchema/blob/master/src/NJsonSchema/Generation/SystemTextJsonUtilities.cs#L45

But not sure whether this is a “sustainable” solution.

@KamranShahid same is done here: https://github.com/RicoSuter/NJsonSchema/blob/master/src/NJsonSchema/Generation/SystemTextJsonUtilities.cs#L45

But not sure whether this is a “sustainable” solution.

I am looking for solution within System.Text.Json. Not wanted to have NewtonSoft dependency anymore

Please support a way to handle NHibernate Proxy objects serialization:

c# public class NHibernateContractResolver : CamelCasePropertyNamesContractResolver { protected override JsonContract CreateContract(Type objectType) { if (typeof(NHibernate.Proxy.INHibernateProxy).IsAssignableFrom(objectType)) { return base.CreateContract(objectType.BaseType); } else { return base.CreateContract(objectType); } } }

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sahithreddyk picture sahithreddyk  ·  3Comments

noahfalk picture noahfalk  ·  3Comments

GitAntoinee picture GitAntoinee  ·  3Comments

omajid picture omajid  ·  3Comments

matty-hall picture matty-hall  ·  3Comments