1

I have the Json shown below.

This is actually a far more complex object in reality, but this extract demonstrates my question.

I am looking at shrinking the size of the Json response being generated. This is currently being generated using the standard JsonResult in MVC,

Is there a way of getting JSonResult to not stream properties that have a value of 0? If that is possible, it would shrink my json response a lot! This in turn would make parsing faster.

 {
    "firstValue": 0.2000,
    "secondValue": 30.80,
    "thirdValue": 0.0,
    "fourthValue": 30.80,
    "fifthValue": 0.0
}

So I would only actually end up passing back the response below to the caller:

 {
    "firstValue": 0.2000,
    "secondValue": 30.80,
    "fourthValue": 30.80,
}

I have seen answers pointing me to using App_Start in my web api but I am using Kestrel which doesnt have an app start - this is being hosted by Service Fabric

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    return new[]
    {
        new ServiceInstanceListener(
            serviceContext =>
                new KestrelCommunicationListener(
                    serviceContext,
                    (url, listener) =>
                    {
                        ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");

                        return new WebHostBuilder()
                            .UseKestrel(options => { options.Listen(IPAddress.Any, 8081); })
                            .ConfigureServices(
                                services => services
                                    .AddSingleton(serviceContext)
                                    .AddSingleton(new ConfigSettings(serviceContext))
                                    .AddSingleton(new HttpClient())
                                    .AddSingleton(new FabricClient()))
                            .UseContentRoot(Directory.GetCurrentDirectory())
                            .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                            .UseStartup<Startup>()
                            .UseSerilog(_log, true)
                            .UseUrls(url)
                            .Build();
                    }))
    };
}
Paul
  • 2,773
  • 7
  • 41
  • 96
  • Seems like you should be able to do this when querying the data out before returning it with a `.Where(x => x.Value != 0)` – GregH Nov 19 '18 at 19:12
  • I dont want to do it that way because this is an object with loads of properties and that would be a huge where clause! – Paul Nov 19 '18 at 19:18
  • I see. In that case you may find newtonsoft's conditional serialization interesting: https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm although I believe you'll still have to define which properties you want to conditionally exclude one way or the other. I'll be interested to see if someone has a more graceful way of achieving this. Hope this helps – GregH Nov 19 '18 at 19:23
  • 1
    Which version of [tag:asp.net-mvc] are you using? For JSON serialization, earlier versions use `JavaScriptSerializer` as noted [here](https://stackoverflow.com/q/14591750) but ASP.Net Core uses Json.NET as noted [here](https://stackoverflow.com/q/29841503). The answer will differ depending on the serializer. – dbc Nov 19 '18 at 19:40
  • Im using ASPNet core - I am running this from inside a service fabric host which is running a Web API - I have the package Microsoft.AspNetCore.Mvc in the service – Paul Nov 19 '18 at 20:17
  • Pretty sure you can tell it to skip null and default values, not an answer, just an FYI. – Trey Nov 19 '18 at 20:21
  • You can get the required behavior by combining [JsonSerializerSettings and Asp.Net Core](https://stackoverflow.com/q/35772387), which shows how to configure `JsonSerializerSettings` in asp.net-core, with [Refactor of ShouldSerialize () in class… can I use IContractResolver?](https://stackoverflow.com/a/16330150/3744182) as well as [this anwer](https://stackoverflow.com/a/10512844/3744182) to another question which show how to skip default values during serialization by setting `settings.DefaultValueHandling = DefaultValueHandling.Ignore`. – dbc Nov 19 '18 at 20:53

2 Answers2

1

That's super easy. Just specify a value for DefaultValueHandling with the value Ignore.

As the description in that link says:

Ignore members where the member value is the same as the member's default value when serializing objects so that it is not written to JSON. This option will ignore all default values (e.g. null for objects and nullable types; 0 for integers, decimals and floating point numbers; and false for booleans). The default value ignored can be changed by placing the DefaultValueAttribute on the property.

Kit
  • 20,354
  • 4
  • 60
  • 103
  • Please see the startup code now added to my question, how do I integrate it in there? There is no register method – Paul Nov 20 '18 at 00:15
  • I managed to get somewhere to run this code, I know it is being hit because I also set the JSON to be pretty printed. I have put DefaultValue(0) above my properties and they are still getting output? – Paul Nov 20 '18 at 01:17
0

As Kit suggested, you could use the DefaultValueHandling behavior.

Besides, you can always custom your own ContractResolver to solve such questions. Here's a version of using custom ContractResolver to ignore default value:

public class IgnoreZeroContractResolver : DefaultContractResolver
{
    public IgnoreZeroContractResolver( ){ }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        property.ShouldSerialize = instance => {
            var shouldSerialize = true;  // indicate should we serialize this property

            var type = instance.GetType();
            var pi = type.GetProperty(property.PropertyName);  
            var pv = pi.GetValue(instance);
            var pvType = pv.GetType();

            // if current value equals the default values , ignore this property 
            if (pv.GetType().IsValueType){
                var defaultValue = Activator.CreateInstance(pvType);  
                if (pv.Equals(defaultValue)) { shouldSerialize = false; } 
            }
            return shouldSerialize;
        };

        return property;
    }

}

And now you can set your resover as the ContractResolver:

public void ConfigureServices(IServiceCollection services)
{

    // ...
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
        .AddJsonOptions(o =>{
           o.SerializerSettings.ContractResolver =new IgnoreZeroContractResolver();
        });

    // ...

}

Test Case :

var x = new {
    FirstValue =0.2000,
    SecondValue= 30.80,
    ThirdValue= 0.0,
    FourthValue= 30.80,
    FifthValue= 0.0,        // double 0
    SixValue= 0             // int 0
};
return new JsonResult(x);

and the response will be :

{"FirstValue":0.2,"SecondValue":30.8,"FourthValue":30.8}
itminus
  • 23,772
  • 2
  • 53
  • 88