Harsha W
Harsha W

Reputation: 3366

Validate response to filter array objects which are having no elements

How can I filter out the array objects which are having 0 elements from the ASP.NET Web API Model.

Ex: I'm am using the below method to filter null objects.

using Newtonsoft.Json;

public string FaxWork { get; set; }
[JsonProperty(PropertyName = "phoneWork", NullValueHandling = NullValueHandling.Ignore)]

How can I use something like above in order to filter out [] empty array objects?

Ex:

"postalAddress": [],
"electronicAddress": []

Upvotes: 0

Views: 570

Answers (1)

dbc
dbc

Reputation: 117086

You can accomplish this using the conditional property serialization functionality of Json.NET.

If you just want to ignore a single member when its array value is empty, add a ShouldSerialize{PropertyName}() method to your class that returns false when you don't want it serialized, e.g.:

public class RootObject
{
    public string[] PostalAddress { get; set; }

    public bool ShouldSerializePostalAddress() { return PostalAddress != null && PostalAddress.Length > 0; }
}

If you need to do this for many different collection-valued members of many different types, you can create a custom contract resolver that automatically generates a ShouldSerialize predicate for all of then:

public class SkipEmptyCollectionsContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization)
            .AddShouldSerializeEmptyCollections(this);
        return property;
    }
}

public static class JsonPropertyExtensions
{
    public static JsonProperty AddShouldSerializeEmptyCollections(this JsonProperty property, IContractResolver resolver)
    {
        if (property == null)
            throw new ArgumentNullException();
        if ((typeof(IEnumerable).IsAssignableFrom(property.PropertyType) || property.PropertyType.IsAssignableFrom(typeof(IEnumerable)))
            && property.PropertyType != typeof(string)
            && property.Readable)
        {
            Predicate<object> shouldSerialize = (parent) =>
            {
                var value = property.ValueProvider.GetValue(parent);
                if (value == null || value is string)
                    return true; // null properties are filtered by the NullValueHandling setting.
                var contract = resolver.ResolveContract(value.GetType());
                if (contract is JsonArrayContract)
                {
                    return (value as IEnumerable).Any();
                }
                return true;
            };
            var oldShouldSerialize = property.ShouldSerialize;
            if (oldShouldSerialize == null)
                property.ShouldSerialize = shouldSerialize;
            else
                property.ShouldSerialize = (o) => shouldSerialize(o) && oldShouldSerialize(o);
        }

        return property;
    }
}

public static class EnumerableExtensions
{
    public static bool Any(this IEnumerable enumerable)
    {
        if (enumerable == null)
            return false;
        if (enumerable is ICollection)
        {
            return ((ICollection)enumerable).Count > 0;
        }
        var enumerator = enumerable.GetEnumerator();
        using (enumerator as IDisposable)
        {
            return enumerator.MoveNext();
        }
    }
}

Then serialize using JsonSerializerSettings such as the following, which also enables camel casing of names:

var settings = new JsonSerializerSettings
{
    ContractResolver = new SkipEmptyCollectionsContractResolver { NamingStrategy = new CamelCaseNamingStrategy() },
    NullValueHandling = NullValueHandling.Ignore,
};

If you want to conditionally filter out empty collections using attributes, you could do so with the following contract resolver and attribute:

public enum EmptyArrayHandling
{
    Include = 0,
    Ignore = 1,
}

[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class JsonPropertyExtensionsAttribute : System.Attribute
{
    public EmptyArrayHandling EmptyArrayHandling { get; set; }
}

public class ConditionallySkipEmptyCollectionsContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        var attr = property.AttributeProvider.GetAttributes(typeof(JsonPropertyExtensionsAttribute), false).Cast<JsonPropertyExtensionsAttribute>().FirstOrDefault();
        if (attr != null && attr.EmptyArrayHandling == EmptyArrayHandling.Ignore)
            property = property.AddShouldSerializeEmptyCollections(this);
        return property;
    }
}

Then apply to your members as follows:

public class RootObject
{
    [JsonPropertyExtensions(EmptyArrayHandling = EmptyArrayHandling.Ignore)]
    public string[] PostalAddress { get; set; }
}

Note that if your "collection" is actually a complex LINQ query, the ShouldSerialize method will have to enumerate the first element of the query to see if it is empty, which may lead to poor performance because the query will get evaluated twice. To avoid this, you can evaluate the entire query as a list before serializing.

You may want to cache the contract resolver for best performance.

Upvotes: 2

Related Questions