-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Add PSJsonSerializerV2 for ConvertTo-Json using TruncatingConverterFactory #26654
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
base: master
Are you sure you want to change the base?
Add PSJsonSerializerV2 for ConvertTo-Json using TruncatingConverterFactory #26654
Conversation
iSazonov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yotsuda Thanks for starting the approach!
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Show resolved
Hide resolved
…Cmdlet/ConvertToJsonCommandV2.cs Co-authored-by: Ilya <darpa@yandex.ru>
…Cmdlet/ConvertToJsonCommandV2.cs Co-authored-by: Ilya <darpa@yandex.ru>
74a83c8 to
783f3b3
Compare
|
@yotsuda It seems you lost last commits - I don't see updates. |
|
@iSazonov Sorry, I missed the same pattern in
|
iSazonov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yotsuda Great progress! Thanks!
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Show resolved
Hide resolved
|
|
||
| // Convert to string when depth exceeded | ||
| string stringValue = LanguagePrimitives.ConvertTo<string>(pso.ImmediateBaseObjectIsEmpty ? pso : obj); | ||
| writer.WriteStringValue(stringValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems you don't understand my request. I mean if max depth is reached, V1 convert base object to string and process extended/adapted properties too. In V2 we only convert base object to string but don't process extended/adapted properties.
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
…e write-only check to WriteProperty
|
Please see failed tests. $jstr
{
"Major": 2,
"Minor": 3,
"Build": 4,
"Revision": 14,
"MajorRevision": 0,
"MinorRevision": 14,
"Note": "a version object",
"Rev": 14,
"IsOld": true
}What is $jstr in V2? As for |
iSazonov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yotsuda Thanks for your efforts! I didn't expect that there would be so many difficulties, but it seems that we have already overcome most of them.
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
|
|
||
| // GetTypeInfo() has internal caching, no need for additional cache | ||
| var typeInfo = JsonSerializerOptions.Default.GetTypeInfo(type); | ||
| return typeInfo.Kind == JsonTypeInfoKind.None; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we process type(object) in V1 and V2?
See https://source.dot.net/#System.Text.Json/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs,1337
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Show resolved
Hide resolved
| public static bool ShouldSkipProperty(PSPropertyInfo prop) | ||
| { | ||
| // Check for Hidden attribute | ||
| if (prop.IsHidden) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, how this (doesn't) works for script-defined classes.
Both issues are fixed in the latest commits. AddMember on JsonObject (Version + ETS): V2 now produces the same output as V1 for Version objects with ETS properties: The fix adds V1-compatible logic: "true primitive types" (string, DateTime, etc.) always serialize as scalars ignoring ETS, while non-primitive STJ scalar types (Version, IPAddress, etc.) serialize as objects when they have ETS properties. [char]127 serialization: Implemented as you suggested - the test now selects expected result based on experimental feature status:
Commit: 8efb723 |
iSazonov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yotsuda Thanks! It seems I see final :-)
I expect you to address all the previous comments that are not closed. After that, we should work on compatibility tests and come back here to complete the work.
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Show resolved
Hide resolved
| return false; | ||
| } | ||
|
|
||
| // V1 primitive types always serialize as scalars, ignoring ETS properties |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure? I see V1 code (ProcessValue method) skip ETS in AddPsProperties method only if:
- isPureObj = true (LanguagePrimitives.ConvertTo() add ETS)
- it is not PSObject (it is raw object)
- It is string or Datetime type.
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
test/powershell/Modules/Microsoft.PowerShell.Utility/Json.Tests.ps1
Outdated
Show resolved
Hide resolved
…Cmdlet/ConvertToJsonCommandV2.cs Co-authored-by: Ilya <darpa@yandex.ru>
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommandV2.cs
Outdated
Show resolved
Hide resolved
| private void SerializeAsObject(Utf8JsonWriter writer, PSObject pso, JsonSerializerOptions options) | ||
| { | ||
| writer.WriteStartObject(); | ||
| AppendPSProperties(writer, pso, options, PSMemberViewTypes.Extended | PSMemberViewTypes.Adapted); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see V1 does:
- Enumerate properties using reflection.
- Add extended and adapted properties (excluding duplicates from 1.).
It seems if we request adapted properties we get reflection properties too. I guess there may be differences, but I do not know how it works in depth.
@SeeminglyScience @daxian-dbw Could you please help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created Version type object, then add ETS noteproperty with Add-Member, then request properties with PSMemberInfoIntegratingCollection:
- for
BaseI see only the note property, - for
ExtendedI see only the note property, - for
AdaptedI see the note property and native properties.
My understanding is that is how PSObject wrapping works. PSObject is a property bag. So for Base just the ps object is considered. For Extend - only ETS properties as exclusively separate. For Adapted - adapters are involved including .Net Adapter which return native .Net members. Some adapters can be invovled (including XML, CIM/WMI,COM). Property priority is Extended -> Adapded -> Base.
V1 code add native .Net properties by reflection then enumerate Extended and Adapted ones with PSMemberInfoIntegratingCollection and add them only if the name is not already in list. So reflection properties win.
If we use only PSMemberInfoIntegratingCollection in V2 - Extended properties win. It is breaking change.
Perhaps it would be worthwhile to adhere to this native PowerShell behavior, but since historically this cmdlet works differently, it is not difficult for us to do the same by taking properties from the Json Default cache or by psObj.Properties.Match("*", PSMemberTypes.Property); (although it is more expensive).
…ack in WriteProperty
PR Summary
Implements
TruncatingConverterFactorypattern forConvertTo-JsonV2 as discussed with @iSazonov in PR #26637.This PR is an alternative implementation approach based on the Factory pattern, branched from
feature-convertto-json-isazonov-approach.Fixes #5749
PR Context
Background
In PR #26637, @iSazonov suggested implementing the Factory approach in a new PR to evaluate whether it provides a better architecture than the current implementation.
Thanks to @iSazonov's guidance on the technical considerations, this implementation now works correctly.
Solution
This implementation uses a
TruncatingConverterFactorythat dispatches to type-specific converters:JsonConverterPSObject(Extended/Adapted properties)JsonConverterDictionary<T>(Base properties only)JsonConverterEnumerable<T>JsonConverterComposite<T>(Base properties only)Architecture Benefits
Key Differences from PR #26637
RawObjectWrapperclassTruncatingConverterFactorydispatchWriteValueJsonConverterPSObjectwith_basePropertiesOnlyflagPR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerPSJsonSerializerV2Changes Made
Modified Files
ConvertToJsonCommandV2.csTruncatingConverterFactory- dispatches to appropriate converters based on typeJsonConverterDictionary<T>- handles IDictionary typesJsonConverterEnumerable<T>- handles IEnumerable typesJsonConverterComposite<T>- handles composite objects with Base properties onlyRawObjectWrapperclass_basePropertiesOnlyfield andfilterToPropertyOnlyparameter fromJsonConverterPSObjectWriteValuehelper toJsonSerializerHelperConvertTo-Json.PSJsonSerializerV2.Tests.ps1Testing
Existing Tests (ConvertTo-Json.Tests.ps1)
V2 Specific Tests (ConvertTo-Json.PSJsonSerializerV2.Tests.ps1)
Related Work
Branch Structure
gitGraph commit id: "master" branch system-text-json commit id: "77d8a1b0c" commit id: "159b76387" branch isazonov-approach commit id: "e7dfff962" commit id: "da0c8cbfb" branch factory-test commit id: "test" type: REVERSE checkout isazonov-approach commit id: "6913cb119" commit id: "2ecc51e05" commit id: "2c549207a" tag: "PR #26637" branch factory-v2 commit id: "af393269b" tag: "HEAD" checkout system-text-json commit id: "07cea1dc5" tag: "PR #26624"system-text-jsonisazonov-approachfactory-testfactory-v2