Custom JsonConverter added to JsonSerializerOptions.Converters not used by default - c#

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.

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

How to mock IConfiguration.GetValue

I tried in vain to mock a top-level (not part of any section) configuration value (.NET Core's IConfiguration). For example, neither of these will work (using NSubstitute, but it would be the same with Moq or any mock package I believe):
var config = Substitute.For<IConfiguration>();
config.GetValue<string>(Arg.Any<string>()).Returns("TopLevelValue");
config.GetValue<string>("TopLevelKey").Should().Be("TopLevelValue"); // nope
// non generic overload
config.GetValue(typeof(string), Arg.Any<string>()).Returns("TopLevelValue");
config.GetValue(typeof(string), "TopLevelKey").Should().Be("TopLevelValue"); // nope
In my case, I also need to call GetSection from this same config instance.
You can use an actual Configuration instance with in-memory data.
//Arrange
var inMemorySettings = new Dictionary<string, string> {
{"TopLevelKey", "TopLevelValue"},
{"SectionName:SomeKey", "SectionValue"},
//...populate as needed for the test
};
IConfiguration configuration = new ConfigurationBuilder()
.AddInMemoryCollection(inMemorySettings)
.Build();
//...
Now it is a matter of using the configuration as desired to exercise the test
//...
string value = configuration.GetValue<string>("TopLevelKey");
string sectionValue = configuration.GetSection("SectionName").GetValue<string>("SomeKey");
//...
Reference: Memory Configuration Provider
I do not have idea about NSubstitute, but this is how we can do in Moq.
Aproach is same in either cases.
GetValue<T>() internally makes use of GetSection().
You can Mock GetSection and return your Own IConfigurationSection.
This includes two steps.
1). Create a mock for IConfigurationSection (mockSection) & Setup .Value Property to return your desired config value.
2). Mock .GetSection on Mock< IConfiguration >, and return the above mockSection.Object
Mock<IConfigurationSection> mockSection = new Mock<IConfigurationSection>();
mockSection.Setup(x=>x.Value).Returns("ConfigValue");
Mock<IConfiguration> mockConfig = new Mock<IConfiguration>();
mockConfig.Setup(x=>x.GetSection(It.Is<string>(k=>k=="ConfigKey"))).Returns(mockSection.Object);
Mock IConfiguration
Mock<IConfiguration> config = new Mock<IConfiguration>();
SetupGet
config.SetupGet(x => x[It.Is<string>(s => s == "DeviceTelemetryContainer")]).Returns("testConatiner");
config.SetupGet(x => x[It.Is<string>(s => s == "ValidPowerStatus")]).Returns("On");
IConfiguration.GetSection<T> must be mocked indirectly. I don't fully understand why because NSubstitute, if I understand correctly, creates its own implementation of an interface you're mocking on the fly (in memory assembly). But this seems to be the only way it can be done. Including a top-level section along with a regular section.
var config = Substitute.For<IConfiguration>();
var configSection = Substitute.For<IConfigurationSection>();
var configSubSection = Substitute.For<IConfigurationSection>();
configSubSection.Key.Returns("SubsectionKey");
configSubSection.Value.Returns("SubsectionValue");
configSection.GetSection(Arg.Is("SubsectionKey")).Returns(configSubSection);
config.GetSection(Arg.Is("TopLevelSectionName")).Returns(configSection);
var topLevelSection = Substitute.For<IConfigurationSection>();
topLevelSection.Value.Returns("TopLevelValue");
topLevelSection.Key.Returns("TopLevelKey");
config.GetSection(Arg.Is<string>(key => key != "TopLevelSectionName")).Returns(topLevelSection);
// GetValue mocked indirectly.
config.GetValue<string>("TopLevelKey").Should().Be("TopLevelValue");
config.GetSection("TopLevelSectionName").GetSection("SubsectionKey").Value.Should().Be("SubsectionValue");
I could imagine in some rare scenarios it is needed but, in my humble opinion, most of the time, mocking IConfiguration highlight a code design flaw.
You should rely as much as possible to the option pattern to provide a strongly typed access to a part of your configuration. Also it will ease testing and make your code fail during startup if your application is misconfigured (instead than at runtime when the code reading IConfiguration is executed).
If you really(really) need to mock it then I would advice to not mock it but fake it with an in-memory configuration as explained in #Nkosi's answer
While Nkosi's answer works great for simple structures, sometimes you want to be able to have more complex objects (like arrays) without repeating the whole section path and to be able to use the expected types themselves. If you don't really care too much about performance (and should you in your unit tests?) then this extension method might be helpful.
public static void AddObject(this IConfigurationBuilder cb, object model) {
cb.AddJsonStream(new MemoryStream(Encoding.UTF8.GetString(JsonConvert.SerializeObject(model))));
}
And then use it like this
IConfiguration configuration = new ConfigurationBuilder()
.AddObject(new {
SectionName = myObject
})
.Build();
Use SetupGet method to mock the Configuration value and return any string.
var configuration = new Mock<IConfiguration>();
configuration.SetupGet(x => x[It.IsAny<string>()]).Returns("the string you want to return");
We need to mock IConfiguration.GetSection wichs is executed within GetValue extension method.
You inject the IConfiguration:
private readonly Mock<IConfiguration> _configuration;
and the in the //Arrange Section:
_configuration.Setup(c => c.GetSection(It.IsAny())).Returns(new Mock().Object);
It worked like a charm for me.
I've found this solution to work reliably for me for my XUnit C# tests & corresponding C# code:
In Controller
string authConnection = this._config["Keys:AuthApi"] + "/somePathHere/Tokens/jwt";
In XUnit Test
Mock<IConfiguration> mockConfig = new Mock<IConfiguration>();
mockConfig.SetupGet(x => x[It.Is<string>(s => s == "Keys:AuthApi")]).Returns("some path here");

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

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

