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

[JsonSerializer] Relax restrictions on ctor param type to immutable property type matching where reasonable #44428

Open
Tracked by #71944 ...
JSkimming opened this issue Nov 9, 2020 · 54 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Text.Json enhancement Product code improvement that does NOT require public API changes/additions Team:Libraries
Milestone

Comments

@JSkimming
Copy link

JSkimming commented Nov 9, 2020

Description

I'm trying to deserialise an object from a json string. In this case the constructor allows a nullable value type as the parameter, setting a non-null property (defaulting if null).

I expect (since it is the behaviour with Newtonsoft.Json) the serializer to handle compatible constructor and bound value type properties (or fields) where the only difference is one is nullable.

The following demonstrates the issue:

public class Example
{
    public Example(Guid? aGuid) => AGuid = aGuid ?? Guid.Empty;
    public Guid AGuid { get; }
}

Example original = new Example(Guid.NewGuid());

// Works with Newtonsoft.Json.
string withJsonNet = JsonConvert.SerializeObject(original);
JsonConvert.DeserializeObject<Example>(withJsonNet);

// Fails with System.Text.Json.
string withSystemTextJson = System.Text.Json.JsonSerializer.Serialize(original);
System.Text.Json.JsonSerializer.Deserialize<Example>(withSystemTextJson);

An InvalidOperationException is thrown from the method System.Text.Json.JsonSerializer.Deserialize :

System.InvalidOperationException: Each parameter in constructor 'Void .ctor(System.Nullable`1[System.Guid])' on type 'Example' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive.

Configuration

I'm building an ASP.NET Core app targeting netcoreapp3.1, and using version 5.0.0-rc.2.20475.5 of System.Text.Json. I'm also using version 12.0.3 of Newtonsoft.Json.

Other information

Stack Trace:

System.InvalidOperationException: Each parameter in constructor 'Void .ctor(System.Nullable`1[System.Guid])' on type 'Example' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(ConstructorInfo constructorInfo, Type parentType)
   at System.Text.Json.Serialization.Converters.ObjectWithParameterizedConstructorConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[TValue](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[TValue](Utf8JsonReader& reader, Type returnType, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, Type returnType, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at <custom code>
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Nov 9, 2020
@layomia
Copy link
Contributor

layomia commented Nov 12, 2020

Why make the constructor parameter nullable? The AGuid property is non-nullable which means that null will never be serialized. Are you expecting null JSON tokens to bind with the parameter on deserialization? Where would this JSON come from?

@layomia layomia removed the untriaged New issue has not been triaged by the area owner label Nov 12, 2020
@layomia layomia added this to the Future milestone Nov 12, 2020
@JSkimming
Copy link
Author

JSkimming commented Nov 13, 2020

Thanks for the reply @layomia

To present a minimal use-case the example is intentionally trivial.

My situation is rather more complex. We have several scenarios where having the parameter as nullable was desirable (and worked well using Newtonsoft.Json), some of which we can, and have, resolved by making the parameter non-nullable.

But there are other scenarios where a nullable parameter with a non-nullable property is still preferred. One being where we have an unknown number of legacy objects that are serialised to a store with null values. The use-cases have since been updated where new instances are not serialised with null, but we still want to support the de-serialization of the legacy objects.

If we had been using System.Text.Json at the time, we probably would have implemented the solution differently, but as I highlighted above Newtonsoft.Json worked.

Hope the context helps.

Ultimately though, this is a difference in behaviour with Newtonsoft.Json. It looks like you are trying to resolve edge cases in to make System.Text.Json "the standard JSON stack for .NET." (See Issue #43620). And this is one such edge case.

@layomia
Copy link
Contributor

layomia commented Jan 25, 2021

From @https://github.com/NN--- in #46480:

Description

    public class Q
    {
        [JsonConstructor]
        public Q(int? x)
        {
            if (x is null) throw new Exception();
            X = x.Value;
        }
        
        public int X { get; }
    }

Given string:

var str = JsonSerializer.Deserialize<Q>("{\"X\":123}" ");

It is deserialized fine with Newtonsoft, but not with System.Text.
The reason is that constructor parameter type is int? while the property has type of int.
Newtonsoft doesn't validate it, giving an option to do anything in the constructor.

There are possible workarounds but they are not as simple as the original code:

Make property private and add a public non nullable:

    public class Q
    {
        [JsonConstructor]
        public Q(int? x)
        {
            if (x is null) throw new Exception();
            X = x.Value;
        }

        public int? X { get; }

        [JsonIgnore]
        public int NonNullableX => X!.Value;
    }

Annotate nullable as non nullable, however it requires explicit call to Value.:

    public class Q
    {
        [JsonConstructor]
        public Q(int? x)
        {
            if (x is null) throw new Exception();
            X = x.Value;
        }

        [DisallowNull][NotNull]
        public int? X { get; }
    }

Configuration

Regression?

Not a regression.

Other information

@layomia
Copy link
Contributor

layomia commented Jan 25, 2021

We could look into relaxing the matching algorithm here if it proves to be unwieldy. The workaround here is to refactor the POCO code slightly to make the property type and the constructor parameter type the same. This issue will not be treated as high priority until a blocked and non-trivial scenario is provided.

@GabeDeBacker
Copy link

Deserializing JSON into an object that can be bound to WPF\XAML is likely very common place and converting an incoming IEnumerable into a observable collection that XAML can use is also likely common.

Not supporting this limits System.Text.Json's use with an XAML\WPF\UWP apps.

@JSkimming
Copy link
Author

JSkimming commented Jan 25, 2021

This issue will not be treated as high priority until a blocked and non-trivial scenario is provided.

@layomia As I explained, the example is trivial to present a minimal use-case. I also went on to explain the non-trivial scenario:

we have an unknown number of legacy objects that are serialised to a store with null values. The use-cases have since been updated where new instances are not serialised with null, but we still want to support the de-serialization of the legacy objects.

We are also blocked. Our workaround is to stick with Newtonsoft.Json.

@layomia
Copy link
Contributor

layomia commented Jan 25, 2021

Thanks for the responses and expanding on the importance here. There are definitely various scenarios where loosening the matching restrictions is helpful. We can address this in the .NET 6.0 release.

@layomia layomia self-assigned this Jan 25, 2021
@layomia layomia modified the milestones: Future, 6.0.0 Jan 25, 2021
@layomia layomia added the enhancement Product code improvement that does NOT require public API changes/additions label Jan 25, 2021
@layomia
Copy link
Contributor

layomia commented Jan 26, 2021

From @GabeDeBacker in #47422:

Description

System.Text.Json deserialization requires that a property type match the constructor type for immutable properties even though the constructor can convert the type.

This is a simple example of a class that will convert an incoming IEnumerable to a ReadOnlyObservableCollection for XAML binding.

        [JsonConstructor]
        public Logger(IEnumerable<LogEntry> entries)
        {
            this.Entries = new ReadOnlyObservableCollection<LogEntry>(this.entries);
        }

        public ReadOnlyObservableCollection<LogEntry> Entries { get; }

When desrializing from JSON, this fails.

Changing the property to be IEnumerable allows the deserialization to succeed, but that means I would need to add “another” property to this class for XAML binding to work. (Which is what this class is used for). The below just doesn’t seem right and was not something I had to do when using NewtonSoft

        public Logger(IEnumerable<LogEntry> entries)
        {
            this.Entries = entries;
            this.ObersvableEntries = new ReadOnlyObservableCollection<LogEntry>(this.entries);
        }

        public IEnumerable<LogEntry> Entries { get; }

        [JsonIgnore]
        public ReadOnlyObservableCollection<LogEntry> ObersvableEntries { get; }

@layomia layomia changed the title System.Text.Json fails to deserialise nullable value type through constructor if bound property is non-nullable [JsonSerializer] Relax restrictions on ctor param type to immutable property type matching where reasonable Jan 26, 2021
@layomia
Copy link
Contributor

layomia commented Jan 26, 2021

Today, we expect an exact match between the constructor parameter type and the immutable property type. This is too restrictive in two major cases:

We can loosen the restriction and support these scenarios by checking that the ctor parameter is assignable from the immutable property type. This new algorithm is in accordance with the serializer always round-tripping (i.e being able to deserialize whatever we serialize), and maintains a high probability that the incoming JSON is compatible with the target ctor param. This is important to avoid unintentional data loss.

If there are more reasonable scenarios that will not be satisfied with this proposal, we can evaluate them and perhaps adjust further.

@layomia
Copy link
Contributor

layomia commented Jan 26, 2021

We can also, or alternatively, consider a property on JsonConstructorAttribute, that indicates no restriction between the ctor parameter type and the immutable property type. This would allow them to be two arbitrarily different types, basically an "I know what I'm doing mode". It would still be required for their CLR names to match.

/// <summary>
/// When placed on a constructor, indicates that the constructor should be used to create
/// instances of the type on deserialization.
/// </summary>
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false)]
public sealed class JsonConstructorAttribute : JsonAttribute
{
    /// <summary>
    /// When <see cref="true" />, indicates that no restriction should be placed on the types of a constructor
    /// parameter and a property when there is a case-insensitive match between their names.
    /// </summary>
    public bool UseRelaxedPropertyMatching { get; set; }

    /// <summary>
    /// Initializes a new instance of <see cref="JsonConstructorAttribute"/>.
    /// </summary>
    public JsonConstructorAttribute() { }
}

A global option can be considered as well, to support non-owned types where we can't decorate with an attribute:

public sealed class JsonSerializerOptions
{
    /// <summary>
    /// When <see cref="true" />, indicates that no restriction should be placed on the types of a constructor
    /// parameter and a property when there is a case-insensitive match between their names.
    /// </summary>
    public bool ConstructorUseRelaxedPropertyMatching { get; set; }
}

All of this design assumes that there will always be a requirement that every constructor parameter binds to an object property, per the original spec for this feature: #33095.

@GabeDeBacker
Copy link

@layomia - Implementing either (or both) of what you mentioned above (the JsonConstructorAttribute argument or is the constructor argument assignable from the property type) would be great additions! Thanks for the conversation

@layomia
Copy link
Contributor

layomia commented Jan 26, 2021

We can also, or alternatively, consider a property on JsonConstructorAttribute, that indicates no restriction between the ctor parameter type and the immutable property type. This would allow them to be two arbitrarily different types, basically an "I know what I'm doing mode". It would still be required for their CLR names to match.

We would likely still need to support the "mapping nullable value-type ctor args to immutable properties of a non-nullable version of the type" case by default - #44428 (comment).

@layomia layomia added this to Targeting preview 2 - 03/11 in System.Text.Json - 6.0 Jan 26, 2021
@layomia layomia removed this from Targeting preview 2 - 03/11 in System.Text.Json - 6.0 Jan 27, 2021
@layomia
Copy link
Contributor

layomia commented Jan 27, 2021

Co-assigning @GabeDeBacker to provide the implementation for this feature, as discussed offline.

@GabeDeBacker
Copy link

I did find an example of where a property type is not assignable from the constructor argument type.
You cannot construct a SecureString from a string.

public class ClassThatStoresSecureStrings
{
    using System;
    using System.Security;

   public ClassThatStoresSecureStrings(string userId, string password)
   {
            // This code repeats for password.
            this.UserId = new SecureString();
            foreach (var ch in userId)
            {
                this.UserId.AppendChar(ch);
            }

            this.UserId.MakeReadOnly();
   }

   public SecureString UserId { get; }
   public SecureString Password {get; }
}

@eiriktsarpalis
Copy link
Member

One possible solution might be to expose a flag on either JsonSerializerOptions or JsonConstructorAttribute that completely disables matching constructor parameters to property types, roughly:

namespace System.Text.Json;

public class JsonSerializerOptions
{
     public bool RequireMatchingPropertyForConstructorParamers { get; set; } = true;
}

public class JsonConstructorAttribute
{
     public bool RequireMatchingPropertyForConstructorParamers { get; set; } = true;
}

which would make the following POCOs legal from a serialization perspective:

JsonSerializer.Deserialize<MyPoco>("{ ... }"); // succeeds deserialization

public class MyPoco
{
    [JsonConstructor(RequireMatchingPropertyForConstructorParamers = true)]
    public MyPoco(int p1, int p2, int p3) { }
}

@raffaeler
Copy link

@eiriktsarpalis
The JsonSerializerOptions is the only possible solution as the JsonConstructor is definitely broken (not be available for default ctors in C# records).
The only possible alternative would be a new attribute on the type rather than on the ctor.

@layomia
Copy link
Contributor

layomia commented Dec 2, 2022

[Triage] This work should include a fix for #56999. Repro below. Also see #56999 (comment).

Description

I would expect the below to work, however it does not. Maybe because covariant return types are new and not yet handled in System.Text.Json yet. I think the example speaks for itself but let me know if more information is required.

Configuration

  • Which version of .NET is the code running on? .NET 5.0
  • What is the architecture (x64, x86, ARM, ARM64)? x64
  • Do you know whether it is specific to that configuration? It is not.

Non-working Example

Using covariant return type read-only properties passed through derived constructor does not work.

https://dotnetfiddle.net/IsNX3q

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
					
public class Program
{
	public static void Main()
	{
		var json = "{ \"prop\": { \"id\": \"abc\", \"num\": 2 } }";
		var obj = JsonSerializer.Deserialize<DerivedClass>(json);
		Console.WriteLine(obj?.Property?.Id ?? "null");
		Console.WriteLine(obj?.Property?.Number?.ToString() ?? "null");
	}
	
	public class BaseClass
	{
		public BaseClass(BaseProperty property)
		{
			Property = property;
		}

		[JsonPropertyName("prop")]
		public virtual BaseProperty Property { get; }
	}
	
	public class DerivedClass : BaseClass
	{
		public DerivedClass(DerivedProperty property)
			: base(property)
		{
		}

		public override DerivedProperty Property { get; }
	}
	
	public class BaseProperty
	{
		[JsonPropertyName("id")]
		public string Id { get; set; }
	}
	
	public class DerivedProperty : BaseProperty
	{
		[JsonPropertyName("num")]
		public int? Number { get; set; }
	}
}

Output:

null
null

Working Example

Using non-covariant return type read-only properties passed through derived constructor does work:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
					
public class Program
{
	public static void Main()
	{
		var json = "{ \"prop\": { \"id\": \"abc\", \"num\": 2 } }";
		var obj = JsonSerializer.Deserialize<DerivedClass>(json);
		Console.WriteLine(obj?.Property?.Id ?? "null");
	}
	
	public class BaseClass
	{
		public BaseClass(BaseProperty property)
		{
			Property = property;
		}

		[JsonPropertyName("prop")]
		public virtual BaseProperty Property { get; }
	}
	
	public class DerivedClass : BaseClass
	{
		public DerivedClass(BaseProperty property)
			: base(property)
		{
		}
	}
	
	public class BaseProperty
	{
		[JsonPropertyName("id")]
		public string Id { get; set; }
	}
}

Output:

abc

@mathieubergouniouxcab
Copy link

Something that no one mentioned : If the error message could at least indicate what parameter caused the issue then that would be the bare minimum.

This question is asked countless times in StackOverflow, it's a huge waste of time for many devs, who have to go through each parameter one by one like cavemen, and wonder : "is it the spelling? Is it that it is nullable?", etc.

@eiriktsarpalis
Copy link
Member

Hi @mathieubergouniouxcab would you be interested in contributing a PR that improves the error message?

@eiriktsarpalis eiriktsarpalis removed the Priority:2 Work that is important, but not critical for the release label Jun 22, 2023
@mathieubergouniouxcab
Copy link

Hi @mathieubergouniouxcab would you be interested in contributing a PR that improves the error message?

Hi Eirik,
I'm perplexed by your answer. I thought System.Text.Json was funded by Microsoft?

@eiriktsarpalis
Copy link
Member

It is also an open source project that happily accepts community contributions. The team can only deliver a limited amount of features and fixes per release -- this particular issue is not slated for .NET 8 hence my suggestion.

@fabio-s-franco
Copy link

fabio-s-franco commented Jun 23, 2023

Of course not, why would it be? It is only a huge waste of time for a lot of people and people have been struggling with it for at least three years only.

I have personally witnessed a fair amount of people go back to newtonsoft and miss all the other good things about this api because they did not want to put up with this anymore.

I wish I had more time to go down the rabbit hole as I consider it personal and would gladly submit a PR as By far the aspect that caused more waste of my time I have ever encountered. But since I really don't have enough time to work on this one, all it's left for now is hope.

And with that hope that I soon get some time to spend on side projects.

@douglasg14b
Copy link

douglasg14b commented Jun 24, 2023

I have personally witnessed a fair amount of people go back to newtonsoft and miss all the other good things about this api because they did not want to put up with this anymore.

This is the norm it seems. When System.Text.Json doesn't have sane defaults, global configuration, extremely poor drilling ergonomics, explicitly doesn't follow the greedy pattern of Json, and fails to address fundamental QoL issues that Newtonsoft has had for ages. It's just not a good fit. It should "Just Work", but it doesn't, not even close. And despite community complaints & requests, feature owners just keep on push back on making it actually nice to use because it needs to be "C#'y"

I keep trying out System.Text.Json every.single.relaease, year over year. And the same fundamental ergonomic & usability failures just keep on keeping on.

This is particularly egregious when I had a new C# dev (C++ & TS dev before) approach me yesterday and say that he really doesn't like or want to continue working with C#. His experience with System.Text.Json set the whole tone & stage for what he thinks C# is. While that is a bit premature, it's not exactly uncommon for new devs to have a terrible experience with some native support and then just write off the ecosystem entirely.

It's bad for image, it's bad for adoption, and it's bad for ergonomics. And there is active pressure to keep it that way, to explicitly keep it's surface area awkward and full of 'gotchas'. It should not be a default recommendation. It's great for advanced & internal use, don't get me wrong, but for your Java/Pythos/JS/...etc devs poking at C# to see if they like it, it's a turnoff.

Pushing people to make FOSS contributions to something that has user-facing design issues, that are explicit isn't really solution. Even moreso when one sees the graveyard of pushed-back feature requests for foundational concerns. That's gotta start at the top.

I love C#/.Net. Which is why I'm so passionate about this, it's frustrating.

/rant 😒

@mathieubergouniouxcab
Copy link

mathieubergouniouxcab commented Jun 26, 2023

well @eiriktsarpalis I'm not going to enter the polemics, but I guess the bottled up frustration in the messages above is a good hint that maybe the logging part should be re-prioritized after 3 years (this thread started in Nov 2020) 😄

@KrzysztofBranicki
Copy link

Hi, are there any plans to address problems mentioned earlier in .NET 8.0? As I described in this issue #55318 which was closed and linked here two years ago, this serializer is not very useful for scenarios when you want to use it for serializing Aggregates modeled using Domain Driven Design principles and store them in document database. That's because it can't handled encapsulation (which is quite important in DDD :)). I would understand that the goal of creating this serializer in the first place was to create fast general purpose JSON serializer for .NET and not something that is only for serializing simple DTOs in web API endpoints.

@eiriktsarpalis
Copy link
Member

This is the norm it seems. When System.Text.Json doesn't have sane defaults, global configuration, extremely poor drilling ergonomics, explicitly doesn't follow the greedy pattern of Json, and fails to address fundamental QoL issues that Newtonsoft has had for ages. It's just not a good fit. It should "Just Work", but it doesn't, not even close. And despite community complaints & requests, feature owners just keep on push back on making it actually nice to use because it needs to be "C#'y"

@douglasg14b it would help if you could open issues that specifically break down the issues you are facing and we might even be able to offer recommendations, workarounds or even prioritize relevant improvements.

I'm not going to enter the polemics, but I guess the bottled up frustration in the messages above is a good hint that maybe the logging part should be re-prioritized after 3 years (this thread started in Nov 2020)

@mathieubergouniouxcab the System.Text.Json backlog currently consists of 210 open issues which we want to address at some point. While I understand the frustration of being impacted by one specific issue, we generally do try to prioritize based on impact and popularity and this issue has been cut from .NET 8.

@mathieubergouniouxcab
Copy link

the System.Text.Json backlog currently consists of [210 open issues]. We prioritize them based on impact and popularity

Understood. I haven't found one specifically dealing with logging which parameter is causing a deserialization fail. Could you confirm that such entry does not exist? If so, then I will create it -- in order for it to have its own thread and its own voting system (and stop polluting this one).

@eiriktsarpalis
Copy link
Member

AFAIK you're the first to bring up that concern. I think it's reasonable to factor into a separate issue since it's of much smaller scope.

@mathieubergouniouxcab
Copy link

mathieubergouniouxcab commented Jun 26, 2023

AFAIK you're the first to bring up that concern. I think it's reasonable to factor into a separate issue since it's of much smaller scope.

I found 8+ entries in your bug tracker related to the error "Each parameter in constructor (...) must bind to a field on deserialization". Devs so obsessed with grinding the issues one by one that they forgot it's 1,000 different flavours of the same issue. https://i.imgur.com/B0St5fv.png

@eiriktsarpalis
Copy link
Member

Care to share the issues that specifically requests this concern?

Something that no one mentioned : If the error message could at least indicate what parameter caused the issue then that would be the bare minimum.

@mathieubergouniouxcab
Copy link

mathieubergouniouxcab commented Jun 26, 2023

Care to share the issues that specifically requests this concern?

The google query I used :

site:https://github.com/dotnet/runtime/issues/ Each parameter in constructor must bind to an object property or field on deserialization

I only clicked on the 8 first ones but there's 20+.
Here are some metrics to evaluate the impact :

  • Most of them complain that the typing is too strict (e.g. fails to recognize that a List can be deserialized as a IEnumerable )
  • very few mention issues witht he parameter's name. But imho it's a classic case of survivor bias, as in : we see only the "valid" issues that made it to the tracker. We don't see the stupid ones, e.g. typos in parameters names, even though they took just as long to spot and fix.

@eiriktsarpalis
Copy link
Member

What is the point you are trying to make? Most of the issues in that query are closed which means that either the error string appears tangentially or the issue has been closed as duplicate of the very issue we are discussing in right now.

@mathieubergouniouxcab
Copy link

mathieubergouniouxcab commented Jun 26, 2023

Precisely. They are duplicates. Which means the same issue keeps coming back like a boomerang because users are not sure if it's by design and report it as a bug, when a clearer message would help.

So anyways, here's the issue created :
#88048

@eiriktsarpalis
Copy link
Member

Precisely. They are duplicates. Which means the same issue keeps coming back like a boomerang because devs are not sure if it's by design, when a clearer message would help.

What exactly are you proposing? That we stop closing duplicates so that it somehow makes the issue more pronounced or discoverable? That never works in practice. When we close a duplicate we link to the original issue and encourage folks to upvote and contribute there.

@mathieubergouniouxcab
Copy link

mathieubergouniouxcab commented Jun 26, 2023

What exactly are you proposing? That we stop closing duplicates ?

No, I'm not suggesting anything. I think a misunderstanding happened at some point in the recent messages. Let's move on: I'm done with this topic, I'm interested only in the error message, as you saw in #88048 . I'll stop polluting this thread now. Thanks for the time and efforts! <3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Text.Json enhancement Product code improvement that does NOT require public API changes/additions Team:Libraries
Projects
None yet
Development

No branches or pull requests