How to globally set default options for System.Text.Json.JsonSerializer? - c#

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?
UPDATE [2020-07-18]: See this answer for a nuget package with convenience methods that honor default settings.
UPDATE [2019-12-23]: Due in part to vocal community input this issue has been added to the roadmap for .NET 5.0.
UPDATE [2019-10-10]: If interested in seeing this behavior implemented for System.Text.Json.JsonSerializer head on over to the open GitHub issue pointed out by Chris Yungmann and weigh in.

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

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.

This seemed to work for me, in StartUp.ConfigureServices:
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
options.JsonSerializerOptions.PropertyNamingPolicy=JsonNamingPolicy.CamelCase;
});

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:
https://stackoverflow.com/a/58331912/1011722
https://stackoverflow.com/a/58959198/1011722
Also, I packaged my convenience extension methods, inspired by #ps2goat's answer and put them on nuget.org and github:
https://www.nuget.org/packages/Fetchgoods.Text.Json.Extensions/

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.

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

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

Related

Is it possible to use a JsonSerializer default option in a .NETCore Console Application? [duplicate]

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?
UPDATE [2020-07-18]: See this answer for a nuget package with convenience methods that honor default settings.
UPDATE [2019-12-23]: Due in part to vocal community input this issue has been added to the roadmap for .NET 5.0.
UPDATE [2019-10-10]: If interested in seeing this behavior implemented for System.Text.Json.JsonSerializer head on over to the open GitHub issue pointed out by Chris Yungmann and weigh in.
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>();
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.
This seemed to work for me, in StartUp.ConfigureServices:
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
options.JsonSerializerOptions.PropertyNamingPolicy=JsonNamingPolicy.CamelCase;
});
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:
https://stackoverflow.com/a/58331912/1011722
https://stackoverflow.com/a/58959198/1011722
Also, I packaged my convenience extension methods, inspired by #ps2goat's answer and put them on nuget.org and github:
https://www.nuget.org/packages/Fetchgoods.Text.Json.Extensions/
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.
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
(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);

MongoCollectionSettings.GuidRepresentation is obsolete, what's the alternative?

I am using MongoDB.Driver 2.11.0 and .Net Standard 2.1. To ensure that a database exists and a collection exists, I have the following code:
IMongoClient client = ...; // inject a Mongo client
MongoDatabaseSettings dbSettings = new MongoDatabaseSettings();
IMongoDatabase db = client.GetDatabase("MyDatabase", dbSettings);
MongoCollectionSettings collectionSettings = new MongoCollectionSettings()
{
GuidRepresentation = GuidRepresentation.Standard,
};
IMongoCollection<MyClass> collection = db.GetCollection<MyClass>("MyClasses", collectionSettings);
In earlier versions of MongoDB.Driver, this code would compile without any warnings. In v2.11.0 I am now getting a warning that "MongoCollectionSettings.GuidRepresentation is obsolete: Configure serializers instead" but I have not been able to find any samples illustrating the new way of setting the Guid serialization format. Does anyone know of other ways to set the serializers for a collection?
If you want to define GuidRepresentation for a specific property, you can do it during the registration of the class map, like so:
BsonClassMap.RegisterClassMap<MyClass>(m =>
{
m.AutoMap();
m.MapIdMember(d => d.Id).SetSerializer(new GuidSerializer(GuidRepresentation.Standard));
});
If you want to do it globally:
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
It was changed in the latest release, see details here: https://mongodb.github.io/mongo-csharp-driver/2.11/reference/bson/guidserialization/

Custom JsonConverter added to JsonSerializerOptions.Converters not used by default

I have implemented a custom json converter for one of my classes since I want to use the constructor for deserialization.
I have added the custom converter to my Startup.cs class:
services.AddControllers()
.AddJsonOptions(opt =>
{
opt.JsonSerializerOptions.WriteIndented = false;
opt.JsonSerializerOptions.Converters.Add(new MyJsonConverter());
});
My custom json converter looks like this:
public class MyJsonConverter : JsonConverter<MyClass>
{
...
}
The problem is that when running
var result = await JsonSerializer.DeserializeAsync<RoleEntity[]>(json);
my custom json converter isn't triggered.
However, if instead specifying it explicitly like the following
var serializerOptions = new JsonSerializerOptions
{
Converters = { new MyJsonConverter() }
};
var result = await JsonSerializer.DeserializeAsync<RoleEntity[]>(json, serializerOptions);
it works.
I just can't see what the problem is. Am I missing something trivial here?
This is the expected behaviour. When you use JsonSerializer.DeserializeAsync directly without specifying any options it, well, won't use any custom options. The options you add in are for MVC (see this answer). You need to either continue using it as you wrote or register options as singleton which you could then resolve/inject and use within your JsonSerializer.DeserializeAsync call.
To add a new singleton you can use IServiceCollection.AddSingleton.
There are many ways to get the result you want (singleton options is just one of them) but the important part of this answer is that the options you define with .AddJsonOptions will not be used for direct calls to JsonSerializer-methods.

Mongo.net - BypassDocumentValidation is not working

