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

System.Text.Json support #1685

Merged
merged 23 commits into from
Feb 15, 2021
Merged

System.Text.Json support #1685

merged 23 commits into from
Feb 15, 2021

Conversation

oskardudycz
Copy link
Collaborator

@oskardudycz oskardudycz commented Dec 27, 2020

Added

  • System.Text.Json support,
  • test matrix configuration for different serializer,
  • updated ISerializer public API to allow passing Stream and DBDataReader

@oskardudycz oskardudycz force-pushed the features/system-text-json branch 2 times, most recently from 3b7839a to 9944183 Compare January 10, 2021 09:10
@oskardudycz oskardudycz force-pushed the features/system-text-json branch 2 times, most recently from 6a8a4ad to 523331a Compare January 23, 2021 20:50
public string StringField;

public double Double { get; set; }
public decimal Decimal { get; set; }
public DateTime Date { get; set; }
public DateTimeOffset DateOffset { get; set; }

public float Float;
public float Float { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's purposely a mix of properties and public fields to ensure that Marten works with both. If we do this, we just need to make sure there are tests specifically for Newtonsoft against public fields. So, ability to use a field as an identity and Linq querying.

Annoying, I know.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Text.Json requires explicit attribute on fields. I reverted the change and added attribute with a comment informing that's only needed for it.

@@ -74,7 +75,7 @@ public IGrammar QuestPartyShouldBe()
}

[FormatAs("Members should be {Members}")]
public string[] Members()
public List<string> Members()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you have to do this? This wouldn't be serialized anywhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done to align other changes, but it's reverted.

