Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Equivalent of DefaultContractResolver in System.Text.Json #31257

Closed
xsoheilalizadeh opened this issue Oct 22, 2019 · 21 comments
Closed

Equivalent of DefaultContractResolver in System.Text.Json #31257

xsoheilalizadeh opened this issue Oct 22, 2019 · 21 comments
Assignees
Milestone

Comments

@xsoheilalizadeh
Copy link

xsoheilalizadeh commented Oct 22, 2019

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

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;
    }
}
@KamranShahid
Copy link

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

@xsoheilalizadeh
Copy link
Author

xsoheilalizadeh commented Oct 24, 2019

@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);
}

@KamranShahid
Copy link

@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

@ericstj
Copy link
Member

ericstj commented Oct 28, 2019

/cc @steveharter

@steveharter
Copy link
Member

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().

@layomia
Copy link
Contributor

layomia commented Nov 23, 2019

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

@Arash-Sabet
Copy link

@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?

@trinitrotoluene
Copy link

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.

@KhalilMohammad
Copy link

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.

@msftgits msftgits transferred this issue from dotnet/corefx Feb 1, 2020
@msftgits msftgits added this to the 5.0 milestone Feb 1, 2020
@paymerich
Copy link

Any new traction/workarounds on this issue ?

@steveharter
Copy link
Member

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.

@trinitrotoluene
Copy link

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.

@techfan101
Copy link

techfan101 commented Feb 28, 2020

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.

@rosieks
Copy link

rosieks commented May 7, 2020

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

@KamranShahid
Copy link

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?

@RicoSuter
Copy link

@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
Copy link

@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

@gokhanabatay
Copy link

Please support a way to handle NHibernate Proxy objects serialization:

 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);
            }
        }
    }

@CuriouserThing
Copy link

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.

As is, this use-case can lead to very many instances of a [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] attribute when it's not feasible to set DefaultIgnoreCondition.

I appreciate that a ShouldSerialize API would unnecessarily duplicate the efforts of a full-fledged contract resolver (as @layomia notes in #36275). Though I hope ShouldSerialize is considered as a fallback.

@steveharter
Copy link
Member

Closing this; it is referenced by #36785. Note two features (serialization order and OnSerialize callbacks) were added that will be able to unblock some "DefaultContractResolver" scenarios.

For 6.0, there was prototyping that exposed the object and property metadata with callbacks that allowed editing such as adding or modifying properties. However, since the prototyping was done later in the release, it would have received little feedback and usage information so the effort was closed and focus was switched to implement property ordering + OnSerialize callbacks.

@RicoSuter
Copy link

RicoSuter commented Jul 19, 2021

@steveharter In order to implement correct schema definition generation, we probably need "prototyping" so that all metadata is available... do you have a link to the issue for this feature?

@ghost ghost locked as resolved and limited conversation to collaborators Aug 18, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests