Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

DictionaryKeyPolicy not applied to keys that contain non primitive ty… #41691

Merged

Conversation

marcnet80
Copy link
Contributor

@maryamariyan
Copy link
Member

maryamariyan commented Nov 6, 2019

Thank you for your contribution. As announced in dotnet/coreclr#27549 this repository will be moving to dotnet/runtime on November 13. If you would like to continue working on this PR after this date, the easiest way to move the change to dotnet/runtime is:

  1. In your corefx repository clone, create patch by running git format-patch origin
  2. In your runtime repository clone, apply the patch by running git apply --directory src/libraries <path to the patch created in step 1>

@marcnet80 marcnet80 requested a review from jozkee November 9, 2019 14:45
@@ -83,7 +84,9 @@ public static partial class JsonSerializer

// An object or another enumerator requires a new stack frame.
state.Push(elementClassInfo, value);
if (options?.DictionaryKeyPolicy != null && state.Current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing)
var extensionDataAttribute = jsonPropertyInfo.PropertyInfo?.GetCustomAttribute(typeof(JsonExtensionDataAttribute));
Copy link
Member

Choose a reason for hiding this comment

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

Is there a specific reason to check the Attribute?
GetCustomAttribute uses reflection which is very expensive. I was thinking that the sole condition of state.Current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing was enough. did you find any issues while just depending on the condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is heavy operation, but we need this check for passing test SerializationWithJsonExtensionDataAttribute_IgoneDictionaryKeyPolicy, I'm debugging this test (with extensionDataAttribute) for non-value class types, they were affected by DictionaryKeyPolicy, state.Current.ExtensionDataStatus had NotStarted value.
Without checking attribute, I've got next:

"{""KeyInt"":1000,""KeyString"":""text"",""KeyBool"":true,""keyObject"":{},""keyList"":[],""keyDictionary"":{}}"

Copy link
Member

Choose a reason for hiding this comment

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

I see the problem.
The reason why you get NotStarted is because, as of now, you are checking after one of the values of the JsonExtensionData dictionary is pushed into the stack in order to be written on the next iteration. The state of said value does not carry the parent dictionary's ExtensionDataStatus and therefore you see it as NotStarted.

To check against the proper ExtensionDataStatus try to move the condition before calling state.Push.


private class TestClassWithDictionary
{
public Dictionary<string, CustomClass> Data { get; set; }
Copy link
Contributor

@layomia layomia Nov 13, 2019

Choose a reason for hiding this comment

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

A test where we serialize a class that has both a dictionary and extension data (both with contents) would be good:

private class TestClassWithDictionaryAndExtensionData
{
    public Dictionary<string, CustomClass> Data { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JsonElement> ExtensionData { 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.

Any special reason to use JsonElement?
In order to pass JsonElement values to the ExtensionData I would need to deserialize into a JsonDocument first, thing that I can avoid by using Dictionary<string, object> instead.

@layomia
Copy link
Contributor

layomia commented Nov 13, 2019

Formatting issues are causing CI failures: https://github.com/dotnet/corefx/pull/41691/checks?check_run_id=300234753

@ahsonkhan ahsonkhan added this to the 5.0 milestone Nov 13, 2019
Copy link
Member

@ahsonkhan ahsonkhan left a comment

Choose a reason for hiding this comment

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

Other than some test nits/suggestions, and @jozkee's feedback, looks good to me.

DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
});

Assert.Equal(JsonCamel, json);
Copy link
Member

Choose a reason for hiding this comment

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

For completeness, can we add another Assert to all these tests where we use both camel case for DictionaryKeyPolicy and PropertyNamingPolicy, just to make sure there isn't some unexpected interaction causing an issue?

So:

json = JsonSerializer.Serialize(obj, new JsonSerializerOptions()
{
    DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});

const string JsonCamel = @"{""keyDict"":{""name"":""text"",""number"":1000,""isValid"":true,""values"":[1,2,3]}}";

Assert.Equal(JsonCamel, json);

Copy link
Member

Choose a reason for hiding this comment

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

And then consider renaming the original JsonCamel to something like JsonDictionaryCamel to differentiate between the two cases.

{
{ "KeyDict", CreateCustomObject() }
};
var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions()
Copy link
Member

Choose a reason for hiding this comment

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

For all these tests, please add an assert for the default JsonSerializer.Serialize(...) output, without any options/naming policy.

Copy link
Member

@jozkee jozkee left a comment

Choose a reason for hiding this comment

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

Please review my suggetions in order to avoid using reflection.
Other than @layomia and @ahsonkhan's feedback, tests looks good to me.

@@ -83,6 +84,13 @@ public static partial class JsonSerializer

// An object or another enumerator requires a new stack frame.
state.Push(elementClassInfo, value);
var extensionDataAttribute = jsonPropertyInfo.PropertyInfo?.GetCustomAttribute(typeof(JsonExtensionDataAttribute));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
var extensionDataAttribute = jsonPropertyInfo.PropertyInfo?.GetCustomAttribute(typeof(JsonExtensionDataAttribute));

@@ -83,6 +84,13 @@ public static partial class JsonSerializer

// An object or another enumerator requires a new stack frame.
state.Push(elementClassInfo, value);
var extensionDataAttribute = jsonPropertyInfo.PropertyInfo?.GetCustomAttribute(typeof(JsonExtensionDataAttribute));
if (options.DictionaryKeyPolicy != null && extensionDataAttribute == null
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (options.DictionaryKeyPolicy != null && extensionDataAttribute == null

Move this condition before state.Push and remove extensionDataAttribute

@@ -83,6 +84,13 @@ public static partial class JsonSerializer

// An object or another enumerator requires a new stack frame.
state.Push(elementClassInfo, value);
var extensionDataAttribute = jsonPropertyInfo.PropertyInfo?.GetCustomAttribute(typeof(JsonExtensionDataAttribute));
if (options.DictionaryKeyPolicy != null && extensionDataAttribute == null
&& state.Current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
&& state.Current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing)

Also this condition.

@@ -5,6 +5,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
using System.Reflection;

@ahsonkhan
Copy link
Member

@marcnet80 - would you be able to update this PR today before the repo moves? Would it be ok for @jozkee to push to your branch instead and move this forward so we can merge it in now rather than having to wait? It's almost ready. Thanks.

@jozkee
Copy link
Member

jozkee commented Nov 14, 2019

I would like to push the requested test changes, however that is going to restart the CI, and since the repository consolidation is occuring in an hour, I am afraid that having a pending CI would prevent this PR from making it to master before consolidation happens, should I hold off said changes?
@steveharter @ahsonkhan

@ahsonkhan
Copy link
Member

I am fine with having the test changes be made in a follow up PR.

@jozkee jozkee merged commit d9dd0da into dotnet:master Nov 14, 2019
@jozkee
Copy link
Member

jozkee commented Nov 14, 2019

@marcnet80 great job and thank you for your contribution.

@layomia layomia added the breaking-change Issue or PR that represents a breaking API or functional change over a prerelease. label Mar 13, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json breaking-change Issue or PR that represents a breaking API or functional change over a prerelease.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants