91

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?


4
  • 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
    Commented Oct 10, 2019 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. Commented Oct 10, 2019 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
    Commented Oct 10, 2019 at 23:38
  • Ah, gotcha. In that sense I think our case would fall under "other resources." @ps2goat Commented Oct 10, 2019 at 23:55

14 Answers 14

37

You can create an extension method. Here's an example (UPDATE 2023-10-27: Pulled the code to the bottom of this answer for completeness)

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>();

Updated 2023-10-27 I revisited this answer and realized that the example code was never included here. I have copied it here in case something happens to the linked code. It was originally targeting .NET Core 3.x but last confirmed with .NET 7.

using System;
using System.Text.Json;
                    
public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World");
        
        
        // "json".Deserialize<object>();
        Car result = @"{""Wheels"": 4, ""Doors"": 2}".DeserializeFeatureX<Car>();
        
        Console.WriteLine($"Doors: { result.Doors }, Wheels: { result.Wheels }");
    }
}

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);
    }
}

public class Car
{
  public int Wheels { get; set; }
  public int Doors { get; set; }
}

0
29

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

services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        options.JsonSerializerOptions.PropertyNamingPolicy=JsonNamingPolicy.CamelCase;
    });
6
  • 23
    It does work. However it's specific to configuring Controllers. Commented Mar 5, 2021 at 22:06
  • 3
    @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. Commented May 20, 2021 at 16:11
  • 2
    To use in non-controllers: using Microsoft.Extensions.Options;, add the parameter (IOptions<JsonOptions> jsonOptions) and [de]serialize with (..., jsonOptions.Value.JsonSerializerOptions);
    – Elijah
    Commented Jul 23, 2023 at 19:44
  • this does not work for me when using JsonSerializer..Deserialize in a MVC controller.
    – kimbaudi
    Commented Nov 1, 2023 at 19:39
  • 1
    @OliverNilsen JsonOptions is the ASP.NET configuration for all things Json of which JsonSerializerOptions is one part of that as seen in your Program.cs. Relevant snippet: blog.elijahlopez.ca/posts/aspnet-serialize-enum-as-string/… note how we are using options.JsonSerializerOptions.XXXXX = and not options.XXXX =?
    – Elijah
    Commented May 5 at 0:28
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.

1
  • 2
    "No, JsonSerializerOptions does not expose the default options." - small comment: nowadays it actually does via JsonSerializerOptions.Default but it is readonly, i.e. can't be manipulated.
    – Guru Stron
    Commented Jan 28 at 13:22
11

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:

1
  • 1
    Nice... thanks for sharing and publishing to Nuget for easy use. It would be nice if the GitHub page had a couple extra examples on how to initialize the defaults instead of having to go find/read the code. Commented May 31 at 20:29
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.

6
  • 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
    Commented Jul 21, 2020 at 18:10
  • Works fine! Thank you Commented Mar 23, 2021 at 20:56
  • doesn't work in .NET7 github.com/dotnet/runtime/issues/31094#issuecomment-1077693953
    – SerjG
    Commented Dec 9, 2022 at 9:22
  • 2
    Fix: public static void SetIgnoreNulls() => typeof(JsonSerializerOptions).GetRuntimeFields().Single(f => f.Name == "_defaultIgnoreCondition").SetValue(JsonSerializerOptions.Default, JsonIgnoreCondition.WhenWritingNull); Why so complicated: github.com/dotnet/runtime/issues/15643
    – SerjG
    Commented Dec 9, 2022 at 9: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
    Commented Dec 27, 2022 at 20:36
3

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

3

Using .NET 8 prerelease and it even includes snake case!

.ConfigureHttpJsonOptions(options => {
          options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
})
1
  • In my tests, this doesn't work with controllers but works with minimal APIs.
    – harvzor
    Commented Feb 22 at 14:18
2

I looked for a solution to use my source generated context as the default serializer, but the default options are still read-only.

I'll just leave this here in case it helps someone else: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation?pivots=dotnet-8-0#source-generation-support-in-aspnet-core

The solution there looks like this:

[JsonSerializable(typeof(WeatherForecast[]))]
internal partial class MyJsonContext : JsonSerializerContext { }
var serializerOptions = new JsonSerializerOptions
{
    TypeInfoResolver = MyJsonContext.Default;
};

services.AddControllers().AddJsonOptions(
    static options =>
        options.JsonSerializerOptions.TypeInfoResolverChain.Add(MyJsonContext.Default));
1

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.

1

As @elijah commented, injecting IOptions<JsonOptions> jsonOptions worked for me. I was then able to use the JsonSerializerOptions property. I noticed no differences between this and what I had configured using AddJsonOptions() in my Program.cs file.

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.

0

For ASP.NET Core 7.0+:

builder.Services.ConfigureHttpJsonOptions(options => {
    options.SerializerOptions.PropertyNameCaseInsensitive = true;
    options.SerializerOptions.WriteIndented = true;
    options.SerializerOptions.AllowTrailingCommas = true;
    options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    options.SerializerOptions.ReferenceHandler =ReferenceHandler.IgnoreCycles;
});

The method HttpJsonServiceExtensions.ConfigureHttpJsonOptions():

Configures options used for reading and writing JSON when using Microsoft.AspNetCore.Http.HttpRequestJsonExtensions.ReadFromJsonAsync and Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsync. JsonOptions uses default values from JsonSerializerDefaults.Web.

This method is available starting from ASP.NET Core 7.0.

1
  • 2
    This will not affect the default JsonSerializer. This only changes the httpJson serialization in asp.net.
    – yousif
    Commented May 15 at 12:54
0

I had to use a different converter for enums in my http client. In my case to avoid using the options in each json conversion, and due to not having the option to change the default options (unless using reflection from above). I created a converter and used it as attribute above the enum

public class EnumStringConverter<TEnum> : JsonConverter<TEnum> where TEnum : Enum
{
    public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string enumString = reader.GetString()!;
        return (TEnum)Enum.Parse(typeof(TEnum), enumString, true);
    }

    public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }
}

Enum:

[JsonConverter(typeof(EnumStringConverter<MyCoolEnum>))]
public enum MyCoolEnum {A,B,C}
-4

(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);
3
  • 1
    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 Commented Dec 9, 2020 at 10:45
  • 8
    "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
    Commented Oct 19, 2021 at 17:32
  • Newtonsoft.Json does not officially support .NET 5/6/7... newtonsoft.com/json/help/html/Introduction.htm
    – joym8
    Commented Mar 22, 2023 at 19:03

Not the answer you're looking for? Browse other questions tagged or ask your own question.