Search code examples
c#jsonodataasp.net-web-api

Prevent null values from being emitted in a WebAPI OData v4 service


I'm running an WebAPI OData v4 service and I don't want null values to be emitted. For example, when I request a User object, the following is returned:

"value": [
{
  "FirstName": "John",
  "LastName": "Smith",
  "EmailAddress": "[email protected]",
  "PasswordHash": null,
  "PhoneNumber": "11234567890",
  "Website": null,
  "Id": "dac9706a-8497-404c-bb5a-ca1024cf213b"
}

Since the PasswordHash and Website fields are null, I'd like them to not be included in the output. I've tried the following lines in the application's Register block, but none of them work:

config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
config.Formatters.JsonFormatter.SerializerSettings.DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate;
config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;

Below is the full contents of the Register method. I'm using the Newtonsoft Json package to handle the JSON serialization.

public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Routes.MapHttpRoute(
            name: "UploadComplete",
            routeTemplate: "UploadFiles/Complete",
            defaults: new { controller = "UploadFiles", action = "Complete" }
        );

        var builder = new ODataConventionModelBuilder();

        builder.EntitySet<Address>("Addresses");
        builder.EntitySet<Assignment>("Assignments");
        builder.EntitySet<Course>("Courses");
        builder.EntitySet<File>("Files");
        builder.EntitySet<UploadFile>("UploadFiles");
        builder.EntitySet<Organization>("Organizations");
        builder.EntitySet<Submission>("Submissions");
        builder.EntitySet<Term>("Terms");
        builder.EntitySet<User>("Users");

        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: builder.GetEdmModel());

        // Neither of these lines work.
        config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
        config.Formatters.JsonFormatter.SerializerSettings.DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate;
        config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
    }

Solution

  • Thanks Brian Rogers for pointing me in the right direction, I ended up overriding ODataEntityTypeSerializer like so:

    public override ODataEntry CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
    {
        ODataEntry entry = base.CreateEntry(selectExpandNode, entityInstanceContext);
    
        // Remove any properties which are null.
        List<ODataProperty> properties = new List<ODataProperty>();
        foreach (ODataProperty property in entry.Properties)
        {
            if (property.Value != null)
            {
                properties.Add(property);
            }
        }
    
        entry.Properties = properties;
        return entry;
    }
    

    And then I used code from RaghuRam-Nadiminti's answer to implement a CustomSerializerProvider class which I could then use in Register.