77

Instead of this:

JsonSerializerOptions options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    // etc.
};
var so = JsonSerializer.Deserialize<SomeObject>(someJsonString, options);

I would like to do something like this:

// This property is a pleasant fiction
JsonSerializer.DefaultSettings = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    // etc.
};

// This uses my options
var soA = JsonSerializer.Deserialize<SomeObject>(someJsonString); 

// And somewhere else in the same codebase...
// This also uses my options
var soB = JsonSerializer.Deserialize<SomeOtherObject>(someOtherJsonString); 

The hope is to not have to pass an instance of JsonSerializerOptions for our most common cases, and override for the exception, not the rule.

As indicated in this q & a, this is a useful feature of Json.Net. I looked in the documentation for System.Text.Json as well as this GitHub repo for .NET Core. And this one.

There doesn't seem to be an analog for managing JSON serialization defaults in .NET Core 3. Or am I overlooking it?


Trevor Reid
  • 3,310
  • 4
  • 27
  • 46
  • `There doesn't seem to be an analog for managing JSON serialization defaults in Core-3` -- are you talking about requests into and out of your API? or requests and responses to other resources? – ps2goat Oct 10 '19 at 22:59
  • @ps2goat I am not sure I understand your question. The matter here is (de)serializing JSON strings. They could come from any number of sources. – Trevor Reid Oct 10 '19 at 23:06
  • 1
    I was asking because there are special places during startup for input and output formatters (e.g., for model binding) – ps2goat Oct 10 '19 at 23:38
  • Ah, gotcha. In that sense I think our case would fall under "other resources." @ps2goat – Trevor Reid Oct 10 '19 at 23:55

9 Answers9

28

You can create an extension method. Here's an example

I use separate methods vs having to build special settings, so that all the settings will be in a single spot and easily reusable.

public static class DeserializeExtensions
{
    private static JsonSerializerOptions defaultSerializerSettings = new JsonSerializerOptions();

    // set this up how you need to!
    private static JsonSerializerOptions featureXSerializerSettings = new JsonSerializerOptions();


    public static T Deserialize<T>(this string json)
    {       
        return JsonSerializer.Deserialize<T>(json, defaultSerializerSettings);
    }

    public static T DeserializeCustom<T>(this string json, JsonSerializerOptions settings)
    {
        return JsonSerializer.Deserialize<T>(json, settings);
    }

    public static T DeserializeFeatureX<T>(this string json)
    {
        return JsonSerializer.Deserialize<T>(json, featureXSerializerSettings);
    }
}

Then you call it as a method on a string, whether literal or a variable.

    Car result = @"{""Wheels"": 4, ""Doors"": 2}".DeserializeFeatureX<Car>();
ps2goat
  • 8,067
  • 1
  • 35
  • 68
22

This seemed to work for me, in StartUp.ConfigureServices:

services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        options.JsonSerializerOptions.PropertyNamingPolicy=JsonNamingPolicy.CamelCase;
    });
Pang
  • 9,564
  • 146
  • 81
  • 122
John
  • 357
  • 2
  • 7
  • 15
    It does work. However it's specific to configuring Controllers. – Trevor Reid Mar 05 '21 at 22:06
  • 2
    @TrevorReid good point, and this is an issue if you e.g. use Refit to make API calls from your asp.net core --> then Refit will ignore these settings. – hansmbakker May 20 '21 at 16:11
  • To use in non-controllers: `using Microsoft.Extensions.Options;`, add the parameter (`IOptions jsonOptions`) and [de]serialize with `(..., jsonOptions.Value.JsonSerializerOptions);` – Elijah Jul 23 '23 at 19:44
20

No, JsonSerializerOptions does not expose the default options. If you are using a particular web framework there may be a way to specify (de-)serialization settings through that. Otherwise, I suggest creating your own convenience methods.

See also this open issue.

Chris Yungmann
  • 1,079
  • 6
  • 14
10

A workaround has been proposed by GitHub user andre-ss6 as follows:

((JsonSerializerOptions)typeof(JsonSerializerOptions)
    .GetField("s_defaultOptions", 
        System.Reflection.BindingFlags.Static |
        System.Reflection.BindingFlags.NonPublic).GetValue(null))
    .PropertyNameCaseInsensitive = true;

Update [2023-02-17]: but for NET7 see this answer.

Trevor Reid
  • 3,310
  • 4
  • 27
  • 46
  • This works nicely, but is very brittle. I'm going to use it (I'm registering custom type converters, not changing PropertyNameCaseInsensitive, but the premise is the same) and cross my fingers that it keeps working until the expected release in November 2020 that official supports setting default options. – Tim Jul 21 '20 at 18:10
  • Works fine! Thank you – PassionateDeveloper Mar 23 '21 at 20:56
  • doesn't work in .NET7 https://github.com/dotnet/runtime/issues/31094#issuecomment-1077693953 – SerjG Dec 09 '22 at 09:22
  • 1
    Fix: `public static void SetIgnoreNulls() => typeof(JsonSerializerOptions).GetRuntimeFields().Single(f => f.Name == "_defaultIgnoreCondition").SetValue(JsonSerializerOptions.Default, JsonIgnoreCondition.WhenWritingNull);` Why so complicated: https://github.com/dotnet/runtime/issues/15643 – SerjG Dec 09 '22 at 09:47
  • @SerjG - It isn't complicated. It's obfuscated. That's because it's a private field that you aren't supposed to even know how to alter. It's internal to the framework, and could change tomorrow or yesterday, and show no trace to you that it changed, causing a butt-ton of issues in your code. Some people are open to taking this risk, but it isn't documented or simple because it's not something C# supports. It is a literal hack. But... thanks for finding the latest updated hack, assuming it works for people, lol. :D – Suamere Dec 27 '22 at 20:36
  • @Suamere I meant that it uses LINQ instead of the `GetRuntimeField` method because of the bug. It's not related to reflection way to set private property at all. Have you check my code and link after it? – SerjG Dec 28 '22 at 08:11