I'm trying to insert a json like this (fieldname with a "."), in a Net Core Console Project
{"name.field" : "MongoDB", "type" : "Database"}
Using the C# code belove:
-with InsertManyOptions with BypassDocumentValidation in true
var options = new InsertManyOptions
{
BypassDocumentValidation = true,
IsOrdered = false
};
await _collection.InsertManyAsync(items, options);
But I have this exception:
Element name 'name.field' is not valid
I´m using :
C# Mongo Driver 2.5
Net Core Project
MongoDB version 4.0.3
Any idea? Thanks!
The BypassDocumentValidation can be used to bypass the JSON Schema validation. The issue you are facing, however, is due to the C# driver which explicitly prevents the use of the dot symbol . as part of a field name.
This used to be required up until MongoDB v3.6 which officially added support for fields with ".".
Looking into the internals of the C# driver you can see that the BsonWriter.WriteName method calls contains this code which throws the Exception you're seeing:
if (!_elementNameValidator.IsValidElementName(name))
{
var message = string.Format("Element name '{0}' is not valid'.", name);
throw new BsonSerializationException(message);
}
The _elementNameValidator is something that is managed internally by the driver which in fact comes with a NoOpElementNameValidator that doesn't do any validations. The driver, however, won't use this validator for "normal" collections.
All that said, I would strongly advise against the use of field names with "unusual" characters anyway because this is likely to set you up for unexpected behaviour and all sorts of other issues down the road.
In order to get around this you can do one of the following things:
a) Write your own custom serializer which is an option that I would personally steer clear off if possible - it adds complexity that most of the time shouldn't be required.
b) Use the below helper extension (copied from one of the unit testing projects inside the driver) to convert the BsonDocument into a RawBsonDocument which can then successfully written to the server:
public static class RawBsonDocumentHelper
{
public static RawBsonDocument FromBsonDocument(BsonDocument document)
{
using (var memoryStream = new MemoryStream())
{
using (var bsonWriter = new BsonBinaryWriter(memoryStream, BsonBinaryWriterSettings.Defaults))
{
var context = BsonSerializationContext.CreateRoot(bsonWriter);
BsonDocumentSerializer.Instance.Serialize(context, document);
}
return new RawBsonDocument(memoryStream.ToArray());
}
}
public static RawBsonDocument FromJson(string json)
{
return FromBsonDocument(BsonDocument.Parse(json));
}
}
And then simply write the RawBsonDocument to the server:
RawBsonDocument rawDoc = RawBsonDocumentHelper.FromJson("{\"name.field\" : \"MongoDB\", \"type\" : \"Database\"}");
collection.InsertOne(rawDoc);

c# CommandLine.Parser - Use constructor that accepts Action<ParserSettings>

I was using this code, but I am getting a compiler warning that this method of creation is deprecated. As I want to remove the warning, and move to the newer version, I want to correct the code, but I can not get the CommandLineParser 1.9.7 library to work.
CommandLine.Parser OptionParser = new CommandLine.Parser(new CommandLine.ParserSettings
{
CaseSensitive = UseCaseSensitive,
IgnoreUnknownArguments = IgnoreUnknownOptions,
MutuallyExclusive = EnableMutuallyExclusive
}
);
bool Result = OptionParser.ParseArguments(Args, this);
This code works and Result would be True/False based on the parameters of the command line and options passed. However, the following warning is posted.
Warning 1 'CommandLine.Parser.Parser(CommandLine.ParserSettings)' is obsolete: 'Use constructor that accepts Action<ParserSettings>.'
The Online help shows this as an example for using the function.
new CommandLine.Parser(configuration: () => new CommandLine.ParserSettings(Console.Error))
I tried changing the code, but I am not getting the Lambda right, and am not sure how to get this to work. While the code executes, I only get the default functions, I can not seem to change the Case Sensitive, Mutually Exclusive, etc... options.
Line using the Constructor (from the inline IDE help)
bool Result = new CommandLine.Parser(configuration: (Settings) => new CommandLine.ParserSettings(UseCaseSensitive, EnableMutuallyExclusive, IgnoreUnknownOptions, null)).ParseArguments(Args, this);
Trying again with the virtual settings:
bool Result = new CommandLine.Parser(configuration: (Settings) => new CommandLine.ParserSettings
{
CaseSensitive = UseCaseSensitive,
IgnoreUnknownArguments = IgnoreUnknownOptions,
MutuallyExclusive = EnableMutuallyExclusive
}
).ParseArguments(Args, this);
The online help has not kept up with the tool, and I could use any pointers someone might have. Thanks in advance...
Looking at the source code the constructor runs that Action passed on new settings that it creates:
public Parser(Action<ParserSettings> configuration)
{
if (configuration == null) throw new ArgumentNullException("configuration");
this.settings = new ParserSettings();
configuration(this.settings);
this.settings.Consumed = true;
}
So in the Action<ParserSettings> you should set the values you want on the parameter, not create new settings (remember that an Action<T> is a prototype for a function that takes a T and does not return a value):
var parser = new CommandLine.Parser( s =>
{
s.CaseSensitive = UseCaseSensitive;
} );
NOTE: The source code I linked to does not appear to be the same version as you are using since Parser( ParserSettings ) is marked internal in the source I found, which means you wouldn't even be able to call it, and some of the ParserSettings properties do not appear in the version I found. However, I believe this answer applies to the version you have as well.

Categories

Resources