@@ -13,150 +13,88 @@ public class Root
public interface IMonstersView
{
Guid Id { get; }
string[] Monsters { get; }
List<string> Monsters { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STJ doesn't support arrays?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't supporting it properly when behind the Array was another type. It's fixed in the System.Text.Json 5, so I reverted the change here with the dependency added.

}

public object FromJson(Type type, TextReader reader)
public Task<T> FromJsonAsync<T>(Stream stream, CancellationToken cancellationToken = default)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should change the signature of this method to return a ValueTask

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used ValueTask in 1ef5e80.


public Task<object> FromJsonAsync(Type type, Stream stream, CancellationToken cancellationToken = default)
{
return Task.FromResult(FromJson(type, stream));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably be using a ValueTask

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used ValueTask in 1ef5e80.

return _serializer.FromJson<T>(json);
}

public Task<T> ResolveAsync(DbDataReader reader, CancellationToken token)
public async Task<T> ResolveAsync(DbDataReader reader, CancellationToken token)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And let's look to see about using ValueTask. Might also want the actual reading from the Stream to be async. We should look at how the ASP.net JsonFormatter uses STJ and see if it buffers data from the Stream or anything like that. Not saying this is necessarily wrong, but we should do our homework.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's the right one - then it looks like is doing that the same way as we do: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs#L77 (plus they're doing additional serialisation from object that we don't want to do).

Copy link
Collaborator Author

@oskardudycz oskardudycz Feb 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we should add method ToStream and ToStreamAsync to IQueryable etc. Plus some custom output formatter that'll just return stream with proper encoding and response content type.

builder.Write(',');
builder.Write(text.ReadToEnd());
builder.Write(text.GetStreamReader().ReadToEnd());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's reevaluate this one to see if there's a way to not have to read and create the string. See if there's a way to pipe the Stream bytes directly to the CommandBuilder

@@ -31,6 +31,7 @@
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.11.1" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="[2.1.0, 6.0.0]" />
<PackageReference Include="System.Text.Json" Version="5.0.0" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll make this an add on lib, right? Not be in the box?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This we need to discuss - in .NET 5 it's already built-in. I added NuGet, as built-in version in .NET Core 3.1 is too old and missing features. We can distribute that as separate package, but then maintainance might be harder as we'll have growing start of packages (NodaTime, Console, etc.).

@@ -29,7 +29,7 @@ public static QueryPlan ExplainQuery(this IManagedConnection runner, NpgsqlComma
cmd.CommandText = string.Concat($"explain ({config} format json) ", cmd.CommandText);
using (var reader = runner.ExecuteReader(cmd))
{
var queryPlans = reader.Read() ? serializer.FromJson<QueryPlanContainer[]>(reader.GetTextReader(0)) : null;
var queryPlans = reader.Read() ? serializer.FromJson<QueryPlanContainer[]>(reader.GetStream(0)) : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing TODO here might be to make the ISerializer interface take in the DbDataReader and index, then let each serializer to whatever they need to do. So you don't have to make the serializers conform to something that isn't idea.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored in be80fc6.



private readonly JsonSerializer _clean = new JsonSerializer
private readonly JsonSerializer _clean = new()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we use the containment operator anymore, so this might no longer be needed. Not 100% sure about that though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to use ToJson instead of ToCleanJson but there were set of failing tests around querying interface types collection.

}

public string ToJson(object document)
{
var writer = new StringWriter();
ToJson(document, writer);
using var stream = new MemoryStream();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we having to write to a stream like this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted those changes and brought back old implementation after changing method signature with DBReader etc.

};

return _serializer.Deserialize<T>(jsonReader);
}

public T FromJson<T>(TextReader reader)
public Task<T> FromJsonAsync<T>(Stream stream, CancellationToken cancellationToken = default)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to be using ValueTask.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to ValueTask in 1ef5e80. We should probably review if there are no places in our API that should return ValueTask (however, that's out of scope of this PR).

@jeremydmiller
Copy link
Member

That looks like a ton of work.

With the ISerializer interface, we might be better off having it take in DbDataReader and an index, then letting each serializer do what works best for it, rather than trying to make an ISerializer conforming interface that isn't ideal. And we've gotta switch to ValueTask instead of returning Task.FromResult() in so many places.

@oskardudycz oskardudycz force-pushed the features/system-text-json branch 4 times, most recently from 64965d4 to c598cdc Compare January 31, 2021 20:13
@oskardudycz oskardudycz force-pushed the features/system-text-json branch 3 times, most recently from 0a13912 to a64371b Compare February 12, 2021 20:51
…ility between parser

Added JsonPropertyName to QueryPlan to support also System.Text.Json
Added JsonInclude for the field in `Target` test document class. Changed `Float` from field to property, as tests do not assume that it should be field.
Fixed Casing in System.Text.Json
…th SerializerType

Used it in StoreOptions
Added TestSetup to allow matrix tests on serializers
Added TestsSettings to keep the test settings together
Added SerializerTypeTargetedFact to allow skipping tests when feature is not supported for the particular serializer
Added pipelines for System.Text.Json
…taReader and index params to allow serializers use its own the most pefromant way
Skipped unsupported test scenarios
Added SerializerTypeTargetedTheory to skip theories for System.Text.Json
@oskardudycz oskardudycz self-assigned this Feb 13, 2021
@oskardudycz oskardudycz changed the title WIP: System.Text.Json support System.Text.Json support Feb 13, 2021
Added serializer matrix to AsynDaemon and Schema testing projects
@oskardudycz
Copy link
Collaborator Author

@jeremydmiller @mysticmind PR is ready for the final review.

@oskardudycz oskardudycz marked this pull request as ready for review February 13, 2021 15:42
{
using var jsonReader = new JsonTextReader(reader)
using var textReader = reader.GetTextReader(index);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For later. If we took in the native Npgsql DbDataReader type instead, you get access to async versions of GetTextReader() and GetStream()

Copy link
Member

@jeremydmiller jeremydmiller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My vote is to pull this in now.

My only comment beyond "thank you for doing all of this and I'm shocked at how much work it really required" is that we need to think seriously about either passing around NpgsqlReader instead of DbDataReader, or downcast (ick) as appropriate to use the async GetStreamAsync() everywhere.

Copy link
Member

@mysticmind mysticmind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Apart from all the changes required to make STJ work, I really liked that you have spruced up the code with C# 9 lang features 👍

BTW, my changes pertaining to changing the code samples markers for migrating docs to vitepress looks to have added conflicts in few files, will address it and then merge the PR.

@mysticmind mysticmind merged commit 5d114a1 into master Feb 15, 2021
@mysticmind mysticmind deleted the features/system-text-json branch February 15, 2021 07:15
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

Successfully merging this pull request may close these issues.

4 participants