Is it possible to return a pre-encoded JSON string in a SignalR hub method?

In an MVC project, I have a method on a hub similar to this:
public string Foo() { return DoCrazyThingThatReturnsJson(); }
Unfortunately SignalR (or something) takes the encoded JSON string and happily encodes it, then returns it, so browser be like LOLWTF. Is there a way to skip this second encoding?
Looking at this here:
We assume that any ArraySegment is already JSON serialized
it seems like something like this may work (note that I haven't tried it, so no promises):
string jsonString = ...; // your serialized data
var jsonBytes = new ArraySegment<byte>(Encoding.UTF8.GetBytes(jsonString));
Clients.Caller.sendJson(jsonBytes);
(The above only works for PersistentConnection because of what constitutes a value - for hubs, the data is wrapped in a container with the RPC info; I'm only leaving this in case using PersistentConnection is an option for you)
Another idea might be creating a container class for a JSON string, then using a custom converter that simply writes the string as-is, then register like this in the Signalr startup code:
var serializer = new JsonSerializer();
serializer.Converters.Add(new PreserializedConverter());
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => serializer);

Autofac generic decorator is duplicating components

I have found what I think may be a bug in Autofac, but I wanted to see if anybody had a possible solution or workaround so I can make this work.
Basically I have set up a generic decorator, which works fine. The problem is that as soon as I call BeginLifetimeScope() with a configuration delegate, it erroneously resolves multiple components of the same type. If I don't use a configuration delegate with BeginLifetimeScope(), then it works correctly. Unfortunately, I need to add additional dependencies to my child scope, so not using a configuration delegate is not an option.
Here is an example that demostrates the problem:
var builder = new ContainerBuilder();
builder.RegisterType<Dependency>()
.Named<IDependency<object>>("service");
builder.RegisterGenericDecorator(
typeof(Decorator<>), typeof(IDependency<>), "service", "decorated");
var container = builder.Build();
// Returns 1
var scope1 = container.BeginLifetimeScope();
Console.WriteLine(
scope1.ResolveNamed<IEnumerable<IDependency<object>>>("decorated").Count());
// Returns 2 - notice the configAction doesn't even have to do anything
var scope2 = container.BeginLifetimeScope(r => { });
Console.WriteLine(
scope2.ResolveNamed<IEnumerable<IDependency<object>>>("decorated").Count());
And here are my fake types:
interface IDependency<T> { }
class Dependency : IDependency<object> { }
class Decorator<T> : IDependency<T> {}
Any help would be greatly appreciated!
It does just seem like a bug. As a workaround, I ended up doing the following:
var param = new TypedParameter(typeof(IDecoratorDependency), new DecoratorDependency());
var decorated = scope.ResolveNamed<IEnumerable<IDependency<object>>>("decorated", param);
That was good enough for my use case. However, this method is inflexible because it only allows me to supply parameters to the root object, in this case Decorator<T>, but not any of its dependencies.

Categories

Resources