I have net6.0 project with minimal api and I would like to use NetwtonsoftJson instead of built in System.Text.Json library for serialization and deserialization.
At the moment I have this configurations for JsonOptions and that works as expected
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
});
If I try to change to something equivalent that uses Newtonsoft.Json.JsonSerializerSettings like below I am not getting same behavior. Instead it looks like it uses default System.Text.Json configuration.
builder.Services.Configure<JsonSerializerSettings>(options =>
{
options.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.Converters.Add(
new StringEnumConverter
{
NamingStrategy = new Newtonsoft.Json.Serialization.CamelCaseNamingStrategy()
});
});
In net5.0 I know I could use this
services.AddControllers().AddNewtonsoftJson((options) => //options); // OR
services.AddMvc().AddNewtonsoftJson((options) => //options);
However, if I use it like above in my net6.0 project then I am not using anymore MinimalApi ?
From my understanding Minimal APIs rely on some conventions regarding type binding. From what I can see they search for method with next signature - ValueTask<TModel?> BindAsync(HttpContext context, ParameterInfo parameter) on the type otherwise will try to use httpContext.Request.ReadFromJsonAsync which internally uses System.Text.Json and that can't be changed, so services.Add...().AddNewtonsoftJson((options) => //options); approach will not work.
To use Newtonsoft.Json you can try next (other than directly handling request via app.MapPost("/pst", (HttpContext c) => c.Request...)):
If you have control over all your classes which needs to be deserialized using it you can inherit them all from some generic base class which will have the method with needed signature (also you can use interface with implemented static method):
public class BaseModel<TModel>
{
public static async ValueTask<TModel?> BindAsync(HttpContext context, ParameterInfo parameter)
{
if (!context.Request.HasJsonContentType())
{
throw new BadHttpRequestException(
"Request content type was not a recognized JSON content type.",
StatusCodes.Status415UnsupportedMediaType);
}
using var sr = new StreamReader(context.Request.Body);
var str = await sr.ReadToEndAsync();
return JsonConvert.DeserializeObject<TModel>(str);
}
}
And usage:
class PostParams : BaseModel<PostParams>
{
[JsonProperty("prop")]
public int MyProperty { get; set; }
}
// accepts json body {"prop": 2}
app.MapPost("/pst", (PostParams po) => po.MyProperty);
Note that BaseModel<TModel> implemenation in this example is quite naive and possibly can be improved (check out HttpRequestJsonExtensions.ReadFromJsonAsync at least).
If you don't have control over the models or don't want to inherit them from some base you can look into creating wrappers:
public class Wrapper<TModel>
{
public Wrapper(TModel? value)
{
Value = value;
}
public TModel? Value { get; }
public static async ValueTask<Wrapper<TModel>?> BindAsync(HttpContext context, ParameterInfo parameter)
{
if (!context.Request.HasJsonContentType())
{
throw new BadHttpRequestException(
"Request content type was not a recognized JSON content type.",
StatusCodes.Status415UnsupportedMediaType);
}
using var sr = new StreamReader(context.Request.Body);
var str = await sr.ReadToEndAsync();
return new Wrapper<TModel>(JsonConvert.DeserializeObject<TModel>(str));
}
}
And usage changes to:
class PostParams
{
[JsonProperty("prop")]
public int MyProperty { get; set; }
}
// accepts json body {"prop": 2}
app.MapPost("/pst", (Wrapper<PostParams> po) => po.Value.MyProperty);
Some extra useful links:
MVC model binders - by David Fowler. Though I was not able to make it work for services.AddControllers().AddNewtonsoftJson((options) => //options);
ParameterBinder - similar approach by Damian Edwards
This question already has answers here:
How to support a nested open complex type in the OData C# driver?
(2 answers)
Closed 2 years ago.
*Edit: turns out this has something to do with being on an OData controller. Adding OData configuration below, from startup.cs.
Disclaimer: I don't have time tonight to make a true minimal reproducible version of my code. I've just pulled the OData settings out of my larger project. Sorry! I'll try to circle back on Monday and create a true minimal reproducible version.
I'm trying to return a JSON in this basic format:
{ "Name": "hello", Props: { "a":"1", "b":"2" }}
Props is a Dictionary, and if I move this code to a "normal" controller, it works as expected. But on the OData controller, it's serializing the Dictionary as an array of objects, like this:
[ { "Key": "a", "Value": "1"}, ...]
I think the OData configuration below is all I'm doing, and I don't believe I've tried to override any JSON serialization settings.
Simple version of controller code:
public class SuperObject{
public SuperObject(string name){
Name = name;
Props = new Dictionary<string, string>();
}
public string Name {get;set;}
public Dictionary<string, string> Props {get;set;}
}
public ActionResult SimpleGet(int key)
{
var x = new SuperObject("hello");
x.Props["a"] = "1";
x.Props["b"] = "2";
return Ok(x);
}
Startup.cs related to OData configuration:
app.UseMvc(routeBuilder => {
routeBuilder.Select().Filter().OrderBy().MaxTop(100).Count();
var builder = new ODataConventionModelBuilder();
builder.EntitySet<RecordFile>("RecordFile");
var coll = builder.EntityType<RecordFile>().Collection;
fn = coll.Function("SimpleGet").Returns<SuperObject>();
fn.Parameter<int>("key");
var model = builder.GetEdmModel();
routeBuilder.MapODataServiceRoute("ODataRoute", "odata", model);
}
Try to use the JsonConvert.Serialize() method and log the serialized result before you send it.
I tried to replicate your issue in a console app, but I didn't get any unexpected results like you did. This means that you are probally doing something wrong elsewhere. Think about other mistakes that maybe give you an unexpected output like: If your application is .NET Core 3.0 or later => Did you tell your application to use Newtonsoft with the .AddNewtonsoftJson() extension method in Startup.cs
static void Main(string[] args)
{
var obj = new SuperObject("appel");
obj.Props["a"] = "5";
obj.Props["b"] = "10";
var jsonText = JsonConvert.SerializeObject(obj);
Console.WriteLine(jsonText);
}
public class SuperObject
{
public SuperObject(string name)
{
Name = name;
Props = new Dictionary<string, string>();
}
public string Name { get; set; }
public Dictionary<string, string> Props { get; set; }
}
Output: (as expected)
// {"Name":"appel","Props":{"a":"5","b":"10"}}
I'm migrating from Newtonsoft.Json to System.Text.Json in my .NET Core 3.0 application.
How do I get have the same behaviour with System.Text.Json as I have in my .NET Core 2.2 app with Newtonsoft.Json configured with DefaultValueHandling = DefaultValueHandling.Ignore? You can find the DefaultValueHandling option described here.
I don't think you can do this, at least not in an easy way. You can easily ignore null values using IgnoreVullValues property of JsonSerializerOptions class but this will only ignore null values and still serialize integers, booleans etc. with default values.
You can find more about JsonSerializerOptions class here
Please try this library I wrote as an extension to System.Text.Json to offer missing features: https://github.com/dahomey-technologies/Dahomey.Json.
You will find support for ignoring default values:
using Dahomey.Json;
using Dahomey.Json.Attributes;
using System.Text.Json;
public class ObjectWithDefaultValue
{
[JsonIgnoreIfDefault]
[DefaultValue(12)]
public int Id { get; set; }
[JsonIgnoreIfDefault]
[DefaultValue("foo")]
public string FirstName { get; set; }
[JsonIgnoreIfDefault]
[DefaultValue("foo")]
public string LastName { get; set; }
[JsonIgnoreIfDefault]
[DefaultValue(12)]
public int Age { get; set; }
}
Setup json extensions by calling on JsonSerializerOptions the extension method SetupExtensions defined in the namespace Dahomey.Json:
Then serialize your class with the regular Sytem.Text.Json API:
public void Test()
{
JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();
ObjectWithDefaultValue obj = new ObjectWithDefaultValue
{
Id = 13,
FirstName = "foo",
LastName = "bar",
Age = 12
};
string json = JsonSerializer.Serialize(obj, options); // {"Id":13,"LastName":"bar"}
}
I think this can be help
services.AddMvc().AddJsonOptions(options => options.JsonSerializerOptions.IgnoreNullValues = true);
There is a proposed api-suggestion #42635 for ignoring default values that has been added to .NET Core 5.0 release due out in November 2020. Newtonsoft currently is the only supported solution worth using for this particular issue.
Example code
JToken json = JObject.Parse(
" {\"Url\": \"www.fakeUrl.com\",\"CallId\": 12}");
var poco = mapper.Map<CallData>(json);
Console.WriteLine(json);
Console.WriteLine(poco.Url + " " + poco.CallId);
Simple Model
public class CallData
{
public int CallId { get; set; }
public string Url { get; set; }
}
Output
{ "Url": "www.fakeUrl.com", "CallId": 12 }
www.fakeUrl.com 0
I'm just curious to why Automapper isn't mapping the integer in this JSON object? I know there are alternatives out such as a custom extension for this but I'm wondering why AutoMapper can't do this simple map?
Automapper V7.0.1
I resolved the issue by adding a custom mapping. I still believe this to be an issue with the underlining libraries and will investigate further as this simple primitive mapping shouldn't need extensions.
Mapper
CreateMap<dynamic ,CallData>().ConvertUsing((jo) =>
{
var callData = new CallData();
JsonSerializer serializer = new JsonSerializer();
if(jo != null)
serializer.Populate((JsonReader) jo.CreateReader(), callData);
return callData;
});
Usage
var response =_mapper.Map<dynamic, CallData>(_callData);
I am using Json.NET to serialize a class to JSON.
I have the class like this:
class Test1
{
[JsonProperty("id")]
public string ID { get; set; }
[JsonProperty("label")]
public string Label { get; set; }
[JsonProperty("url")]
public string URL { get; set; }
[JsonProperty("item")]
public List<Test2> Test2List { get; set; }
}
I want to add a JsonIgnore() attribute to Test2List property only when Test2List is null. If it is not null then I want to include it in my json.
An alternate solution using the JsonProperty attribute:
[JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
// or
[JsonProperty("property_name", NullValueHandling=NullValueHandling.Ignore)]
// or for all properties in a class
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
As seen in this online doc.
As per James Newton King: If you create the serializer yourself rather than using JavaScriptConvert there is a NullValueHandling property which you can set to ignore.
Here's a sample:
JsonSerializer _jsonWriter = new JsonSerializer {
NullValueHandling = NullValueHandling.Ignore
};
Alternatively, as suggested by #amit
JsonConvert.SerializeObject(myObject,
Newtonsoft.Json.Formatting.None,
new JsonSerializerSettings {
NullValueHandling = NullValueHandling.Ignore
});
JSON.NET also respects the EmitDefaultValue property on DataMemberAttribute, in case you don't want to add Newtonsoft-specific attributes to your model:
[DataMember(Name="property_name", EmitDefaultValue=false)]
You can write: [JsonProperty("property_name",DefaultValueHandling = DefaultValueHandling.Ignore)]
It also takes care of not serializing properties with default values (not only null). It can be useful for enums for example.
You can do this to ignore all nulls in an object you're serializing, and any null properties won't then appear in the JSON
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.NullValueHandling = NullValueHandling.Ignore;
var myJson = JsonConvert.SerializeObject(myObject, settings);
In my case, using .NET 6 this was the solution:
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
More info here.
As can be seen in this link on their site (http://james.newtonking.com/archive/2009/10/23/efficient-json-with-json-net-reducing-serialized-json-size.aspx) I support using [Default()] to specify default values
Taken from the link
public class Invoice
{
public string Company { get; set; }
public decimal Amount { get; set; }
// false is default value of bool
public bool Paid { get; set; }
// null is default value of nullable
public DateTime? PaidDate { get; set; }
// customize default values
[DefaultValue(30)]
public int FollowUpDays { get; set; }
[DefaultValue("")]
public string FollowUpEmailAddress { get; set; }
}
Invoice invoice = new Invoice
{
Company = "Acme Ltd.",
Amount = 50.0m,
Paid = false,
FollowUpDays = 30,
FollowUpEmailAddress = string.Empty,
PaidDate = null
};
string included = JsonConvert.SerializeObject(invoice,
Formatting.Indented,
new JsonSerializerSettings { });
// {
// "Company": "Acme Ltd.",
// "Amount": 50.0,
// "Paid": false,
// "PaidDate": null,
// "FollowUpDays": 30,
// "FollowUpEmailAddress": ""
// }
string ignored = JsonConvert.SerializeObject(invoice,
Formatting.Indented,
new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore });
// {
// "Company": "Acme Ltd.",
// "Amount": 50.0
// }
In .Net Core this is much easier now. In your startup.cs just add json options and you can configure the settings there.
public void ConfigureServices(IServiceCollection services)
....
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
});
With Json.NET
public class Movie
{
public string Name { get; set; }
public string Description { get; set; }
public string Classification { get; set; }
public string Studio { get; set; }
public DateTime? ReleaseDate { get; set; }
public List<string> ReleaseCountries { get; set; }
}
Movie movie = new Movie();
movie.Name = "Bad Boys III";
movie.Description = "It's no Bad Boys";
string ignored = JsonConvert.SerializeObject(movie,
Formatting.Indented,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
The result will be:
{
"Name": "Bad Boys III",
"Description": "It's no Bad Boys"
}
With System.Text.Json and .NET Core 3.0 this worked for me:
var jsonSerializerOptions = new JsonSerializerOptions()
{
IgnoreNullValues = true
};
var myJson = JsonSerializer.Serialize(myObject, jsonSerializerOptions );
An adaption to #Mrchief's / #amit's answer, but for people using VB
Dim JSONOut As String = JsonConvert.SerializeObject(
myContainerObject,
New JsonSerializerSettings With {
.NullValueHandling = NullValueHandling.Ignore
}
)
See:
"Object Initializers: Named and Anonymous Types (Visual Basic)"
https://msdn.microsoft.com/en-us/library/bb385125.aspx
Or just by setting like this.
services.AddMvc().AddJsonOptions(options =>
options.JsonSerializerOptions.IgnoreNullValues = true;
});
To expound slightly on GlennG's very helpful answer (translating the syntax from C# to VB.Net is not always "obvious") you can also decorate individual class properties to manage how null values are handled. If you do this don't use the global JsonSerializerSettings from GlennG's suggestion, otherwise it will override the individual decorations. This comes in handy if you want a null item to appear in the JSON so the consumer doesn't have to do any special handling. If, for example, the consumer needs to know an array of optional items is normally available, but is currently empty...
The decoration in the property declaration looks like this:
<JsonPropertyAttribute("MyProperty", DefaultValueHandling:=NullValueHandling.Include)> Public Property MyProperty As New List(of String)
For those properties you don't want to have appear at all in the JSON change :=NullValueHandling.Include to :=NullValueHandling.Ignore.
By the way - I've found that you can decorate a property for both XML and JSON serialization just fine (just put them right next to each other). This gives me the option to call the XML serializer in dotnet or the NewtonSoft serializer at will - both work side-by-side and my customers have the option to work with XML or JSON. This is slick as snot on a doorknob since I have customers that require both!
Here's an option that's similar, but provides another choice:
public class DefaultJsonSerializer : JsonSerializerSettings
{
public DefaultJsonSerializer()
{
NullValueHandling = NullValueHandling.Ignore;
}
}
Then, I use it like this:
JsonConvert.SerializeObject(postObj, new DefaultJsonSerializer());
The difference here is that:
Reduces repeated code by instantiating and configuring JsonSerializerSettings each place it's used.
Saves time in configuring every property of every object to be serialized.
Still gives other developers flexibility in serialization options, rather than having the property explicitly specified on a reusable object.
My use-case is that the code is a 3rd party library and I don't want to force serialization options on developers who would want to reuse my classes.
Potential drawbacks are that it's another object that other developers would need to know about, or if your application is small and this approach wouldn't matter for a single serialization.
This does not exactly answer the original question, but may prove useful depending on the use case. (And since I wound up here after my search, it may be useful for others.)
In my most recent experience, I'm working with a PATCH api. If a property is specified but with no value given (null/undefined because it's js), then the property and value are removed from the object being patched. So I was looking for a way to selectively build an object that could be serialized in such a way that this would work.
I remembered seeing the ExpandoObject, but never had a true use case for it until today. This allows you to build an object dynamically, so you won't have null properties unless you want them there.
Here is a working fiddle, with the code below.
Results:
Standard class serialization
noName: {"Name":null,"Company":"Acme"}
noCompany: {"Name":"Fred Foo","Company":null}
defaultEmpty: {"Name":null,"Company":null}
ExpandoObject serialization
noName: {"Company":"Acme"}
noCompany: {"name":"Fred Foo"}
defaultEmpty: {}
Code:
using Newtonsoft.Json;
using System;
using System.Dynamic;
public class Program
{
public static void Main()
{
SampleObject noName = new SampleObject() { Company = "Acme" };
SampleObject noCompany = new SampleObject() { Name = "Fred Foo" };
SampleObject defaultEmpty = new SampleObject();
Console.WriteLine("Standard class serialization");
Console.WriteLine($" noName: { JsonConvert.SerializeObject(noName) }");
Console.WriteLine($" noCompany: { JsonConvert.SerializeObject(noCompany) }");
Console.WriteLine($" defaultEmpty: { JsonConvert.SerializeObject(defaultEmpty) }");
Console.WriteLine("ExpandoObject serialization");
Console.WriteLine($" noName: { JsonConvert.SerializeObject(noName.CreateDynamicForPatch()) }");
Console.WriteLine($" noCompany: { JsonConvert.SerializeObject(noCompany.CreateDynamicForPatch()) }");
Console.WriteLine($" defaultEmpty: { JsonConvert.SerializeObject(defaultEmpty.CreateDynamicForPatch()) }");
}
}
public class SampleObject {
public string Name { get; set; }
public string Company { get; set; }
public object CreateDynamicForPatch()
{
dynamic x = new ExpandoObject();
if (!string.IsNullOrWhiteSpace(Name))
{
x.name = Name;
}
if (!string.IsNullOrEmpty(Company))
{
x.Company = Company;
}
return x;
}
}
.Net 6 -
Add the code in Program.cs. This will ignore the class or record property if it is null.
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.AddJsonOptions(opts =>
{
var enumConverter = new JsonStringEnumConverter();
opts.JsonSerializerOptions.Converters.Add(enumConverter);
opts.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault | JsonIgnoreCondition.WhenWritingNull;
});
var settings = new JsonSerializerSettings();
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
settings.NullValueHandling = NullValueHandling.Ignore;
//you can add multiple settings and then use it
var bodyAsJson = JsonConvert.SerializeObject(body, Formatting.Indented, settings);