Skip to content

JsonDeserializationDynamicObject: retarget net10.0, drop dead packages, add return-type tests - #2159

Open
vladimir-pecanac-main wants to merge 1 commit into
CodeMazeBlog:mainfrom
vladimir-pecanac-main:seo/68350-dynamic-object
Open

JsonDeserializationDynamicObject: retarget net10.0, drop dead packages, add return-type tests#2159
vladimir-pecanac-main wants to merge 1 commit into
CodeMazeBlog:mainfrom
vladimir-pecanac-main:seo/68350-dynamic-object

Conversation

@vladimir-pecanac-main

Copy link
Copy Markdown
Collaborator

Updates the json-csharp/JsonDeserializationDynamicObject sample for the .NET 10 republish of How to Deserialize JSON Into Dynamic Object in C#.

Retarget and package lift

All three projects move from net6.0 to net10.0. Newtonsoft.Json 13.0.1 to 13.0.4, Microsoft.NET.Test.Sdk 16.11.0 to 18.9.0, xunit 2.4.1 to 2.9.3, xunit.runner.visualstudio 2.4.3 to 4.0.0, coverlet.collector 3.1.0 to 10.0.1, BenchmarkDotNet 0.13.1 to 0.15.8.

Three package references removed

  • System.Text.Json 6.0.2, from all three projects. It is in-box from the shared framework on net10.0, and pinning 6.0.2 on a net10.0 TFM downgrades the in-box assembly. Verified: the solution builds and every test passes with no System.Text.Json package reference at all.
  • Microsoft.AspNetCore.Mvc.Testing 6.0.2, from the test project. The solution has no web host and no WebApplicationFactory; the reference is inherited from a template and dragged an ASP.NET Core dependency into a pure-serialization sample.
  • BenchmarkDotNet.Diagnostics.Windows, from the benchmark project. Unused, and Windows-only in a solution CI builds on Linux.

Benchmark methodology fix

NewtonsoftJsonAnonymousType called UsingAnonymousTypeWithDictionary, which deserializes Rating into a Dictionary<string, double> and reads two values out of it. The native benchmark it is compared against, SystemTextJsonAnonymousType, deserializes Rating into a nested anonymous type and reads one value. The two were not measuring the same operation, and the published article presents them side by side as if they were. NewtonsoftJsonAnonymousType now calls UsingAnonymousType, so both do the same work.

Worth noting for anyone reading the article's table: the fix does not flip that row. Newtonsoft's anonymous type is still faster than the native one on this workload (1.57x the baseline against 1.73x) now that the comparison is like for like, so the result is real rather than an artefact of the mismatch.

New ReturnTypeTests

Asserts what each API actually returns for a JSON object, array, string, number, boolean and the null literal. The interesting cases, all measured on this branch:

  • JsonConvert.DeserializeObject<dynamic> returns CLR primitives for scalars, string / double / bool, not JValue.
  • JsonConvert.DeserializeObject<dynamic>("null") returns a real null reference, while JsonConvert.DeserializeObject<JToken>("null") returns a JValue of JTokenType.Null. Declaring the result as JToken is a different contract, not a free one.
  • JsonSerializer.Deserialize<dynamic> always returns a boxed JsonElement, discriminated by ValueKind, except for the null literal.

These back a lookup table the article gains, so the type names are enforced by CI instead of stated in prose.

One existing test tightened

NativeJsonUnitTest asserted Assert.ThrowsAny<Exception> for member access on the boxed JsonElement. It is specifically Microsoft.CSharp.RuntimeBinder.RuntimeBinderException, so the test now says so.

Build and tests: Release, 0 warnings, 0 errors, 33/33 passing on SDK 10.0.302 / runtime 10.0.10.

…urn-type tests

Retarget all three projects from net6.0 to net10.0 and move the packages
forward: Newtonsoft.Json 13.0.4, Microsoft.NET.Test.Sdk 18.9.0, xunit 2.9.3,
xunit.runner.visualstudio 4.0.0, coverlet.collector 10.0.1, BenchmarkDotNet
0.15.8.

Drop three dead package references:

- System.Text.Json 6.0.2 in all three projects. It is in-box on net10.0 and
  pinning 6.0.2 there downgrades the shared-framework assembly.
- Microsoft.AspNetCore.Mvc.Testing in the test project. The solution has no
  web host and no WebApplicationFactory.
- BenchmarkDotNet.Diagnostics.Windows in the benchmark project. Unused, and
  Windows-only in a solution CI builds on Linux.

Fix a benchmark that was not comparing like with like. NewtonsoftJsonAnonymousType
called UsingAnonymousTypeWithDictionary, which reads two values out of a
Dictionary<string, double>, while SystemTextJsonAnonymousType reads one value out
of a nested anonymous type. It now calls UsingAnonymousType, so both benchmarks do
the same work.

Add ReturnTypeTests, asserting what each API actually returns for an object, an
array, a string, a number, a boolean and the null literal. The interesting case:
DeserializeObject<dynamic> gives CLR primitives for scalars (string, double,
bool), not JValue, and a real null reference for the null literal, while
DeserializeObject<JToken> gives JValue for all of them.

Tighten the existing native dynamic test from Assert.ThrowsAny<Exception> to
Assert.Throws<RuntimeBinderException>, which is the exception member access on a
boxed JsonElement actually raises.
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.

1 participant