10

The default options are not exposed in JsonSerializer for .NET Core 3.1. However, as of December, 2019 this has been added to the road map for 5.0.

The release of .NET 5.0 is expected November, 2020. But there's no guarantee this particular issue will be addressed at any particular time. Other than waiting, these answers suggest workarounds:

Also, I packaged my convenience extension methods, inspired by @ps2goat's answer and put them on nuget.org and github:

Trevor Reid
  • 3,310
  • 4
  • 27
  • 46
2

Some fields has became auto-properties starting .NET7. So there is no way to change it as in earlier answers.

New approach is to change the private fields directly:

public static void SetIgnoreNulls() => typeof(JsonSerializerOptions).GetRuntimeFields()
        .Single(f => f.Name == "_defaultIgnoreCondition")
        .SetValue(JsonSerializerOptions.Default, JsonIgnoreCondition.WhenWritingNull);

Why GetRuntimeFields().Single() and not just GetRuntimeField(%name%)? Answer is here: https://github.com/dotnet/runtime/issues/15643

Trevor Reid
  • 3,310
  • 4
  • 27
  • 46
SerjG
  • 3,325
  • 3
  • 30
  • 30
0

In case one also needs to add custom converters to the default JsonSerializerOptions (System.Text.Json >= 7.0.0) - without the use of controllers - one can use the following trick:

var jsonConverterList = new List<JsonConverter>
{
    new YourCustomConverter1(),
    new YourCustomConverter2()
};

Type type = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(assembly => assembly.GetTypes())
    .SingleOrDefault(t => t.FullName == "System.Text.Json.JsonSerializerOptions+ConverterList");
object[] paramValues = new object[] { JsonSerializerOptions.Default, jsonConverterList };
var converterList = type!.GetConstructors()[0].Invoke(paramValues) as IList<JsonConverter>;
typeof(JsonSerializerOptions).GetRuntimeFields().Single(f => f.Name == "_converters")
    .SetValue(JsonSerializerOptions.Default, converterList);

The property JsonSerializerOptions.Default.Converters does not allow adding items, as is is immutable, therefore this trick replaces the converterList alltogether. And since ConverterList is a private sealed class invoking its constructor requires reflection.

PWC
  • 1
  • 1
0

Found this looking for some inspiration. We have a web API that calls some other APIs. The third-party APIs may use camel casing names, or they may use kebab. Generally, each is internally consistent with itself, but the naming convention changes between APIs. I needed to configure the options in a limited scope but not contaminate other projects.

I ended up making an options object, called such not to confuse with settings, specific to each project (set visibility to internal), surfaced through DI, that had the JSON settings as a property. Project-specific settings, like how an arbitrary API names properties, are contained to that project, and each project is responsible for setting its defaults.

I've found it's better, especially for library code, to be specific and deal with passing around the options object than to try and set up full app defaults only to limit interoperability. This also lets me avoid marking property names on my DTOs, so I can serialize the DTO outbound from my top-level API and not have explicit JSON property names breaking the API-level conventions.

If you do have to support an atypical property-specific name, you can use an internal property with getters and setters to your public undecorated property to isolate the change.

J Scott
  • 781
  • 9
  • 12
-2

(If you ever switch to using Json.NET)

I prefer and recommend being explicit and pass settings to all calls, but you can set defaults with DefaultSettings.

JsonConvert.DefaultSettings = () => MySuperJsonSerializerSettings;

and then

var json = JsonConvert.SerializeObject(o1);
var o2 = JsonConvert.DeserializeObject(x);
tymtam
  • 31,798
  • 8
  • 86
  • 126
  • I guess you got voted down due to the fact that the question was referring to System.Text.Json and not Newtonsofts (much better) package. In my case I don't have the choice to "switch" into Json.Net, since that option was taken away from me in this Blazor web assembly project. Its far from great, its just how it is – Christopher Bonitz Dec 09 '20 at 10:45
  • 7
    "much better" is a matter of perspective - Newtonsoft.JSON is certainly "better" in thems of config and supporting weird (even invalid json). However, System.Text.Json is better in terms of performance (both memory and CPU). – nover Oct 19 '21 at 17:32
  • Newtonsoft.Json does not officially support .NET 5/6/7... https://www.newtonsoft.com/json/help/html/Introduction.htm – joym8 Mar 22 '23 at 19:03