azure-functions-host
azure-functions-host copied to clipboard
Add Config to set output serializer to System.Text.Json
What problem would the feature you're requesting solve? Please describe.
Solves the problem that input serializer and output serializer are different.
Function v3 uses System.Text.Json
as input serializer and Newtonsoft.Json
as output serializer. (#5299)
When sharing a Domain Model with an ASP.NET Core application, different serializers can cause compatibility issues.
Describe the solution you'd like
I need to change the output serializer to System.Text.Json
.
Describe alternatives you've considered
There is a way to configure all applications to use Newtonsoft.Json
.
However, it does not match the current trend of .NET Core.
Additional context
https://github.com/Azure/azure-functions-host/blob/v3.x/src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs#L75
Thanks @shibayan for opening the issue. Functions v3 uses Newtonsoft.Json
for serializer at all times. Is there a place you saw System.Text.Json
used for serializing?
Regardless of that, this sounds like a fair ask to use System.Text.Json
. I am not sure the work that may be involved and if it's something we will be able to look at very soon.
Adding @fabiocav if he had any comment on this ask or priority.
cc: @brettsam (had a super brief chat on this offline)
@ankitkumarr Thank you for replying.
Yes. Changing the default output serializer to System.Text.Json
also has compatibility issues, so I want it to be switchable in settings.
ASP.NET Core 3.0 (MVC / SignalR) uses System.Text.Json
by default. Attributes such as JsonProperty
and JsonIgnore
are not compatible with each other, causing problems when sharing domain models.
@fabiocav @ankitkumarr
I actually came here to see if there was an issue covering a move to use STJ when auto-deserializing POCOs as part of an HttpTrigger, etc. but haven't found something encompassing quite that just yet.
Would it be fair to write an issue that says we should remove usage of NJ in favor of STJ in v3? Folks that requires NJ for compatibility can then import the package (free of unification struggles, yay!) and continue on, but w/in Functions and as part of things that are auto serialized/deserialized we'd be using STJ.
If we don't have an issue like this I'm happy to capture one. I think it would quite heavily impact each first-party extension as well, but they could each get their own similar issue.
I'm also happy to help out w/ the effort if acceptable. Or perhaps this better fits as an issue for the Webjobs SDK repo?
@brandonh-msft the serialization is not the main issue, the binding support is. A large number of apps use JObject
as the binding target type. Removing that in the 3.0 timeframe wasn't viable, and removing in a current major would be a breaking change for those apps.
This is also the only reason why runtime unification exists, but in the vast majority of cases, this should not present a problem, and you still have the flexibility to reference different versions internally.
Do you have a concrete example of a problem you're dealing with because of that behavior?
I am also currently running into the problem of needing to use a custom JsonConverter that's designed to work with System.Text.Json, but Azure Functions appears to enforce Newtonsoft with no way to override in Startup.Configure
I'm having an issue with this as well (https://github.com/Azure/azure-functions-host/issues/5203#issuecomment-667859103).
I believe it is impossible to use System.Text.Json
when
- returning an OkObjectResult,
- when using the built-in deserialization of trigger input
This is annoying because projects shared between an asp.net core project and an azure functions project will have to "live in 2 worlds".
Imagine you want to set [JsonConverter(typeof(JsonStringEnumConverter))]
on a data model enum (using System.Text.Json
) and you want to use that in Azure Functions, it's a no go
Concerning "Function v3 uses System.Text.Json as input serializer and Newtonsoft.Json as output serializer." I have also observed this to be the case. This is confusing and IMO would have been much more logical if MS had stuck with one or the other.
I just ran into the same case. Will this get fixed with .NET5 for Azure Functions?
@stevo-knievo the model with the OOP worker for .NET 5 in functions is a bit different, but you have full control over the stack there.
@fabiocav thanks for your reply! I'm looking forward to using .NET5 together with Azure Functions.
I am having trouble with this as well. Internally using System.Text.Json
, including custom converters and pocos, so far so good. When trying to return a JsonResult
from a HttpTrigger
I realized that it is using Newtonsoft (and thus wont take my JsonSerializerOptions
)
is there any way I can work around this?
Same here, having an object in the model and System.Text.Json
transforms this to "ValueKind" objects but then when serialized via OkObjectResult gives me a corrupt Value.
an easy sample is a bool object of false
get transformed to a object : { "valueKind": 6 }
Do I need to rewoke all this to NewtonSoft?
Sample Object Read from Cosmos via System.Text.Json
[ { "Name": "Enabled", "Value": false, "Type": "bool", "DefaultValue": true, "AllowedValues": [] }, { "Name": "Severity", "Value": "Low", "Type": "enum", "DefaultValue": "Medium", "AllowedValues": [ "Low", "Medium", "Warning", "Critical" ] } ]
Ends up like this when using OkObjectResult
or JsonResult
as return IActionResult
it ends up like this, note the valueKind. And that breaks the consumer.
[ { "name": "Enabled", "value": { "valueKind": 6 }, "type": "bool", "defaultValue": true, "allowedValues": [] }, { "name": "Severity", "value": { "valueKind": 3 }, "type": "enum", "defaultValue": "Medium", "allowedValues": [ "Low", "Medium", "Warning", "Critical" ] } ]
This doesn't help people with existing Azure Functions, but the Azure Functions for .net 5 isolated runtime allows you to configure the JSON serialization options now. So for anyone starting new azure functions or have the budget and energy to upgrade existing ones, there's now first-class support for configuring the serializer exactly how you like it.
I think System.Text.Json is now the default, but if you want Newtonsoft.Json it looks like there's a way to opt-in to and configure either. I found this example.
Program.cs
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure.Core.Serialization;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Configuration
{
public class Program
{
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(workerApplication =>
{
// Use any of the extension methods in WorkerConfigurationExtensions.
})
.Build();
host.Run();
}
}
internal static class WorkerConfigurationExtensions
{
/// <summary>
/// Calling ConfigureFunctionsWorkerDefaults() configures the Functions Worker to use System.Text.Json for all JSON
/// serialization and sets JsonSerializerOptions.PropertyNameCaseInsensitive = true;
/// This method uses DI to modify the JsonSerializerOptions. Call /api/HttpFunction to see the changes.
/// </summary>
public static IFunctionsWorkerApplicationBuilder ConfigureSystemTextJson(this IFunctionsWorkerApplicationBuilder builder)
{
builder.Services.Configure<JsonSerializerOptions>(jsonSerializerOptions =>
{
jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
jsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
// override the default value
jsonSerializerOptions.PropertyNameCaseInsensitive = false;
});
return builder;
}
/// <summary>
/// The functions worker uses the Azure SDK's ObjectSerializer to abstract away all JSON serialization. This allows you to
/// swap out the default System.Text.Json implementation for the Newtonsoft.Json implementation.
/// To do so, add the Microsoft.Azure.Core.NewtonsoftJson nuget package and then update the WorkerOptions.Serializer property.
/// This method updates the Serializer to use Newtonsoft.Json. Call /api/HttpFunction to see the changes.
/// </summary>
public static IFunctionsWorkerApplicationBuilder UseNewtonsoftJson(this IFunctionsWorkerApplicationBuilder builder)
{
builder.Services.Configure<WorkerOptions>(workerOptions =>
{
var settings = NewtonsoftJsonObjectSerializer.CreateJsonSerializerSettings();
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
settings.NullValueHandling = NullValueHandling.Ignore;
workerOptions.Serializer = new NewtonsoftJsonObjectSerializer(settings);
});
return builder;
}
}
}
@tyson-benson Came from Google trying to figure out how to camel case my JSON deserialization for the new .NET 5 isolated process functions. Thank you!
So for .Net 6 will this functionality be available for all c# functions or only the isolated process functions?
Is there any news on this ? Now that .NET 6 us supported and the v4 is out
In my recent adventures with this for .NET 6/Function v4 is that it is still case that you can only configure for STJ if running in an isolated process.
Personally, I would like a big hammer that says "AZURE_FUNCTION_PLEASE_CONFIGURE_FOR_STJ_I_AM_AWARE_OF_THE_ISSUES_WITH_JOBJECT_BINDINGS=True".
'Migrate to System.Text.Json' say all the Microsoft Docs since 2019, yet here we are in March 2023 and Azure Functions doesn't support it.
Guys, I get it right or I'm missing something ?
HttpClient uses STJ and Azure Function HttpTrigger response uses Newtonsoft ?
We ran into the similar case with Function v4, .net 6, & in-process model. (first of all, I'm not a native English speaker... sorry about poor English.)
[FunctionName("DataService")]
public static async Task<IActionResult> DataService(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "dataservice/{action:alpha}")] HttpRequest req, string action)
{
IActionResult result = await req.Dispatch(action, DataOptions);
return result;
}
Our Dispatch
method returns various result such as NotFoundResult
, BadRequestResult
, or JsonResult
. The Dispatch
must respone dynamically by various clients. For example, for a C# client, Dispatch
method returns a JsonResult
, such as it include a JsonSerializerOptions
which is set to serialize our DTO with rich type information. On the other hand, for a Javascript client, it will returns a JsonResult
without JsonSerializerOptions
so that Javascript could consume JSON more nativly.
It works very well in ASP.NET Core environment. Howerver, Azure function throws an exception when a JsonResult
has a JsonSerializerOptions
.
Microsoft.AspNetCore.Mvc.NewtonsoftJson: Property 'JsonResult.SerializerSettings' must be an instance of type 'Newtonsoft.Json.JsonSerializerSettings'.
To workaround, we add an extra helper method.
private static IActionResult ProcessJsonResult(this IActionResult result)
{
if (result is JsonResult jsonResult)
{
object resultValue = jsonResult.Value;
JsonSerializerOptions options = jsonResult.SerializerSettings as JsonSerializerOptions;
ContentResult contentResult = new()
{
StatusCode = jsonResult.StatusCode,
ContentType = "application/json",
Content = JsonSerializer.Serialize(resultValue, options)
};
result = contentResult;
}
return result;
}
[FunctionName("DataService")]
public static async Task<IActionResult> DataService(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "dataservice/{action:alpha}")] HttpRequest req, string action)
{
IActionResult result = await req.Dispatch(action, DataOptions);
return result.ProcessJsonResult();
}
It works... However, we think that the helper method takes more memory(not a stream!) and is not asynchronous. We had migrated from NJ to STJ since STJ takes less memory and is more faster than NJ!