Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Config to set output serializer to System.Text.Json #5469

Open
shibayan opened this issue Jan 12, 2020 · 21 comments
Open

Add Config to set output serializer to System.Text.Json #5469

shibayan opened this issue Jan 12, 2020 · 21 comments

Comments

@shibayan
Copy link

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

@ankitkumarr
Copy link
Contributor

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 ankitkumarr added this to the Triaged milestone Jan 13, 2020
@shibayan
Copy link
Author

@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.

@brandonh-msft
Copy link
Member

brandonh-msft commented May 5, 2020

@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?

@fabiocav
Copy link
Member

fabiocav commented May 5, 2020

@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?

@pheuter
Copy link

pheuter commented Aug 4, 2020

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

@hansmbakker
Copy link

I'm having an issue with this as well (#5203 (comment)).

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

@ByteDev
Copy link

ByteDev commented Nov 5, 2020

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.

@stevo-knievo
Copy link

I just ran into the same case. Will this get fixed with .NET5 for Azure Functions?

@fabiocav
Copy link
Member

@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.

@stevo-knievo
Copy link

@fabiocav thanks for your reply! I'm looking forward to using .NET5 together with Azure Functions.

@mtin
Copy link

mtin commented Jan 27, 2021

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?

@MLogdberg
Copy link

MLogdberg commented Feb 6, 2021

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" ] } ]

@tyson-benson
Copy link

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

@kamranayub
Copy link

@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!

@snapfisher
Copy link

So for .Net 6 will this functionality be available for all c# functions or only the isolated process functions?

@abouroubi
Copy link

Is there any news on this ? Now that .NET 6 us supported and the v4 is out

@DarinMacRae
Copy link

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".

@jeremy-morren
Copy link

'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.

@CarlosHMoreira
Copy link

Guys, I get it right or I'm missing something ?

HttpClient uses STJ and Azure Function HttpTrigger response uses Newtonsoft ?

@ksyu33
Copy link

ksyu33 commented Jan 5, 2024

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!

@fabiocav fabiocav removed this from the Triaged milestone Jan 23, 2024
@DeltekDavid
Copy link

We recently upgraded to .NET 8, but we have to stick with the in-process model for now. This is blocking us from migrating to System.Text.Json solution-wide. In addition, we use durable functions. Would we need every activity function to also use a workaround similar to @ksyu33 's above? Since durable functions also serializes the object state internally? A real bummer, we had migrated to STJ everywhere else and then started seeing errors in our Http trigger functions (because we'd migrated all the POCOs to use STJ attributes)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests