├── .azure-pipelines
└── ci-build.yml
├── .editorconfig
├── .github
├── CODEOWNERS
├── dependabot.yml
├── policies
│ └── resourceManagement.yml
└── workflows
│ ├── auto-merge-dependabot.yml
│ ├── build-and_test.yml
│ ├── conflicting-pr-label.yml
│ └── sonarcloud.yml
├── .gitignore
├── .vscode
└── tasks.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── Microsoft.Kiota.Serialization.Json.Tests
├── Converters
│ └── JsonGuidConverter.cs
├── IntersectionWrapperParseTests.cs
├── JsonAsyncParseNodeFactoryTests.cs
├── JsonParseNodeFactoryTests.cs
├── JsonParseNodeTests.cs
├── JsonSerializationWriterFactoryTests.cs
├── JsonSerializationWriterTests.cs
├── Microsoft.Kiota.Serialization.Json.Tests.csproj
├── Mocks
│ ├── ConverterTestEntity.cs
│ ├── DerivedTestEntity.cs
│ ├── IntersectionTypeMock.cs
│ ├── SecondTestEntity.cs
│ ├── TestEntity.cs
│ ├── TestEnum.cs
│ ├── TestNamingEnum.cs
│ ├── UnionTypeMock.cs
│ └── UntypedTestEntity.cs
└── UnionWrapperParseTests.cs
├── Microsoft.Kiota.Serialization.Json.sln
├── README.md
├── SECURITY.md
├── SUPPORT.md
├── scripts
├── EnableSigning.ps1
├── GetNugetPackageVersion.ps1
└── ValidateProjectVersionUpdated.ps1
└── src
├── 35MSSharedLib1024.snk
├── JsonParseNode.cs
├── JsonParseNodeFactory.cs
├── JsonSerializationWriter.cs
├── JsonSerializationWriterFactory.cs
├── KiotaJsonSerializationContext.cs
├── Microsoft.Kiota.Serialization.Json.csproj
└── TypeConstants.cs
/.azure-pipelines/ci-build.yml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Microsoft Corporation. All rights reserved.
2 | # Licensed under the MIT License.
3 |
4 | name: $(BuildDefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)
5 |
6 | trigger:
7 | branches:
8 | include:
9 | - main
10 | pr:
11 | branches:
12 | include:
13 | - main
14 |
15 | variables:
16 | buildPlatform: 'Any CPU'
17 | buildConfiguration: 'Release'
18 | ProductBinPath: '$(Build.SourcesDirectory)\src\bin\$(BuildConfiguration)'
19 |
20 | resources:
21 | repositories:
22 | - repository: 1ESPipelineTemplates
23 | type: git
24 | name: 1ESPipelineTemplates/1ESPipelineTemplates
25 | ref: refs/tags/release
26 |
27 | extends:
28 | template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
29 | parameters:
30 | pool:
31 | name: Azure-Pipelines-1ESPT-ExDShared
32 | vmImage: windows-latest
33 | stages:
34 |
35 | - stage: build
36 | jobs:
37 | - job: build
38 | steps:
39 |
40 | - task: UseDotNet@2
41 | displayName: 'Use .NET 6' # needed for ESRP signing
42 | inputs:
43 | version: 6.x
44 |
45 | - task: UseDotNet@2
46 | displayName: 'Use .NET 8'
47 | inputs:
48 | version: 8.x
49 |
50 | # Install the nuget tool.
51 | - task: NuGetToolInstaller@1
52 | displayName: 'Install Nuget dependency manager'
53 | inputs:
54 | versionSpec: '>=5.2.0'
55 | checkLatest: true
56 |
57 | - task: PowerShell@2
58 | displayName: 'Enable signing'
59 | inputs:
60 | targetType: filePath
61 | filePath: 'scripts\EnableSigning.ps1'
62 | arguments: '-projectPath "$(Build.SourcesDirectory)/src/Microsoft.Kiota.Serialization.Json.csproj"'
63 | pwsh: true
64 | enabled: true
65 |
66 | # Build the Product project
67 | - task: DotNetCoreCLI@2
68 | displayName: 'Build Microsoft.Kiota.Serialization.Json'
69 | inputs:
70 | projects: '$(Build.SourcesDirectory)\Microsoft.Kiota.Serialization.Json.sln'
71 | arguments: '--configuration $(BuildConfiguration) --no-incremental'
72 |
73 | # Run the Unit test
74 | - task: DotNetCoreCLI@2
75 | displayName: 'Test Microsoft.Kiota.Serialization.Json'
76 | inputs:
77 | command: test
78 | projects: '$(Build.SourcesDirectory)\Microsoft.Kiota.Serialization.Json.sln'
79 | arguments: '--configuration $(BuildConfiguration) --no-build -f net8.0'
80 |
81 | - task: EsrpCodeSigning@2
82 | displayName: 'ESRP DLL Strong Name'
83 | inputs:
84 | ConnectedServiceName: 'microsoftgraph ESRP CodeSign DLL and NuGet (AKV)'
85 | FolderPath: $(ProductBinPath)
86 | Pattern: '**\*Microsoft.Kiota.Serialization.Json.dll'
87 | UseMinimatch: true
88 | signConfigType: inlineSignParams
89 | inlineOperation: |
90 | [
91 | {
92 | "keyCode": "CP-233863-SN",
93 | "operationSetCode": "StrongNameSign",
94 | "parameters": [],
95 | "toolName": "sign",
96 | "toolVersion": "1.0"
97 | },
98 | {
99 | "keyCode": "CP-233863-SN",
100 | "operationSetCode": "StrongNameVerify",
101 | "parameters": [],
102 | "toolName": "sign",
103 | "toolVersion": "1.0"
104 | }
105 | ]
106 | SessionTimeout: 20
107 |
108 | - task: EsrpCodeSigning@2
109 | displayName: 'ESRP DLL CodeSigning'
110 | inputs:
111 | ConnectedServiceName: 'microsoftgraph ESRP CodeSign DLL and NuGet (AKV)'
112 | FolderPath: src
113 | Pattern: '**\*Microsoft.Kiota.Serialization.Json.dll'
114 | UseMinimatch: true
115 | signConfigType: inlineSignParams
116 | inlineOperation: |
117 | [
118 | {
119 | "keyCode": "CP-230012",
120 | "operationSetCode": "SigntoolSign",
121 | "parameters": [
122 | {
123 | "parameterName": "OpusName",
124 | "parameterValue": "Microsoft"
125 | },
126 | {
127 | "parameterName": "OpusInfo",
128 | "parameterValue": "http://www.microsoft.com"
129 | },
130 | {
131 | "parameterName": "FileDigest",
132 | "parameterValue": "/fd \"SHA256\""
133 | },
134 | {
135 | "parameterName": "PageHash",
136 | "parameterValue": "/NPH"
137 | },
138 | {
139 | "parameterName": "TimeStamp",
140 | "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
141 | }
142 | ],
143 | "toolName": "sign",
144 | "toolVersion": "1.0"
145 | },
146 | {
147 | "keyCode": "CP-230012",
148 | "operationSetCode": "SigntoolVerify",
149 | "parameters": [ ],
150 | "toolName": "sign",
151 | "toolVersion": "1.0"
152 | }
153 | ]
154 | SessionTimeout: 20
155 |
156 | # arguments are not parsed in DotNetCoreCLI@2 task for `pack` command, that's why we have a custom pack command here
157 | - pwsh: dotnet pack $env:BUILD_SOURCESDIRECTORY/src/Microsoft.Kiota.Serialization.Json.csproj /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg --no-build --output $env:BUILD_ARTIFACTSTAGINGDIRECTORY --configuration $env:BUILD_CONFIGURATION
158 | env:
159 | BUILD_CONFIGURATION: $(BuildConfiguration)
160 | displayName: Dotnet pack
161 |
162 | - task: PowerShell@2
163 | displayName: 'Validate project version has been incremented'
164 | condition: and(contains(variables['build.sourceBranch'], 'refs/heads/main'), succeeded())
165 | inputs:
166 | targetType: 'filePath'
167 | filePath: $(System.DefaultWorkingDirectory)\scripts\ValidateProjectVersionUpdated.ps1
168 | arguments: '-projectPath "$(Build.SourcesDirectory)/src/Microsoft.Kiota.Serialization.Json.csproj" -packageName "Microsoft.Kiota.Serialization.Json"'
169 | pwsh: true
170 |
171 | - task: EsrpCodeSigning@2
172 | displayName: 'ESRP CodeSigning Nuget Packages'
173 | inputs:
174 | ConnectedServiceName: 'microsoftgraph ESRP CodeSign DLL and NuGet (AKV)'
175 | FolderPath: '$(Build.ArtifactStagingDirectory)'
176 | Pattern: '*.nupkg'
177 | UseMinimatch: true
178 | signConfigType: inlineSignParams
179 | inlineOperation: |
180 | [
181 | {
182 | "keyCode": "CP-401405",
183 | "operationSetCode": "NuGetSign",
184 | "parameters": [ ],
185 | "toolName": "sign",
186 | "toolVersion": "1.0"
187 | },
188 | {
189 | "keyCode": "CP-401405",
190 | "operationSetCode": "NuGetVerify",
191 | "parameters": [ ],
192 | "toolName": "sign",
193 | "toolVersion": "1.0"
194 | }
195 | ]
196 | SessionTimeout: 20
197 |
198 | - task: CopyFiles@2
199 | displayName: 'Copy release scripts to artifact staging directory'
200 | condition: and(contains(variables['build.sourceBranch'], 'refs/heads/main'), succeeded())
201 | inputs:
202 | SourceFolder: '$(Build.SourcesDirectory)'
203 | Contents: 'scripts\**'
204 | TargetFolder: '$(Build.ArtifactStagingDirectory)'
205 |
206 | - task: 1ES.PublishPipelineArtifact@1
207 | displayName: 'Upload Artifact: Nugets'
208 | inputs:
209 | artifactName: Nugets
210 | targetPath: $(Build.ArtifactStagingDirectory)
211 |
212 | - stage: deploy
213 | condition: and(contains(variables['build.sourceBranch'], 'refs/heads/main'), succeeded())
214 | dependsOn: build
215 | jobs:
216 | - deployment: deploy_dotnet_serialization_json
217 | dependsOn: []
218 | environment: nuget-org
219 | strategy:
220 | runOnce:
221 | deploy:
222 | pool:
223 | vmImage: ubuntu-latest
224 | steps:
225 | # Install the nuget tool.
226 | - task: NuGetToolInstaller@0
227 | displayName: 'Use NuGet >=5.2.0'
228 | inputs:
229 | versionSpec: '>=5.2.0'
230 | checkLatest: true
231 | - task: DownloadPipelineArtifact@2
232 | displayName: Download nupkg from artifacts
233 | inputs:
234 | artifact: Nugets
235 | source: current
236 | - task: PowerShell@2
237 | displayName: 'Extract release information to pipeline'
238 | inputs:
239 | targetType: 'filePath'
240 | filePath: $(Pipeline.Workspace)\scripts\GetNugetPackageVersion.ps1
241 | pwsh: true
242 | arguments: '-packageDirPath "$(Pipeline.Workspace)/"'
243 | - task: 1ES.PublishNuget@1
244 | displayName: 'Push release to NuGet.org'
245 | inputs:
246 | command: push
247 | packagesToPush: '$(Pipeline.Workspace)/Microsoft.Kiota.Serialization.Json.*.nupkg'
248 | packageParentPath: '$(Pipeline.Workspace)'
249 | nuGetFeedType: external
250 | publishFeedCredentials: 'Kiota Nuget Connection'
251 | - task: GitHubRelease@1
252 | displayName: 'GitHub release (create)'
253 | inputs:
254 | gitHubConnection: 'Kiota_Release'
255 | target: $(Build.SourceVersion)
256 | tagSource: userSpecifiedTag
257 | tag: 'v$(VERSION_STRING)'
258 | title: '$(VERSION_STRING)'
259 | releaseNotesSource: inline
260 | assets: '!**/**'
261 | changeLogType: issueBased
262 | isPreRelease : '$(IS_PRE_RELEASE)'
263 | addChangeLog : true
264 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\Users\anomondi\Documents\kiota\serialization\dotnet\json codebase based on best match to current usage at 17/08/2021
2 | # You can modify the rules from these initially generated values to suit your own policies
3 | # You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
4 | [*.cs]
5 |
6 |
7 | #Core editorconfig formatting - indentation
8 |
9 | #use soft tabs (spaces) for indentation
10 | indent_style = space
11 |
12 | #Formatting - indentation options
13 |
14 | #indent switch case contents.
15 | csharp_indent_case_contents = true
16 | #indent switch labels
17 | csharp_indent_switch_labels = true
18 |
19 | #Formatting - new line options
20 |
21 | #place else statements on a new line
22 | csharp_new_line_before_else = true
23 | #require members of object intializers to be on separate lines
24 | csharp_new_line_before_members_in_object_initializers = true
25 |
26 | #Formatting - organize using options
27 |
28 | #sort System.* using directives alphabetically, and place them before other usings
29 | dotnet_sort_system_directives_first = true
30 |
31 | #Formatting - spacing options
32 |
33 | #require NO space between a cast and the value
34 | csharp_space_after_cast = false
35 | #require a space before the colon for bases or interfaces in a type declaration
36 | csharp_space_after_colon_in_inheritance_clause = true
37 | #require NO space after a keyword in a control flow statement such as a for loop
38 | csharp_space_after_keywords_in_control_flow_statements = false
39 | #require a space before the colon for bases or interfaces in a type declaration
40 | csharp_space_before_colon_in_inheritance_clause = true
41 | #remove space within empty argument list parentheses
42 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
43 | #remove space between method call name and opening parenthesis
44 | csharp_space_between_method_call_name_and_opening_parenthesis = false
45 | #do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
46 | csharp_space_between_method_call_parameter_list_parentheses = false
47 | #remove space within empty parameter list parentheses for a method declaration
48 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
49 | #place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
50 | csharp_space_between_method_declaration_parameter_list_parentheses = false
51 |
52 | #Formatting - wrapping options
53 |
54 | #leave code block on single line
55 | csharp_preserve_single_line_blocks = true
56 | #leave statements and member declarations on the same line
57 | csharp_preserve_single_line_statements = true
58 |
59 | #Style - Code block preferences
60 |
61 | #prefer no curly braces if allowed
62 | csharp_prefer_braces = false:suggestion
63 |
64 | #Style - expression bodied member options
65 |
66 | #prefer block bodies for constructors
67 | csharp_style_expression_bodied_constructors = false:suggestion
68 | #prefer expression-bodied members for methods
69 | csharp_style_expression_bodied_methods = true:suggestion
70 |
71 | #Style - expression level options
72 |
73 | #prefer out variables to be declared inline in the argument list of a method call when possible
74 | csharp_style_inlined_variable_declaration = true:suggestion
75 | #prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
76 | dotnet_style_predefined_type_for_member_access = true:suggestion
77 |
78 | #Style - Expression-level preferences
79 |
80 | #prefer default over default(T)
81 | csharp_prefer_simple_default_expression = true:suggestion
82 | #prefer objects to be initialized using object initializers when possible
83 | dotnet_style_object_initializer = true:suggestion
84 |
85 | #Style - implicit and explicit types
86 |
87 | #prefer var over explicit type in all cases, unless overridden by another code style rule
88 | csharp_style_var_elsewhere = true:suggestion
89 | #prefer var when the type is already mentioned on the right-hand side of a declaration expression
90 | csharp_style_var_when_type_is_apparent = true:suggestion
91 |
92 | #Style - language keyword and framework type options
93 |
94 | #prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
95 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
96 |
97 | #Style - Miscellaneous preferences
98 |
99 | #prefer anonymous functions over local functions
100 | csharp_style_pattern_local_over_anonymous_function = false:suggestion
101 |
102 | #Style - modifier options
103 |
104 | #prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
105 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
106 |
107 | #Style - Modifier preferences
108 |
109 | #when this rule is set to a list of modifiers, prefer the specified ordering.
110 | csharp_preferred_modifier_order = public,private,static,readonly:suggestion
111 |
112 | #Style - qualification options
113 |
114 | #prefer fields not to be prefaced with this. or Me. in Visual Basic
115 | dotnet_style_qualification_for_field = false:suggestion
116 | #prefer methods not to be prefaced with this. or Me. in Visual Basic
117 | dotnet_style_qualification_for_method = false:suggestion
118 | #prefer properties not to be prefaced with this. or Me. in Visual Basic
119 | dotnet_style_qualification_for_property = false:suggestion
120 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @microsoft/kiota-write
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: nuget
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | labels:
9 | - dependencies
10 | - package-ecosystem: github-actions
11 | directory: "/"
12 | schedule:
13 | interval: daily
14 | open-pull-requests-limit: 10
15 |
--------------------------------------------------------------------------------
/.github/policies/resourceManagement.yml:
--------------------------------------------------------------------------------
1 | id:
2 | name: GitOps.PullRequestIssueManagement
3 | description: GitOps.PullRequestIssueManagement primitive
4 | owner:
5 | resource: repository
6 | disabled: false
7 | where:
8 | configuration:
9 | resourceManagementConfiguration:
10 | scheduledSearches:
11 | - description:
12 | frequencies:
13 | - hourly:
14 | hour: 6
15 | filters:
16 | - isIssue
17 | - isOpen
18 | - hasLabel:
19 | label: 'status:waiting-for-author-feedback'
20 | - hasLabel:
21 | label: 'Status: No Recent Activity'
22 | - noActivitySince:
23 | days: 3
24 | actions:
25 | - closeIssue
26 | - description:
27 | frequencies:
28 | - hourly:
29 | hour: 6
30 | filters:
31 | - isIssue
32 | - isOpen
33 | - hasLabel:
34 | label: 'status:waiting-for-author-feedback'
35 | - noActivitySince:
36 | days: 4
37 | - isNotLabeledWith:
38 | label: 'Status: No Recent Activity'
39 | actions:
40 | - addLabel:
41 | label: 'Status: No Recent Activity'
42 | - addReply:
43 | reply: This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **4 days**. It will be closed if no further activity occurs **within 3 days of this comment**.
44 | - description:
45 | frequencies:
46 | - hourly:
47 | hour: 6
48 | filters:
49 | - isIssue
50 | - isOpen
51 | - hasLabel:
52 | label: 'Resolution: Duplicate'
53 | - noActivitySince:
54 | days: 1
55 | actions:
56 | - addReply:
57 | reply: This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes.
58 | - closeIssue
59 | eventResponderTasks:
60 | - if:
61 | - payloadType: Issue_Comment
62 | - isAction:
63 | action: Created
64 | - isActivitySender:
65 | issueAuthor: True
66 | - hasLabel:
67 | label: 'status:waiting-for-author-feedback'
68 | - isOpen
69 | then:
70 | - addLabel:
71 | label: 'Needs: Attention :wave:'
72 | - removeLabel:
73 | label: 'status:waiting-for-author-feedback'
74 | description:
75 | - if:
76 | - payloadType: Issues
77 | - not:
78 | isAction:
79 | action: Closed
80 | - hasLabel:
81 | label: 'Status: No Recent Activity'
82 | then:
83 | - removeLabel:
84 | label: 'Status: No Recent Activity'
85 | description:
86 | - if:
87 | - payloadType: Issue_Comment
88 | - hasLabel:
89 | label: 'Status: No Recent Activity'
90 | then:
91 | - removeLabel:
92 | label: 'Status: No Recent Activity'
93 | description:
94 | - if:
95 | - payloadType: Pull_Request
96 | then:
97 | - inPrLabel:
98 | label: WIP
99 | description:
100 | onFailure:
101 | onSuccess:
102 |
--------------------------------------------------------------------------------
/.github/workflows/auto-merge-dependabot.yml:
--------------------------------------------------------------------------------
1 | name: Auto-merge dependabot updates
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 | permissions:
8 | pull-requests: write
9 | contents: write
10 |
11 | jobs:
12 |
13 | dependabot-merge:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | if: ${{ github.actor == 'dependabot[bot]' }}
18 |
19 | steps:
20 | - name: Dependabot metadata
21 | id: metadata
22 | uses: dependabot/fetch-metadata@v2.2.0
23 | with:
24 | github-token: "${{ secrets.GITHUB_TOKEN }}"
25 |
26 | - name: Enable auto-merge for Dependabot PRs
27 | # Only if version bump is not a major version change
28 | if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}}
29 | run: gh pr merge --auto --merge "$PR_URL"
30 | env:
31 | PR_URL: ${{github.event.pull_request.html_url}}
32 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
33 |
--------------------------------------------------------------------------------
/.github/workflows/build-and_test.yml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: [ 'main', 'dev', 'feature/*' ]
7 | pull_request:
8 | branches: [ 'main', 'dev' ]
9 |
10 | permissions:
11 | contents: read #those permissions are required to run the codeql analysis
12 | actions: read
13 | security-events: write
14 |
15 | jobs:
16 | build-and-test:
17 | runs-on: windows-latest
18 | env:
19 | solutionName: Microsoft.Kiota.Serialization.Json.sln
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Setup .NET
23 | uses: actions/setup-dotnet@v4
24 | with:
25 | dotnet-version: 8.x
26 | - name: Initialize CodeQL
27 | uses: github/codeql-action/init@v3
28 | with:
29 | languages: csharp
30 | - name: Restore dependencies
31 | run: dotnet restore ${{ env.solutionName }}
32 | - name: Build
33 | run: dotnet build ${{ env.solutionName }} --no-restore -c Release /p:UseSharedCompilation=false
34 | - name: Test for net462
35 | run: dotnet test ${{ env.solutionName }} --no-build --verbosity normal -c Release --framework net462
36 | - name: Test for net8.0 and collect coverage
37 | run: dotnet test ${{ env.solutionName }} --no-build --verbosity normal -c Release /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=opencover --framework net8.0
38 | - name: Perform CodeQL Analysis
39 | uses: github/codeql-action/analyze@v3
40 |
--------------------------------------------------------------------------------
/.github/workflows/conflicting-pr-label.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: PullRequestConflicting
4 |
5 | # Controls when the action will run. Triggers the workflow on push or pull request
6 | # events but only for the master branch
7 | on:
8 | push:
9 | branches: [ main ]
10 | pull_request:
11 | types: [synchronize]
12 | branches: [ main ]
13 |
14 | permissions:
15 | pull-requests: write
16 | contents: read
17 |
18 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
19 | jobs:
20 | # This workflow contains a single job called "build"
21 | build:
22 | # The type of runner that the job will run on
23 | runs-on: ubuntu-latest
24 |
25 | # Steps represent a sequence of tasks that will be executed as part of the job
26 | steps:
27 | - name: check if prs are dirty
28 | uses: eps1lon/actions-label-merge-conflict@releases/2.x
29 | if: env.LABELING_TOKEN != '' && env.LABELING_TOKEN != null
30 | id: check
31 | with:
32 | dirtyLabel: "conflicting"
33 | repoToken: "${{ secrets.GITHUB_TOKEN }}"
34 | continueOnMissingPermissions: true
35 | commentOnDirty: 'This pull request has conflicting changes, the author must resolve the conflicts before this pull request can be merged.'
36 | commentOnClean: 'Conflicts have been resolved. A maintainer will take a look shortly.'
37 | env:
38 | LABELING_TOKEN: ${{secrets.GITHUB_TOKEN }}
39 |
--------------------------------------------------------------------------------
/.github/workflows/sonarcloud.yml:
--------------------------------------------------------------------------------
1 | name: Sonarcloud
2 | on:
3 | workflow_dispatch:
4 | push:
5 | branches:
6 | - main
7 | paths-ignore: ['.vscode/**']
8 | pull_request:
9 | types: [opened, synchronize, reopened]
10 | paths-ignore: ['.vscode/**']
11 |
12 | permissions:
13 | contents: read
14 | pull-requests: read
15 |
16 | env:
17 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
18 |
19 | jobs:
20 | checksecret:
21 | name: check if SONAR_TOKEN is set in github secrets
22 | runs-on: ubuntu-latest
23 | outputs:
24 | is_SONAR_TOKEN_set: ${{ steps.checksecret_job.outputs.is_SONAR_TOKEN_set }}
25 | steps:
26 | - name: Check whether unity activation requests should be done
27 | id: checksecret_job
28 | run: |
29 | echo "is_SONAR_TOKEN_set=${{ env.SONAR_TOKEN != '' }}" >> $GITHUB_OUTPUT
30 | build:
31 | needs: [checksecret]
32 | if: needs.checksecret.outputs.is_SONAR_TOKEN_set == 'true'
33 | name: Build
34 | runs-on: ubuntu-latest
35 | steps:
36 | - name: Set up JDK 17
37 | uses: actions/setup-java@v4
38 | with:
39 | distribution: 'adopt'
40 | java-version: 17
41 | - name: Setup .NET
42 | uses: actions/setup-dotnet@v4
43 | with: # At the moment the scanner requires dotnet 5 https://www.nuget.org/packages/dotnet-sonarscanner
44 | dotnet-version: |
45 | 5.x
46 | 8.x
47 | - uses: actions/checkout@v4
48 | with:
49 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
50 | - name: Cache SonarCloud packages
51 | uses: actions/cache@v4
52 | with:
53 | path: ~/.sonar/cache
54 | key: ${{ runner.os }}-sonar
55 | restore-keys: ${{ runner.os }}-sonar
56 | - name: Install SonarCloud scanner
57 | run: dotnet tool install dotnet-sonarscanner --create-manifest-if-needed
58 | - name: Build and analyze
59 | env:
60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
61 | CollectCoverage: true
62 | CoverletOutputFormat: 'opencover' # https://github.com/microsoft/vstest/issues/4014#issuecomment-1307913682
63 | shell: pwsh
64 | run: |
65 | dotnet tool run dotnet-sonarscanner begin /k:"microsoft_kiota-serialization-json-dotnet" /o:"microsoft" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="Microsoft.Kiota.Serialization.Json.Tests/coverage.net8.0.opencover.xml"
66 | dotnet workload restore
67 | dotnet build
68 | dotnet test Microsoft.Kiota.Serialization.Json.sln --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover --framework net8.0
69 | dotnet tool run dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
352 | # JetBrains Rider
353 | .idea/
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "build",
8 | "command": "dotnet",
9 | "type": "process",
10 | "args": [
11 | "build",
12 | "${workspaceFolder}/Microsoft.Kiota.Serialization.Json.sln",
13 | // Ask dotnet build to generate full paths for file names.
14 | "/property:GenerateFullPaths=true",
15 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel
16 | "/consoleloggerparameters:NoSummary"
17 | ],
18 | "group": "build",
19 | "problemMatcher": "$msCompile"
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [1.3.3] - 2024-05-24
11 |
12 | ### Changed
13 |
14 | - Remove all LINQ usage from repo (except tests)
15 |
16 | ## [1.3.2] - 2024-05-23
17 |
18 | ### Changed
19 |
20 | - Fixed an issue where fixed versions of abstractions would result in restore failures. [microsoft/kiota-http-dotnet#256](https://github.com/microsoft/kiota-http-dotnet/issues/258)
21 |
22 | ## [1.3.1] - 2024-05-20
23 |
24 | ### Changed
25 |
26 | - Updated serialization and deserialization of enums to remove LINQ to resolve NativeAOT compatibility issue
27 |
28 | ## [1.3.0] - 2024-05-13
29 |
30 | ### Added
31 |
32 | - Implements IAsyncParseNodeFactory interface which adds async support
33 |
34 | ## [1.2.3] - 2024-04-25
35 |
36 | ### Changed
37 |
38 | - Parse empty strings as nullable Guid
39 |
40 | ## [1.2.2] - 2024-04-19
41 |
42 | ### Changed
43 |
44 | - Replaced the included license by license expression for better auditing capabilities.
45 |
46 | ## [1.2.1] - 2024-04-17
47 |
48 | ### Changed
49 |
50 | - Have made System.Text.Json only be included on Net Standard's TFM & net 5
51 |
52 | ## [1.2.0] - 2024-03-22
53 |
54 | ### Added
55 |
56 | - Added support for untyped nodes. (https://github.com/microsoft/kiota-serialization-json-dotnet/issues/197)
57 |
58 | ## [1.1.8] - 2024-02-27
59 |
60 | - Reduced `DynamicallyAccessedMembers` scope for enum methods to prevent ILC warnings.
61 |
62 | ## [1.1.7] - 2024-02-26
63 |
64 | - Add ability to use `JsonSerializerContext` (and `JsonSerialzerOptions`) when serializing and deserializing
65 |
66 | ## [1.1.6] - 2024-02-23
67 |
68 | ### Changed
69 |
70 | - Added `net6.0` and `net8.0` as target frameworks.
71 |
72 | ## [1.1.5] - 2024-02-05
73 |
74 | ### Changed
75 |
76 | - Fixes `IsTrimmable` property on the project.
77 |
78 | ## [1.1.4] - 2024-01-30
79 |
80 | ### Changed
81 |
82 | - Fixed AOT warnings caused by reflection on enum types.
83 |
84 | ## [1.1.3] - 2024-01-29
85 |
86 | ### Changed
87 |
88 | - Fixed a bug where serialization of decimal values would write them as empty objects.
89 |
90 | ### Added
91 |
92 | ## [1.1.2] - 2023-11-15
93 |
94 | ### Added
95 |
96 | - Added support for dotnet 8.
97 |
98 | ## [1.1.1] - 2023-10-23
99 |
100 | ### Changed
101 |
102 | - Fixed a bug where deserialization of downcast type fields would be ignored.
103 |
104 | ## [1.1.0] - 2023-10-23
105 |
106 | ### Added
107 |
108 | - Added support for dotnet trimming.
109 |
110 | ## [1.0.8] - 2023-07-14
111 |
112 | ### Changed
113 |
114 | - Fixes deserialization of arrays with item type long
115 |
116 | ## [1.0.7] - 2023-06-28
117 |
118 | ### Changed
119 |
120 | - Fixed composed types serialization.
121 |
122 | ## [1.0.6] - 2023-05-19
123 |
124 | ### Changed
125 |
126 | - #86: Fix inheritance new keyword for hiding existing implementation of deserializing method
127 | - #85: Bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.0
128 | - #84: Bump Microsoft.TestPlatform.ObjectModel from 17.5.0 to 17.6.0
129 | - #82: Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0
130 | - #81: Bump Microsoft.Kiota.Abstractions from 1.1.0 to 1.1.1
131 |
132 | ## [1.0.5] - 2023-05-17
133 |
134 | ### Changed
135 |
136 | - Fixes a bug where 'new' keyword on derived classes from IParsable is not being respected, returning null properties for json parsed nodes
137 |
138 | ### Added
139 |
140 | ## [1.0.5] - 2023-04-04
141 |
142 | ### Changed
143 |
144 | - Fixes a bug where EnumMember attribute enums would have the first letter lowecased
145 |
146 | ## [1.0.4] - 2023-04-03
147 |
148 | ### Changed
149 |
150 | - Fixes a bug where EnumMember attribute was not taken into account during serialization/deserialization
151 |
152 | ## [1.0.3] - 2023-03-15
153 |
154 | ### Changed
155 |
156 | - Fixes serialization of DateTime type in the additionalData
157 |
158 | ## [1.0.2] - 2023-03-10
159 |
160 | ### Changed
161 |
162 | - Bumps abstraction dependency
163 |
164 | ## [1.0.1] - 2023-03-08
165 |
166 | ### Changed
167 |
168 | - Update minimum version of [`System.Text.Json`](https://www.nuget.org/packages/System.Text.Json) to `6.0.0`.
169 |
170 | ## [1.0.0] - 2023-02-27
171 |
172 | ### Added
173 |
174 | - GA release
175 |
176 | ## [1.0.0-rc.3] - 2023-01-27
177 |
178 | ### Changed
179 |
180 | - Relaxed nullability tolerance when merging objects for composed types.
181 |
182 | ## [1.0.0-rc.2] - 2023-01-17
183 |
184 | ### Changed
185 |
186 | - Adds support for nullable reference types
187 |
188 | ## [1.0.0-rc.1] - 2022-12-15
189 |
190 | ### Changed
191 |
192 | - Release candidate 1
193 |
194 | ## [1.0.0-preview.7] - 2022-09-02
195 |
196 | ### Added
197 |
198 | - Added support for composed types serialization.
199 |
200 | ## [1.0.0-preview.6] - 2022-05-27
201 |
202 | ### Changed
203 |
204 | - Fixes a bug where JsonParseNode.GetChildNode would throw an exception if the property name did not exist in the json.
205 |
206 | ## [1.0.0-preview.5] - 2022-05-18
207 |
208 | ### Changed
209 |
210 | - Updated abstractions version to 1.0.0.preview8
211 |
212 | ## [1.0.0-preview.4] - 2022-04-12
213 |
214 | ### Changed
215 |
216 | - Breaking: Changes target runtime to netstandard2.0
217 |
218 | ## [1.0.0-preview.3] - 2022-04-11
219 |
220 | ### Changed
221 |
222 | - Fixes handling of JsonElement types in additionData during serialization
223 |
224 | ## [1.0.0-preview.2] - 2022-04-04
225 |
226 | ### Changed
227 |
228 | - Breaking: simplifies the field deserializers.
229 |
230 | ## [1.0.0-preview.1] - 2022-03-18
231 |
232 | ### Added
233 |
234 | - Initial Nuget release
235 |
236 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 | - Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support)
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Converters/JsonGuidConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 |
6 | namespace Microsoft.Kiota.Serialization.Json.Tests.Converters;
7 |
8 | ///
9 | /// Converts a GUID object or value to/from JSON.
10 | ///
11 | public class JsonGuidConverter : JsonConverter
12 | {
13 | ///
14 | public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
15 | => reader.TokenType == JsonTokenType.Null
16 | ? Guid.Empty
17 | : ReadInternal(ref reader);
18 |
19 | ///
20 | public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options)
21 | => WriteInternal(writer, value);
22 |
23 | private static Guid ReadInternal(ref Utf8JsonReader reader)
24 | => Guid.Parse(reader.GetString()!);
25 |
26 | private static void WriteInternal(Utf8JsonWriter writer, Guid value)
27 | => writer.WriteStringValue(value.ToString("N", CultureInfo.InvariantCulture));
28 | }
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/IntersectionWrapperParseTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Linq;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 | using Microsoft.Kiota.Serialization.Json.Tests.Mocks;
6 | using Xunit;
7 |
8 | namespace Microsoft.Kiota.Serialization.Json.Tests;
9 |
10 | public class IntersectionWrapperParseTests {
11 | private readonly JsonParseNodeFactory _parseNodeFactory = new();
12 | private readonly JsonSerializationWriterFactory _serializationWriterFactory = new();
13 | private const string contentType = "application/json";
14 | [Fact]
15 | public async Task ParsesIntersectionTypeComplexProperty1()
16 | {
17 | // Given
18 | using var payload = new MemoryStream(Encoding.UTF8.GetBytes("{\"displayName\":\"McGill\",\"officeLocation\":\"Montreal\", \"id\": \"opaque\"}"));
19 | var parseNode = await _parseNodeFactory.GetRootParseNodeAsync(contentType, payload);
20 |
21 | // When
22 | var result = parseNode.GetObjectValue(IntersectionTypeMock.CreateFromDiscriminator);
23 |
24 | // Then
25 | Assert.NotNull(result);
26 | Assert.NotNull(result.ComposedType1);
27 | Assert.NotNull(result.ComposedType2);
28 | Assert.Null(result.ComposedType3);
29 | Assert.Null(result.StringValue);
30 | Assert.Equal("opaque", result.ComposedType1.Id);
31 | Assert.Equal("McGill", result.ComposedType2.DisplayName);
32 | }
33 | [Fact]
34 | public async Task ParsesIntersectionTypeComplexProperty2()
35 | {
36 | // Given
37 | using var payload = new MemoryStream(Encoding.UTF8.GetBytes("{\"displayName\":\"McGill\",\"officeLocation\":\"Montreal\", \"id\": 10}"));
38 | var parseNode = await _parseNodeFactory.GetRootParseNodeAsync(contentType, payload);
39 |
40 | // When
41 | var result = parseNode.GetObjectValue(IntersectionTypeMock.CreateFromDiscriminator);
42 |
43 | // Then
44 | Assert.NotNull(result);
45 | Assert.NotNull(result.ComposedType1);
46 | Assert.NotNull(result.ComposedType2);
47 | Assert.Null(result.ComposedType3);
48 | Assert.Null(result.StringValue);
49 | Assert.Null(result.ComposedType1.Id);
50 | Assert.Null(result.ComposedType2.Id); // it's expected to be null since we have conflicting properties here and the parser will only try one to avoid having to brute its way through
51 | Assert.Equal("McGill", result.ComposedType2.DisplayName);
52 | }
53 | [Fact]
54 | public async Task ParsesIntersectionTypeComplexProperty3()
55 | {
56 | // Given
57 | using var payload = new MemoryStream(Encoding.UTF8.GetBytes("[{\"@odata.type\":\"#microsoft.graph.TestEntity\",\"officeLocation\":\"Ottawa\", \"id\": \"11\"}, {\"@odata.type\":\"#microsoft.graph.TestEntity\",\"officeLocation\":\"Montreal\", \"id\": \"10\"}]"));
58 | var parseNode = await _parseNodeFactory.GetRootParseNodeAsync(contentType, payload);
59 |
60 | // When
61 | var result = parseNode.GetObjectValue(IntersectionTypeMock.CreateFromDiscriminator);
62 |
63 | // Then
64 | Assert.NotNull(result);
65 | Assert.Null(result.ComposedType1);
66 | Assert.Null(result.ComposedType2);
67 | Assert.NotNull(result.ComposedType3);
68 | Assert.Null(result.StringValue);
69 | Assert.Equal(2, result.ComposedType3.Count);
70 | Assert.Equal("Ottawa", result.ComposedType3.First().OfficeLocation);
71 | }
72 | [Fact]
73 | public async Task ParsesIntersectionTypeStringValue()
74 | {
75 | // Given
76 | using var payload = new MemoryStream(Encoding.UTF8.GetBytes("\"officeLocation\""));
77 | var parseNode = await _parseNodeFactory.GetRootParseNodeAsync(contentType, payload);
78 |
79 | // When
80 | var result = parseNode.GetObjectValue(IntersectionTypeMock.CreateFromDiscriminator);
81 |
82 | // Then
83 | Assert.NotNull(result);
84 | Assert.Null(result.ComposedType2);
85 | Assert.Null(result.ComposedType1);
86 | Assert.Null(result.ComposedType3);
87 | Assert.Equal("officeLocation", result.StringValue);
88 | }
89 | [Fact]
90 | public void SerializesIntersectionTypeStringValue()
91 | {
92 | // Given
93 | using var writer = _serializationWriterFactory.GetSerializationWriter(contentType);
94 | var model = new IntersectionTypeMock {
95 | StringValue = "officeLocation"
96 | };
97 |
98 | // When
99 | model.Serialize(writer);
100 | using var resultStream = writer.GetSerializedContent();
101 | using var streamReader = new StreamReader(resultStream);
102 | var result = streamReader.ReadToEnd();
103 |
104 | // Then
105 | Assert.Equal("\"officeLocation\"", result);
106 | }
107 | [Fact]
108 | public void SerializesIntersectionTypeComplexProperty1()
109 | {
110 | // Given
111 | using var writer = _serializationWriterFactory.GetSerializationWriter(contentType);
112 | var model = new IntersectionTypeMock {
113 | ComposedType1 = new() {
114 | Id = "opaque",
115 | OfficeLocation = "Montreal",
116 | },
117 | ComposedType2 = new() {
118 | DisplayName = "McGill",
119 | },
120 | };
121 |
122 | // When
123 | model.Serialize(writer);
124 | using var resultStream = writer.GetSerializedContent();
125 | using var streamReader = new StreamReader(resultStream);
126 | var result = streamReader.ReadToEnd();
127 |
128 | // Then
129 | Assert.Equal("{\"id\":\"opaque\",\"officeLocation\":\"Montreal\",\"displayName\":\"McGill\"}", result);
130 | }
131 | [Fact]
132 | public void SerializesIntersectionTypeComplexProperty2()
133 | {
134 | // Given
135 | using var writer = _serializationWriterFactory.GetSerializationWriter(contentType);
136 | var model = new IntersectionTypeMock {
137 | ComposedType2 = new() {
138 | DisplayName = "McGill",
139 | Id = 10,
140 | },
141 | };
142 |
143 | // When
144 | model.Serialize(writer);
145 | using var resultStream = writer.GetSerializedContent();
146 | using var streamReader = new StreamReader(resultStream);
147 | var result = streamReader.ReadToEnd();
148 |
149 | // Then
150 | Assert.Equal("{\"displayName\":\"McGill\",\"id\":10}", result);
151 | }
152 |
153 | [Fact]
154 | public void SerializesIntersectionTypeComplexProperty3()
155 | {
156 | // Given
157 | using var writer = _serializationWriterFactory.GetSerializationWriter(contentType);
158 | var model = new IntersectionTypeMock {
159 | ComposedType3 = new() {
160 | new() {
161 | OfficeLocation = "Montreal",
162 | Id = "10",
163 | },
164 | new() {
165 | OfficeLocation = "Ottawa",
166 | Id = "11",
167 | }
168 | },
169 | };
170 |
171 | // When
172 | model.Serialize(writer);
173 | using var resultStream = writer.GetSerializedContent();
174 | using var streamReader = new StreamReader(resultStream);
175 | var result = streamReader.ReadToEnd();
176 |
177 | // Then
178 | Assert.Equal("[{\"id\":\"10\",\"officeLocation\":\"Montreal\"},{\"id\":\"11\",\"officeLocation\":\"Ottawa\"}]", result);
179 | }
180 | }
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/JsonAsyncParseNodeFactoryTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 | using Xunit;
6 |
7 | namespace Microsoft.Kiota.Serialization.Json.Tests
8 | {
9 | public class JsonAsyncParseNodeFactoryTests
10 | {
11 | private readonly JsonParseNodeFactory _jsonParseNodeFactory;
12 | private const string TestJsonString = "{\"key\":\"value\"}";
13 |
14 | public JsonAsyncParseNodeFactoryTests()
15 | {
16 | _jsonParseNodeFactory = new JsonParseNodeFactory();
17 | }
18 |
19 | [Fact]
20 | public async Task GetsWriterForJsonContentType()
21 | {
22 | using var jsonStream = new MemoryStream(Encoding.UTF8.GetBytes(TestJsonString));
23 | var jsonWriter = await _jsonParseNodeFactory.GetRootParseNodeAsync(_jsonParseNodeFactory.ValidContentType, jsonStream);
24 |
25 | // Assert
26 | Assert.NotNull(jsonWriter);
27 | Assert.IsAssignableFrom(jsonWriter);
28 | }
29 |
30 | [Fact]
31 | public async Task ThrowsArgumentOutOfRangeExceptionForInvalidContentType()
32 | {
33 | var streamContentType = "application/octet-stream";
34 | using var jsonStream = new MemoryStream(Encoding.UTF8.GetBytes(TestJsonString));
35 | var exception = await Assert.ThrowsAsync(
36 | async () => await _jsonParseNodeFactory.GetRootParseNodeAsync(streamContentType, jsonStream));
37 |
38 | // Assert
39 | Assert.NotNull(exception);
40 | Assert.Equal($"expected a {_jsonParseNodeFactory.ValidContentType} content type", exception.ParamName);
41 | }
42 |
43 | [Theory]
44 | [InlineData(null)]
45 | [InlineData("")]
46 | public async Task ThrowsArgumentNullExceptionForNoContentType(string contentType)
47 | {
48 | using var jsonStream = new MemoryStream(Encoding.UTF8.GetBytes(TestJsonString));
49 | var exception = await Assert.ThrowsAsync(
50 | async () => await _jsonParseNodeFactory.GetRootParseNodeAsync(contentType, jsonStream));
51 |
52 | // Assert
53 | Assert.NotNull(exception);
54 | Assert.Equal("contentType", exception.ParamName);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/JsonParseNodeFactoryTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 | using Xunit;
6 |
7 | namespace Microsoft.Kiota.Serialization.Json.Tests
8 | {
9 | public class JsonParseNodeFactoryTests
10 | {
11 | private readonly JsonParseNodeFactory _jsonParseNodeFactory;
12 | private const string TestJsonString = "{\"key\":\"value\"}";
13 |
14 | public JsonParseNodeFactoryTests()
15 | {
16 | _jsonParseNodeFactory = new JsonParseNodeFactory();
17 | }
18 |
19 | [Fact]
20 | public async Task GetsWriterForJsonContentType()
21 | {
22 | using var jsonStream = new MemoryStream(Encoding.UTF8.GetBytes(TestJsonString));
23 | var jsonWriter = await _jsonParseNodeFactory.GetRootParseNodeAsync(_jsonParseNodeFactory.ValidContentType,jsonStream);
24 |
25 | // Assert
26 | Assert.NotNull(jsonWriter);
27 | Assert.IsAssignableFrom(jsonWriter);
28 | }
29 |
30 | [Fact]
31 | public async Task ThrowsArgumentOutOfRangeExceptionForInvalidContentType()
32 | {
33 | var streamContentType = "application/octet-stream";
34 | using var jsonStream = new MemoryStream(Encoding.UTF8.GetBytes(TestJsonString));
35 | var exception = await Assert.ThrowsAsync(async () => await _jsonParseNodeFactory.GetRootParseNodeAsync(streamContentType,jsonStream));
36 |
37 | // Assert
38 | Assert.NotNull(exception);
39 | Assert.Equal($"expected a {_jsonParseNodeFactory.ValidContentType} content type", exception.ParamName);
40 | }
41 |
42 | [Theory]
43 | [InlineData(null)]
44 | [InlineData("")]
45 | public async Task ThrowsArgumentNullExceptionForNoContentType(string contentType)
46 | {
47 | using var jsonStream = new MemoryStream(Encoding.UTF8.GetBytes(TestJsonString));
48 | var exception = await Assert.ThrowsAsync(async () => await _jsonParseNodeFactory.GetRootParseNodeAsync(contentType,jsonStream));
49 |
50 | // Assert
51 | Assert.NotNull(exception);
52 | Assert.Equal("contentType", exception.ParamName);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/JsonParseNodeTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Text.Json;
4 | using Microsoft.Kiota.Abstractions;
5 | using Microsoft.Kiota.Abstractions.Serialization;
6 | using Microsoft.Kiota.Serialization.Json.Tests.Converters;
7 | using Microsoft.Kiota.Serialization.Json.Tests.Mocks;
8 | using Xunit;
9 |
10 | namespace Microsoft.Kiota.Serialization.Json.Tests
11 | {
12 | public class JsonParseNodeTests
13 | {
14 | private const string TestUserJson = "{\r\n" +
15 | " \"@odata.context\": \"https://graph.microsoft.com/v1.0/$metadata#users/$entity\",\r\n" +
16 | " \"@odata.id\": \"https://graph.microsoft.com/v2/dcd219dd-bc68-4b9b-bf0b-4a33a796be35/directoryObjects/48d31887-5fad-4d73-a9f5-3c356e68a038/Microsoft.DirectoryServices.User\",\r\n" +
17 | " \"businessPhones\": [\r\n" +
18 | " \"+1 412 555 0109\"\r\n" +
19 | " ],\r\n" +
20 | " \"displayName\": \"Megan Bowen\",\r\n" +
21 | " \"numbers\":\"one,two,thirtytwo\"," +
22 | " \"testNamingEnum\":\"Item2:SubItem1\"," +
23 | " \"givenName\": \"Megan\",\r\n" +
24 | " \"accountEnabled\": true,\r\n" +
25 | " \"createdDateTime\": \"2017 -07-29T03:07:25Z\",\r\n" +
26 | " \"jobTitle\": \"Auditor\",\r\n" +
27 | " \"mail\": \"MeganB@M365x214355.onmicrosoft.com\",\r\n" +
28 | " \"mobilePhone\": null,\r\n" +
29 | " \"officeLocation\": null,\r\n" +
30 | " \"preferredLanguage\": \"en-US\",\r\n" +
31 | " \"surname\": \"Bowen\",\r\n" +
32 | " \"workDuration\": \"PT1H\",\r\n" +
33 | " \"startWorkTime\": \"08:00:00.0000000\",\r\n" +
34 | " \"endWorkTime\": \"17:00:00.0000000\",\r\n" +
35 | " \"userPrincipalName\": \"MeganB@M365x214355.onmicrosoft.com\",\r\n" +
36 | " \"birthDay\": \"2017-09-04\",\r\n" +
37 | " \"id\": \"48d31887-5fad-4d73-a9f5-3c356e68a038\"\r\n" +
38 | "}";
39 | private const string TestStudentJson = "{\r\n" +
40 | " \"@odata.context\": \"https://graph.microsoft.com/v1.0/$metadata#users/$entity\",\r\n" +
41 | " \"@odata.type\": \"microsoft.graph.student\",\r\n" +
42 | " \"@odata.id\": \"https://graph.microsoft.com/v2/dcd219dd-bc68-4b9b-bf0b-4a33a796be35/directoryObjects/48d31887-5fad-4d73-a9f5-3c356e68a038/Microsoft.DirectoryServices.User\",\r\n" +
43 | " \"businessPhones\": [\r\n" +
44 | " \"+1 412 555 0109\"\r\n" +
45 | " ],\r\n" +
46 | " \"displayName\": \"Megan Bowen\",\r\n" +
47 | " \"numbers\":\"one,two,thirtytwo\"," +
48 | " \"testNamingEnum\":\"Item2:SubItem1\"," +
49 | " \"givenName\": \"Megan\",\r\n" +
50 | " \"accountEnabled\": true,\r\n" +
51 | " \"createdDateTime\": \"2017 -07-29T03:07:25Z\",\r\n" +
52 | " \"jobTitle\": \"Auditor\",\r\n" +
53 | " \"mail\": \"MeganB@M365x214355.onmicrosoft.com\",\r\n" +
54 | " \"mobilePhone\": null,\r\n" +
55 | " \"officeLocation\": null,\r\n" +
56 | " \"preferredLanguage\": \"en-US\",\r\n" +
57 | " \"surname\": \"Bowen\",\r\n" +
58 | " \"workDuration\": \"PT1H\",\r\n" +
59 | " \"startWorkTime\": \"08:00:00.0000000\",\r\n" +
60 | " \"endWorkTime\": \"17:00:00.0000000\",\r\n" +
61 | " \"userPrincipalName\": \"MeganB@M365x214355.onmicrosoft.com\",\r\n" +
62 | " \"birthDay\": \"2017-09-04\",\r\n" +
63 | " \"enrolmentDate\": \"2017-09-04\",\r\n" +
64 | " \"id\": \"48d31887-5fad-4d73-a9f5-3c356e68a038\"\r\n" +
65 | "}";
66 |
67 | private const string TestUntypedJson = "{\r\n" +
68 | " \"@odata.context\": \"https://graph.microsoft.com/v1.0/$metadata#sites('contoso.sharepoint.com')/lists('fa631c4d-ac9f-4884-a7f5-13c659d177e3')/items('1')/fields/$entity\",\r\n" +
69 | " \"id\": \"5\",\r\n" +
70 | " \"title\": \"Project 101\",\r\n" +
71 | " \"location\": {\r\n" +
72 | " \"address\": {\r\n" +
73 | " \"city\": \"Redmond\",\r\n" +
74 | " \"postalCode\": \"98052\",\r\n" +
75 | " \"state\": \"Washington\",\r\n" +
76 | " \"street\": \"NE 36th St\"\r\n" +
77 | " },\r\n" +
78 | " \"coordinates\": {\r\n" +
79 | " \"latitude\": 47.641942,\r\n" +
80 | " \"longitude\": -122.127222\r\n" +
81 | " },\r\n" +
82 | " \"displayName\": \"Microsoft Building 92\",\r\n" +
83 | " \"floorCount\": 50,\r\n" +
84 | " \"hasReception\": true,\r\n" +
85 | " \"contact\": null\r\n" +
86 | " },\r\n" +
87 | " \"keywords\": [\r\n" +
88 | " {\r\n" +
89 | " \"created\": \"2023-07-26T10:41:26Z\",\r\n" +
90 | " \"label\": \"Keyword1\",\r\n" +
91 | " \"termGuid\": \"10e9cc83-b5a4-4c8d-8dab-4ada1252dd70\",\r\n" +
92 | " \"wssId\": 6442450942\r\n" +
93 | " },\r\n" +
94 | " {\r\n" +
95 | " \"created\": \"2023-07-26T10:51:26Z\",\r\n" +
96 | " \"label\": \"Keyword2\",\r\n" +
97 | " \"termGuid\": \"2cae6c6a-9bb8-4a78-afff-81b88e735fef\",\r\n" +
98 | " \"wssId\": 6442450943\r\n" +
99 | " }\r\n" +
100 | " ],\r\n" +
101 | " \"detail\": null,\r\n" +
102 | " \"table\": [[1,2,3],[4,5,6],[7,8,9]],\r\n" +
103 | " \"extra\": {\r\n" +
104 | " \"createdDateTime\":\"2024-01-15T00:00:00\\u002B00:00\"\r\n" +
105 | " }\r\n" +
106 | "}";
107 |
108 | private static readonly string TestUserCollectionString = $"[{TestUserJson}]";
109 |
110 | [Fact]
111 | public void GetsEntityValueFromJson()
112 | {
113 | // Arrange
114 | using var jsonDocument = JsonDocument.Parse(TestUserJson);
115 | var jsonParseNode = new JsonParseNode(jsonDocument.RootElement);
116 | // Act
117 | var testEntity = jsonParseNode.GetObjectValue(TestEntity.CreateFromDiscriminator);
118 | // Assert
119 | Assert.NotNull(testEntity);
120 | Assert.Null(testEntity.OfficeLocation);
121 | Assert.NotEmpty(testEntity.AdditionalData);
122 | Assert.True(testEntity.AdditionalData.ContainsKey("jobTitle"));
123 | Assert.True(testEntity.AdditionalData.ContainsKey("mobilePhone"));
124 | Assert.Equal("Auditor", testEntity.AdditionalData["jobTitle"]);
125 | Assert.Equal("48d31887-5fad-4d73-a9f5-3c356e68a038", testEntity.Id);
126 | Assert.Equal(TestEnum.One | TestEnum.Two, testEntity.Numbers ); // Unknown enum value is not included
127 | Assert.Equal(TestNamingEnum.Item2SubItem1, testEntity.TestNamingEnum ); // correct value is chosen
128 | Assert.Equal(TimeSpan.FromHours(1), testEntity.WorkDuration); // Parses timespan values
129 | Assert.Equal(new Time(8,0,0).ToString(),testEntity.StartWorkTime.ToString());// Parses time values
130 | Assert.Equal(new Time(17, 0, 0).ToString(), testEntity.EndWorkTime.ToString());// Parses time values
131 | Assert.Equal(new Date(2017,9,4).ToString(), testEntity.BirthDay.ToString());// Parses date values
132 | }
133 | [Fact]
134 | public void GetsFieldFromDerivedType()
135 | {
136 | // Arrange
137 | using var jsonDocument = JsonDocument.Parse(TestStudentJson);
138 | var jsonParseNode = new JsonParseNode(jsonDocument.RootElement);
139 | // Act
140 | var testEntity = jsonParseNode.GetObjectValue(TestEntity.CreateFromDiscriminator) as DerivedTestEntity;
141 | // Assert
142 | Assert.NotNull(testEntity);
143 | Assert.NotNull(testEntity.EnrolmentDate);
144 | }
145 |
146 | [Fact]
147 | public void GetCollectionOfObjectValuesFromJson()
148 | {
149 | // Arrange
150 | using var jsonDocument = JsonDocument.Parse(TestUserCollectionString);
151 | var jsonParseNode = new JsonParseNode(jsonDocument.RootElement);
152 | // Act
153 | var testEntityCollection = jsonParseNode.GetCollectionOfObjectValues(x => new TestEntity()).ToArray();
154 | // Assert
155 | Assert.NotEmpty(testEntityCollection);
156 | Assert.Equal("48d31887-5fad-4d73-a9f5-3c356e68a038", testEntityCollection[0].Id);
157 | }
158 |
159 | [Fact]
160 | public void GetsChildNodeAndGetCollectionOfPrimitiveValuesFromJsonParseNode()
161 | {
162 | // Arrange
163 | using var jsonDocument = JsonDocument.Parse(TestUserJson);
164 | var rootParseNode = new JsonParseNode(jsonDocument.RootElement);
165 | // Act to get business phones list
166 | var phonesListChildNode = rootParseNode.GetChildNode("businessPhones");
167 | var phonesList = phonesListChildNode.GetCollectionOfPrimitiveValues().ToArray();
168 | // Assert
169 | Assert.NotEmpty(phonesList);
170 | Assert.Equal("+1 412 555 0109", phonesList[0]);
171 | }
172 |
173 | [Fact]
174 | public void ReturnsDefaultIfChildNodeDoesNotExist()
175 | {
176 | // Arrange
177 | using var jsonDocument = JsonDocument.Parse(TestUserJson);
178 | var rootParseNode = new JsonParseNode(jsonDocument.RootElement);
179 | // Try to get an imaginary node value
180 | var imaginaryNode = rootParseNode.GetChildNode("imaginaryNode");
181 | // Assert
182 | Assert.Null(imaginaryNode);
183 | }
184 |
185 | [Fact]
186 | public void ParseGuidWithConverter()
187 | {
188 | // Arrange
189 | var id = Guid.NewGuid();
190 | var json = $"{{\"id\": \"{id:N}\"}}";
191 | var serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.General)
192 | {
193 | Converters = { new JsonGuidConverter() }
194 | };
195 | var serializationContext = new KiotaJsonSerializationContext(serializerOptions);
196 | using var jsonDocument = JsonDocument.Parse(json);
197 | var rootParseNode = new JsonParseNode(jsonDocument.RootElement, serializationContext);
198 |
199 | // Act
200 | var entity = rootParseNode.GetObjectValue(_ => new ConverterTestEntity());
201 |
202 | // Assert
203 | Assert.Equal(id, entity.Id);
204 | }
205 |
206 | [Fact]
207 | public void ParseGuidWithoutConverter()
208 | {
209 | // Arrange
210 | var id = Guid.NewGuid();
211 | var json = $"{{\"id\": \"{id:D}\"}}";
212 | var serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.General);
213 | var serializationContext = new KiotaJsonSerializationContext(serializerOptions);
214 | using var jsonDocument = JsonDocument.Parse(json);
215 | var rootParseNode = new JsonParseNode(jsonDocument.RootElement, serializationContext);
216 |
217 | // Act
218 | var entity = rootParseNode.GetObjectValue(_ => new ConverterTestEntity());
219 |
220 | // Assert
221 | Assert.Equal(id, entity.Id);
222 | }
223 |
224 | [Fact]
225 | public void ParseGuidEmptyString()
226 | {
227 | // Arrange
228 | var json = $"{{\"id\": \"\"}}";
229 | var serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.General);
230 | var serializationContext = new KiotaJsonSerializationContext(serializerOptions);
231 | using var jsonDocument = JsonDocument.Parse(json);
232 | var rootParseNode = new JsonParseNode(jsonDocument.RootElement, serializationContext);
233 |
234 | // Act
235 | var entity = rootParseNode.GetObjectValue(_ => new ConverterTestEntity());
236 |
237 | // Assert
238 | Assert.Null(entity.Id);
239 | }
240 |
241 | [Fact]
242 | public void GetEntityWithUntypedNodesFromJson()
243 | {
244 | // Arrange
245 | using var jsonDocument = JsonDocument.Parse(TestUntypedJson);
246 | var rootParseNode = new JsonParseNode(jsonDocument.RootElement);
247 | // Act
248 | var entity = rootParseNode.GetObjectValue(UntypedTestEntity.CreateFromDiscriminatorValue);
249 | // Assert
250 | Assert.NotNull(entity);
251 | Assert.Equal("5", entity.Id);
252 | Assert.Equal("Project 101", entity.Title);
253 | Assert.NotNull(entity.Location);
254 | Assert.IsType(entity.Location); // creates untyped object
255 | var location = (UntypedObject)entity.Location;
256 | var locationProperties = location.GetValue();
257 | Assert.IsType(locationProperties["address"]);
258 | Assert.IsType(locationProperties["displayName"]); // creates untyped string
259 | Assert.IsType(locationProperties["floorCount"]); // creates untyped number
260 | Assert.IsType(locationProperties["hasReception"]); // creates untyped boolean
261 | Assert.IsType(locationProperties["contact"]); // creates untyped null
262 | Assert.IsType(locationProperties["coordinates"]); // creates untyped null
263 | var coordinates = (UntypedObject)locationProperties["coordinates"];
264 | var coordinatesProperties = coordinates.GetValue();
265 | Assert.IsType(coordinatesProperties["latitude"]); // creates untyped decimal
266 | Assert.IsType(coordinatesProperties["longitude"]);
267 | Assert.Equal("Microsoft Building 92", ((UntypedString)locationProperties["displayName"]).GetValue());
268 | Assert.Equal(50, ((UntypedInteger)locationProperties["floorCount"]).GetValue());
269 | Assert.True(((UntypedBoolean)locationProperties["hasReception"]).GetValue());
270 | Assert.Null(((UntypedNull)locationProperties["contact"]).GetValue());
271 | Assert.NotNull(entity.Keywords);
272 | Assert.IsType(entity.Keywords); // creates untyped array
273 | Assert.Equal(2, ((UntypedArray)entity.Keywords).GetValue().Count());
274 | Assert.Null(entity.Detail);
275 | var extra = entity.AdditionalData["extra"];
276 | Assert.NotNull(extra);
277 | Assert.NotNull(entity.Table);
278 | var table = (UntypedArray)entity.Table;// the table is a collection
279 | foreach(var value in table.GetValue())
280 | {
281 | var row = (UntypedArray)value;
282 | Assert.NotNull(row);// The values are a nested collection
283 | foreach(var item in row.GetValue())
284 | {
285 | var rowItem = (UntypedInteger)item;
286 | Assert.NotNull(rowItem);// The values are a nested collection
287 | }
288 | }
289 | }
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/JsonSerializationWriterFactoryTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Xunit;
3 |
4 | namespace Microsoft.Kiota.Serialization.Json.Tests
5 | {
6 | public class JsonSerializationWriterFactoryTests
7 | {
8 | private readonly JsonSerializationWriterFactory _jsonSerializationFactory;
9 |
10 | public JsonSerializationWriterFactoryTests()
11 | {
12 | _jsonSerializationFactory = new JsonSerializationWriterFactory();
13 | }
14 |
15 | [Fact]
16 | public void GetsWriterForJsonContentType()
17 | {
18 | var jsonWriter = _jsonSerializationFactory.GetSerializationWriter(_jsonSerializationFactory.ValidContentType);
19 |
20 | // Assert
21 | Assert.NotNull(jsonWriter);
22 | Assert.IsAssignableFrom(jsonWriter);
23 | }
24 |
25 | [Fact]
26 | public void ThrowsArgumentOutOfRangeExceptionForInvalidContentType()
27 | {
28 | var streamContentType = "application/octet-stream";
29 | var exception = Assert.Throws(() => _jsonSerializationFactory.GetSerializationWriter(streamContentType));
30 |
31 | // Assert
32 | Assert.NotNull(exception);
33 | Assert.Equal($"expected a {_jsonSerializationFactory.ValidContentType} content type", exception.ParamName);
34 | }
35 |
36 | [Theory]
37 | [InlineData(null)]
38 | [InlineData("")]
39 | public void ThrowsArgumentNullExceptionForNoContentType(string contentType)
40 | {
41 | var exception = Assert.Throws(() => _jsonSerializationFactory.GetSerializationWriter(contentType));
42 |
43 | // Assert
44 | Assert.NotNull(exception);
45 | Assert.Equal("contentType", exception.ParamName);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/JsonSerializationWriterTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Text.Json;
6 | using Microsoft.Kiota.Abstractions;
7 | using Microsoft.Kiota.Abstractions.Serialization;
8 | using Microsoft.Kiota.Serialization.Json.Tests.Converters;
9 | using Microsoft.Kiota.Serialization.Json.Tests.Mocks;
10 | using Xunit;
11 |
12 | namespace Microsoft.Kiota.Serialization.Json.Tests
13 | {
14 | public class JsonSerializationWriterTests
15 | {
16 | [Fact]
17 | public void WritesSampleObjectValue()
18 | {
19 | // Arrange
20 | var testEntity = new TestEntity()
21 | {
22 | Id = "48d31887-5fad-4d73-a9f5-3c356e68a038",
23 | WorkDuration = TimeSpan.FromHours(1),
24 | StartWorkTime = new Time(8, 0, 0),
25 | BirthDay = new Date(2017, 9, 4),
26 | HeightInMetres = 1.80m,
27 | AdditionalData = new Dictionary
28 | {
29 | {"mobilePhone",null}, // write null value
30 | {"accountEnabled",false}, // write bool value
31 | {"jobTitle","Author"}, // write string value
32 | {"createdDateTime", DateTimeOffset.MinValue}, // write date value
33 | {"weightInKgs", 51.80m}, // write weigth
34 | {"businessPhones", new List() {"+1 412 555 0109"}}, // write collection of primitives value
35 | {"endDateTime", new DateTime(2023,03,14,0,0,0,DateTimeKind.Utc) }, // ensure the DateTime doesn't crash
36 | {"manager", new TestEntity{Id = "48d31887-5fad-4d73-a9f5-3c356e68a038"}}, // write nested object value
37 | }
38 | };
39 | using var jsonSerializerWriter = new JsonSerializationWriter();
40 | // Act
41 | jsonSerializerWriter.WriteObjectValue(string.Empty,testEntity);
42 | // Get the json string from the stream.
43 | var serializedStream = jsonSerializerWriter.GetSerializedContent();
44 | using var reader = new StreamReader(serializedStream, Encoding.UTF8);
45 | var serializedJsonString = reader.ReadToEnd();
46 |
47 | // Assert
48 | var expectedString = "{" +
49 | "\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"," +
50 | "\"workDuration\":\"PT1H\","+ // Serializes timespans
51 | "\"birthDay\":\"2017-09-04\"," + // Serializes dates
52 | "\"heightInMetres\":1.80,"+
53 | "\"startWorkTime\":\"08:00:00\"," + //Serializes times
54 | "\"mobilePhone\":null," +
55 | "\"accountEnabled\":false," +
56 | "\"jobTitle\":\"Author\"," +
57 | "\"createdDateTime\":\"0001-01-01T00:00:00+00:00\"," +
58 | "\"weightInKgs\":51.80,"+
59 | "\"businessPhones\":[\"\\u002B1 412 555 0109\"]," +
60 | "\"endDateTime\":\"2023-03-14T00:00:00+00:00\"," +
61 | "\"manager\":{\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"}" +
62 | "}";
63 | Assert.Equal(expectedString, serializedJsonString);
64 | }
65 |
66 | [Fact]
67 | public void WritesSampleObjectValueWithJsonElementAdditionalData()
68 | {
69 | var nullJsonElement = JsonDocument.Parse("null").RootElement;
70 | var arrayJsonElement = JsonDocument.Parse("[\"+1 412 555 0109\"]").RootElement;
71 | var objectJsonElement = JsonDocument.Parse("{\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"}").RootElement;
72 |
73 | // Arrange
74 | var testEntity = new TestEntity()
75 | {
76 | Id = "48d31887-5fad-4d73-a9f5-3c356e68a038",
77 | WorkDuration = TimeSpan.FromHours(1),
78 | StartWorkTime = new Time(8, 0, 0),
79 | BirthDay = new Date(2017, 9, 4),
80 | AdditionalData = new Dictionary
81 | {
82 | {"mobilePhone", nullJsonElement}, // write null value
83 | {"accountEnabled",false}, // write bool value
84 | {"jobTitle","Author"}, // write string value
85 | {"createdDateTime", DateTimeOffset.MinValue}, // write date value
86 | {"businessPhones", arrayJsonElement }, // write collection of primitives value
87 | {"manager", objectJsonElement }, // write nested object value
88 | }
89 | };
90 | using var jsonSerializerWriter = new JsonSerializationWriter();
91 | // Act
92 | jsonSerializerWriter.WriteObjectValue(string.Empty, testEntity);
93 | // Get the json string from the stream.
94 | var serializedStream = jsonSerializerWriter.GetSerializedContent();
95 | using var reader = new StreamReader(serializedStream, Encoding.UTF8);
96 | var serializedJsonString = reader.ReadToEnd();
97 |
98 | // Assert
99 | var expectedString = "{" +
100 | "\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"," +
101 | "\"workDuration\":\"PT1H\"," + // Serializes timespans
102 | "\"birthDay\":\"2017-09-04\"," + // Serializes dates
103 | "\"startWorkTime\":\"08:00:00\"," + //Serializes times
104 | "\"mobilePhone\":null," +
105 | "\"accountEnabled\":false," +
106 | "\"jobTitle\":\"Author\"," +
107 | "\"createdDateTime\":\"0001-01-01T00:00:00+00:00\"," +
108 | "\"businessPhones\":[\"\\u002B1 412 555 0109\"]," +
109 | "\"manager\":{\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"}" +
110 | "}";
111 | Assert.Equal(expectedString, serializedJsonString);
112 | }
113 |
114 | [Fact]
115 | public void WritesSampleCollectionOfObjectValues()
116 | {
117 | // Arrange
118 | var testEntity = new TestEntity()
119 | {
120 | Id = "48d31887-5fad-4d73-a9f5-3c356e68a038",
121 | Numbers = TestEnum.One | TestEnum.Two,
122 | TestNamingEnum = TestNamingEnum.Item2SubItem1,
123 | AdditionalData = new Dictionary
124 | {
125 | {"mobilePhone",null}, // write null value
126 | {"accountEnabled",false}, // write bool value
127 | {"jobTitle","Author"}, // write string value
128 | {"createdDateTime", DateTimeOffset.MinValue}, // write date value
129 | {"businessPhones", new List() {"+1 412 555 0109"}}, // write collection of primitives value
130 | {"manager", new TestEntity{Id = "48d31887-5fad-4d73-a9f5-3c356e68a038"}}, // write nested object value
131 | }
132 | };
133 | var entityList = new List() { testEntity};
134 | using var jsonSerializerWriter = new JsonSerializationWriter();
135 | // Act
136 | jsonSerializerWriter.WriteCollectionOfObjectValues(string.Empty, entityList);
137 | // Get the json string from the stream.
138 | var serializedStream = jsonSerializerWriter.GetSerializedContent();
139 | using var reader = new StreamReader(serializedStream, Encoding.UTF8);
140 | var serializedJsonString = reader.ReadToEnd();
141 |
142 | // Assert
143 | var expectedString = "[{" +
144 | "\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"," +
145 | "\"numbers\":\"one,two\"," +
146 | "\"testNamingEnum\":\"Item2:SubItem1\"," +
147 | "\"mobilePhone\":null," +
148 | "\"accountEnabled\":false," +
149 | "\"jobTitle\":\"Author\"," +
150 | "\"createdDateTime\":\"0001-01-01T00:00:00+00:00\"," +
151 | "\"businessPhones\":[\"\\u002B1 412 555 0109\"]," +
152 | "\"manager\":{\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"}" +
153 | "}]";
154 | Assert.Equal(expectedString, serializedJsonString);
155 | }
156 |
157 | [Fact]
158 | public void WritesEnumValuesAsCamelCasedIfNotEscaped()
159 | {
160 | // Arrange
161 | var testEntity = new TestEntity()
162 | {
163 | TestNamingEnum = TestNamingEnum.Item1,
164 | };
165 | var entityList = new List() { testEntity };
166 | using var jsonSerializerWriter = new JsonSerializationWriter();
167 | // Act
168 | jsonSerializerWriter.WriteCollectionOfObjectValues(string.Empty, entityList);
169 | // Get the json string from the stream.
170 | var serializedStream = jsonSerializerWriter.GetSerializedContent();
171 | using var reader = new StreamReader(serializedStream, Encoding.UTF8);
172 | var serializedJsonString = reader.ReadToEnd();
173 |
174 | // Assert
175 | var expectedString = "[{" +
176 | "\"testNamingEnum\":\"item1\"" + // Camel Cased
177 | "}]";
178 | Assert.Equal(expectedString, serializedJsonString);
179 | }
180 |
181 | [Fact]
182 | public void WritesEnumValuesAsDescribedIfEscaped()
183 | {
184 | // Arrange
185 | var testEntity = new TestEntity()
186 | {
187 | TestNamingEnum = TestNamingEnum.Item2SubItem1,
188 | };
189 | var entityList = new List() { testEntity };
190 | using var jsonSerializerWriter = new JsonSerializationWriter();
191 | // Act
192 | jsonSerializerWriter.WriteCollectionOfObjectValues(string.Empty, entityList);
193 | // Get the json string from the stream.
194 | var serializedStream = jsonSerializerWriter.GetSerializedContent();
195 | using var reader = new StreamReader(serializedStream, Encoding.UTF8);
196 | var serializedJsonString = reader.ReadToEnd();
197 |
198 | // Assert
199 | var expectedString = "[{" +
200 | "\"testNamingEnum\":\"Item2:SubItem1\"" + // Appears same as attribute
201 | "}]";
202 | Assert.Equal(expectedString, serializedJsonString);
203 | }
204 |
205 | [Fact]
206 | public void WriteGuidUsingConverter()
207 | {
208 | // Arrange
209 | var id = Guid.NewGuid();
210 | var testEntity = new ConverterTestEntity { Id = id };
211 | var serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.General)
212 | {
213 | Converters = { new JsonGuidConverter() }
214 | };
215 | var serializationContext = new KiotaJsonSerializationContext(serializerOptions);
216 | using var jsonSerializerWriter = new JsonSerializationWriter(serializationContext);
217 |
218 | // Act
219 | jsonSerializerWriter.WriteObjectValue(string.Empty, testEntity);
220 | var serializedStream = jsonSerializerWriter.GetSerializedContent();
221 | using var reader = new StreamReader(serializedStream, Encoding.UTF8);
222 | var serializedJsonString = reader.ReadToEnd();
223 |
224 | // Assert
225 | var expectedString = $"{{\"id\":\"{id:N}\"}}";
226 | Assert.Equal(expectedString, serializedJsonString);
227 | }
228 |
229 | [Fact]
230 | public void WriteGuidUsingNoConverter()
231 | {
232 | // Arrange
233 | var id = Guid.NewGuid();
234 | var testEntity = new ConverterTestEntity { Id = id };
235 | var serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.General);
236 | var serializationContext = new KiotaJsonSerializationContext(serializerOptions);
237 | using var jsonSerializerWriter = new JsonSerializationWriter(serializationContext);
238 |
239 | // Act
240 | jsonSerializerWriter.WriteObjectValue(string.Empty, testEntity);
241 | var serializedStream = jsonSerializerWriter.GetSerializedContent();
242 | using var reader = new StreamReader(serializedStream, Encoding.UTF8);
243 | var serializedJsonString = reader.ReadToEnd();
244 |
245 | // Assert
246 | var expectedString = $"{{\"id\":\"{id:D}\"}}";
247 | Assert.Equal(expectedString, serializedJsonString);
248 | }
249 | [Fact]
250 | public void WritesSampleObjectValueWithUntypedProperties()
251 | {
252 | // Arrange
253 | var untypedTestEntity = new UntypedTestEntity
254 | {
255 | Id = "1",
256 | Title = "Title",
257 | Location = new UntypedObject(new Dictionary
258 | {
259 | {"address", new UntypedObject(new Dictionary
260 | {
261 | {"city", new UntypedString("Redmond") },
262 | {"postalCode", new UntypedString("98052") },
263 | {"state", new UntypedString("Washington") },
264 | {"street", new UntypedString("NE 36th St") }
265 | })},
266 | {"coordinates", new UntypedObject(new Dictionary
267 | {
268 | {"latitude", new UntypedDouble(47.641942d) },
269 | {"longitude", new UntypedDouble(-122.127222d) }
270 | })},
271 | {"displayName", new UntypedString("Microsoft Building 92") },
272 | {"floorCount", new UntypedInteger(50) },
273 | {"hasReception", new UntypedBoolean(true) },
274 | {"contact", new UntypedNull() }
275 | }),
276 | Keywords = new UntypedArray(new List
277 | {
278 | new UntypedObject(new Dictionary
279 | {
280 | {"created", new UntypedString("2023-07-26T10:41:26Z") },
281 | {"label", new UntypedString("Keyword1") },
282 | {"termGuid", new UntypedString("10e9cc83-b5a4-4c8d-8dab-4ada1252dd70") },
283 | {"wssId", new UntypedLong(6442450941) }
284 | }),
285 | new UntypedObject(new Dictionary
286 | {
287 | {"created", new UntypedString("2023-07-26T10:51:26Z") },
288 | {"label", new UntypedString("Keyword2") },
289 | {"termGuid", new UntypedString("2cae6c6a-9bb8-4a78-afff-81b88e735fef") },
290 | {"wssId", new UntypedLong(6442450942) }
291 | })
292 | }),
293 | AdditionalData = new Dictionary
294 | {
295 | { "extra", new UntypedObject(new Dictionary
296 | {
297 | {"createdDateTime", new UntypedString("2024-01-15T00:00:00+00:00") }
298 | }) }
299 | }
300 | };
301 | using var jsonSerializerWriter = new JsonSerializationWriter();
302 | // Act
303 | jsonSerializerWriter.WriteObjectValue(string.Empty, untypedTestEntity);
304 | // Get the json string from the stream.
305 | var serializedStream = jsonSerializerWriter.GetSerializedContent();
306 | using var reader = new StreamReader(serializedStream, Encoding.UTF8);
307 | var serializedJsonString = reader.ReadToEnd();
308 |
309 | // Assert
310 | var expectedString = "{" +
311 | "\"id\":\"1\"," +
312 | "\"title\":\"Title\"," +
313 | "\"location\":{" +
314 | "\"address\":{\"city\":\"Redmond\",\"postalCode\":\"98052\",\"state\":\"Washington\",\"street\":\"NE 36th St\"}," +
315 | "\"coordinates\":{\"latitude\":47.641942,\"longitude\":-122.127222}," +
316 | "\"displayName\":\"Microsoft Building 92\"," +
317 | "\"floorCount\":50," +
318 | "\"hasReception\":true," +
319 | "\"contact\":null}," +
320 | "\"keywords\":[" +
321 | "{\"created\":\"2023-07-26T10:41:26Z\",\"label\":\"Keyword1\",\"termGuid\":\"10e9cc83-b5a4-4c8d-8dab-4ada1252dd70\",\"wssId\":6442450941}," +
322 | "{\"created\":\"2023-07-26T10:51:26Z\",\"label\":\"Keyword2\",\"termGuid\":\"2cae6c6a-9bb8-4a78-afff-81b88e735fef\",\"wssId\":6442450942}]," +
323 | "\"extra\":{\"createdDateTime\":\"2024-01-15T00:00:00\\u002B00:00\"}}";
324 | Assert.Equal(expectedString, serializedJsonString);
325 | }
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Microsoft.Kiota.Serialization.Json.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0;net462
5 | false
6 | latest
7 | Library
8 |
9 |
10 |
11 |
12 | all
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 |
15 |
16 |
17 |
18 |
19 |
20 | runtime; build; native; contentfiles; analyzers; buildtransitive
21 | all
22 |
23 |
24 | runtime; build; native; contentfiles; analyzers; buildtransitive
25 | all
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Mocks/ConverterTestEntity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.Kiota.Abstractions.Serialization;
4 |
5 | namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks;
6 |
7 | public class ConverterTestEntity : IParsable
8 | {
9 | public Guid? Id { get; set; }
10 |
11 | public IDictionary> GetFieldDeserializers() => new Dictionary>
12 | {
13 | { "id", n => Id = n.GetGuidValue() }
14 | };
15 |
16 | public void Serialize(ISerializationWriter writer)
17 | {
18 | _ = writer ?? throw new ArgumentNullException(nameof(writer));
19 | writer.WriteGuidValue("id", Id);
20 | }
21 | }
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Mocks/DerivedTestEntity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.Kiota.Abstractions;
4 | using Microsoft.Kiota.Abstractions.Serialization;
5 |
6 | namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks
7 | {
8 | public class DerivedTestEntity : TestEntity
9 | {
10 | ///
11 | /// Date enrolled in primary school
12 | ///
13 | public Date? EnrolmentDate { get; set; }
14 | public override IDictionary> GetFieldDeserializers()
15 | {
16 | var parentDeserializers = base.GetFieldDeserializers();
17 | parentDeserializers.Add("enrolmentDate", n => { EnrolmentDate = n.GetDateValue(); });
18 | return parentDeserializers;
19 | }
20 | public override void Serialize(ISerializationWriter writer)
21 | {
22 | base.Serialize(writer);
23 | writer.WriteDateValue("enrolmentDate", EnrolmentDate.Value);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Mocks/IntersectionTypeMock.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Microsoft.Kiota.Abstractions.Serialization;
5 |
6 | namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks;
7 |
8 | public class IntersectionTypeMock : IParsable, IComposedTypeWrapper
9 | {
10 | public TestEntity ComposedType1 { get; set; }
11 | public SecondTestEntity ComposedType2 { get; set; }
12 | public string StringValue { get; set; }
13 | public List ComposedType3 { get; set; }
14 | public static IntersectionTypeMock CreateFromDiscriminator(IParseNode parseNode) {
15 | var result = new IntersectionTypeMock();
16 | if (parseNode.GetStringValue() is string stringValue) {
17 | result.StringValue = stringValue;
18 | } else if (parseNode.GetCollectionOfObjectValues(TestEntity.CreateFromDiscriminator) is IEnumerable values && values.Any()) {
19 | result.ComposedType3 = values.ToList();
20 | } else {
21 | result.ComposedType1 = new();
22 | result.ComposedType2 = new();
23 | }
24 | return result;
25 | }
26 | public IDictionary> GetFieldDeserializers() {
27 | if(ComposedType1 != null || ComposedType1 != null) {
28 | return ParseNodeHelper.MergeDeserializersForIntersectionWrapper(ComposedType1, ComposedType2);
29 | }
30 | return new Dictionary>();
31 | }
32 | public void Serialize(ISerializationWriter writer) {
33 | _ = writer ?? throw new ArgumentNullException(nameof(writer));
34 | if (!string.IsNullOrEmpty(StringValue)) {
35 | writer.WriteStringValue(null, StringValue);
36 | } else if (ComposedType3 != null) {
37 | writer.WriteCollectionOfObjectValues(null, ComposedType3);
38 | } else {
39 | writer.WriteObjectValue(null, ComposedType1, ComposedType2);
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Mocks/SecondTestEntity.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 | using System.Collections.Generic;
4 | using Microsoft.Kiota.Abstractions.Serialization;
5 |
6 | namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks;
7 |
8 | public class SecondTestEntity : IParsable
9 | {
10 | public string DisplayName { get; set; }
11 | public int? Id { get; set; } // intentionally conflicts with the Id property of the TestEntity class
12 | public long? FailureRate { get; set; }
13 | public IDictionary> GetFieldDeserializers() {
14 | return new Dictionary> {
15 | { "displayName", node => DisplayName = node.GetStringValue() },
16 | { "id", node => Id = node.GetIntValue() },
17 | { "failureRate", node => FailureRate = node.GetLongValue()},
18 | };
19 | }
20 | public void Serialize(ISerializationWriter writer) {
21 | writer.WriteStringValue("displayName", DisplayName);
22 | writer.WriteIntValue("id", Id);
23 | writer.WriteLongValue("failureRate", FailureRate);
24 | }
25 | }
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Mocks/TestEntity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.Kiota.Abstractions;
4 | using Microsoft.Kiota.Abstractions.Serialization;
5 |
6 | namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks
7 | {
8 | public class TestEntity : IParsable, IAdditionalDataHolder
9 | {
10 | /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
11 | public IDictionary AdditionalData { get; set; }
12 | /// Read-only.
13 | public string Id { get; set; }
14 | /// Read-only.
15 | public TestEnum? Numbers { get; set; }
16 | /// Read-only.
17 | public TestNamingEnum? TestNamingEnum { get; set; }
18 | /// Read-only.
19 | public TimeSpan? WorkDuration { get; set; }
20 | /// Read-only.
21 | public Date? BirthDay { get; set; }
22 | /// Read-only.
23 | public Time? StartWorkTime { get; set; }
24 | /// Read-only.
25 | public Time? EndWorkTime { get; set; }
26 | /// Read-only.
27 | public DateTimeOffset? CreatedDateTime { get; set; }
28 | /// Read-only.
29 | public decimal? HeightInMetres { get; set; }
30 | /// Read-only.
31 | public string OfficeLocation { get; set; }
32 | ///
33 | /// Instantiates a new entity and sets the default values.
34 | ///
35 | public TestEntity()
36 | {
37 | AdditionalData = new Dictionary();
38 | }
39 | ///
40 | /// The deserialization information for the current model
41 | ///
42 | public virtual IDictionary> GetFieldDeserializers()
43 | {
44 | return new Dictionary> {
45 | {"id", n => { Id = n.GetStringValue(); } },
46 | {"numbers", n => { Numbers = n.GetEnumValue(); } },
47 | {"testNamingEnum", n => { TestNamingEnum = n.GetEnumValue(); } },
48 | {"createdDateTime", n => { CreatedDateTime = n.GetDateTimeOffsetValue(); } },
49 | {"officeLocation", n => { OfficeLocation = n.GetStringValue(); } },
50 | {"workDuration", n => { WorkDuration = n.GetTimeSpanValue(); } },
51 | {"heightInMetres", n => { HeightInMetres = n.GetDecimalValue(); } },
52 | {"birthDay", n => { BirthDay = n.GetDateValue(); } },
53 | {"startWorkTime", n => { StartWorkTime = n.GetTimeValue(); } },
54 | {"endWorkTime", n => { EndWorkTime = n.GetTimeValue(); } },
55 | };
56 | }
57 | ///
58 | /// Serializes information the current object
59 | /// Serialization writer to use to serialize this model
60 | ///
61 | public virtual void Serialize(ISerializationWriter writer)
62 | {
63 | _ = writer ?? throw new ArgumentNullException(nameof(writer));
64 | writer.WriteStringValue("id", Id);
65 | writer.WriteEnumValue("numbers",Numbers);
66 | writer.WriteEnumValue("testNamingEnum",TestNamingEnum);
67 | writer.WriteDateTimeOffsetValue("createdDateTime", CreatedDateTime);
68 | writer.WriteStringValue("officeLocation", OfficeLocation);
69 | writer.WriteTimeSpanValue("workDuration", WorkDuration);
70 | writer.WriteDateValue("birthDay", BirthDay);
71 | writer.WriteDecimalValue("heightInMetres", HeightInMetres);
72 | writer.WriteTimeValue("startWorkTime", StartWorkTime);
73 | writer.WriteTimeValue("endWorkTime", EndWorkTime);
74 | writer.WriteAdditionalData(AdditionalData);
75 | }
76 | public static TestEntity CreateFromDiscriminator(IParseNode parseNode) {
77 | var discriminatorValue = parseNode.GetChildNode("@odata.type")?.GetStringValue();
78 | return discriminatorValue switch
79 | {
80 | "microsoft.graph.user" => new TestEntity(),
81 | "microsoft.graph.group" => new TestEntity(),
82 | "microsoft.graph.student" => new DerivedTestEntity(),
83 | _ => new TestEntity(),
84 | };
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Mocks/TestEnum.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks
4 | {
5 | [Flags]
6 | public enum TestEnum
7 | {
8 | One = 0x00000001,
9 | Two = 0x00000002,
10 | Four = 0x00000004,
11 | Eight = 0x00000008,
12 | Sixteen = 0x00000010
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Mocks/TestNamingEnum.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks
4 | {
5 | public enum TestNamingEnum
6 | {
7 | Item1,
8 | [EnumMember(Value = "Item2:SubItem1")]
9 | Item2SubItem1,
10 | [EnumMember(Value = "Item3:SubItem1")]
11 | Item3SubItem1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Mocks/UnionTypeMock.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Microsoft.Kiota.Abstractions.Serialization;
5 |
6 | namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks;
7 |
8 | public class UnionTypeMock : IParsable, IComposedTypeWrapper
9 | {
10 | public TestEntity ComposedType1 { get; set; }
11 | public SecondTestEntity ComposedType2 { get; set; }
12 | public string StringValue { get; set; }
13 | public List ComposedType3 { get; set; }
14 | public static UnionTypeMock CreateFromDiscriminator(IParseNode parseNode) {
15 | var result = new UnionTypeMock();
16 | var discriminator = parseNode.GetChildNode("@odata.type")?.GetStringValue();
17 | if("#microsoft.graph.testEntity".Equals(discriminator)) {
18 | result.ComposedType1 = new();
19 | }
20 | else if("#microsoft.graph.secondTestEntity".Equals(discriminator)) {
21 | result.ComposedType2 = new();
22 | }
23 | else if (parseNode.GetStringValue() is string stringValue) {
24 | result.StringValue = stringValue;
25 | } else if (parseNode.GetCollectionOfObjectValues(TestEntity.CreateFromDiscriminator) is IEnumerable values && values.Any()) {
26 | result.ComposedType3 = values.ToList();
27 | }
28 | return result;
29 | }
30 | public IDictionary> GetFieldDeserializers() {
31 | if (ComposedType1 != null)
32 | return ComposedType1.GetFieldDeserializers();
33 | else if (ComposedType2 != null)
34 | return ComposedType2.GetFieldDeserializers();
35 | //composed type3 is omitted on purpose
36 | return new Dictionary>();
37 | }
38 | public void Serialize(ISerializationWriter writer) {
39 | _ = writer ?? throw new ArgumentNullException(nameof(writer));
40 | if (ComposedType1 != null) {
41 | writer.WriteObjectValue(null, ComposedType1);
42 | }
43 | else if (ComposedType2 != null) {
44 | writer.WriteObjectValue(null, ComposedType2);
45 | } else if (ComposedType3 != null) {
46 | writer.WriteCollectionOfObjectValues(null, ComposedType3);
47 | } else if (!string.IsNullOrEmpty(StringValue)) {
48 | writer.WriteStringValue(null, StringValue);
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/Mocks/UntypedTestEntity.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 | using System.Collections.Generic;
4 | using Microsoft.Kiota.Abstractions.Serialization;
5 |
6 | namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks;
7 |
8 | public class UntypedTestEntity : IParsable, IAdditionalDataHolder
9 | {
10 | /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.
11 | public IDictionary AdditionalData { get; set; }
12 | public string Id { get; set; }
13 | public string Title { get; set; }
14 | public UntypedNode Location { get; set; }
15 | public UntypedNode Keywords { get; set; }
16 | public UntypedNode Detail { get; set; }
17 | public UntypedNode Table { get; set; }
18 | public UntypedTestEntity()
19 | {
20 | AdditionalData = new Dictionary();
21 | }
22 |
23 | public IDictionary> GetFieldDeserializers()
24 | {
25 | return new Dictionary>
26 | {
27 | { "id", node => Id = node.GetStringValue() },
28 | { "title", node => Title = node.GetStringValue() },
29 | { "location", node => Location = node.GetObjectValue(UntypedNode.CreateFromDiscriminatorValue) },
30 | { "keywords", node => Keywords = node.GetObjectValue(UntypedNode.CreateFromDiscriminatorValue) },
31 | { "detail", node => Detail = node.GetObjectValue(UntypedNode.CreateFromDiscriminatorValue) },
32 | { "table", node => Table = node.GetObjectValue(UntypedNode.CreateFromDiscriminatorValue) },
33 | };
34 | }
35 | public void Serialize(ISerializationWriter writer)
36 | {
37 | writer.WriteStringValue("id", Id);
38 | writer.WriteStringValue("title", Title);
39 | writer.WriteObjectValue("location", Location);
40 | writer.WriteObjectValue("keywords", Keywords);
41 | writer.WriteObjectValue("detail", Detail);
42 | writer.WriteObjectValue("table", Table);
43 | writer.WriteAdditionalData(AdditionalData);
44 | }
45 | public static UntypedTestEntity CreateFromDiscriminatorValue(IParseNode parseNode)
46 | {
47 | var discriminatorValue = parseNode.GetChildNode("@odata.type")?.GetStringValue();
48 | return discriminatorValue switch
49 | {
50 | _ => new UntypedTestEntity(),
51 | };
52 | }
53 | }
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.Tests/UnionWrapperParseTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Linq;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 | using Microsoft.Kiota.Serialization.Json.Tests.Mocks;
6 | using Xunit;
7 |
8 | namespace Microsoft.Kiota.Serialization.Json.Tests;
9 |
10 | public class UnionWrapperParseTests {
11 | private readonly JsonParseNodeFactory _parseNodeFactory = new();
12 | private readonly JsonSerializationWriterFactory _serializationWriterFactory = new();
13 | private const string contentType = "application/json";
14 | [Fact]
15 | public async Task ParsesUnionTypeComplexProperty1()
16 | {
17 | // Given
18 | using var payload = new MemoryStream(Encoding.UTF8.GetBytes("{\"@odata.type\":\"#microsoft.graph.testEntity\",\"officeLocation\":\"Montreal\", \"id\": \"opaque\"}"));
19 | var parseNode = await _parseNodeFactory.GetRootParseNodeAsync(contentType, payload);
20 |
21 | // When
22 | var result = parseNode.GetObjectValue(UnionTypeMock.CreateFromDiscriminator);
23 |
24 | // Then
25 | Assert.NotNull(result);
26 | Assert.NotNull(result.ComposedType1);
27 | Assert.Null(result.ComposedType2);
28 | Assert.Null(result.ComposedType3);
29 | Assert.Null(result.StringValue);
30 | Assert.Equal("opaque", result.ComposedType1.Id);
31 | }
32 | [Fact]
33 | public async Task ParsesUnionTypeComplexProperty2()
34 | {
35 | // Given
36 | using var payload = new MemoryStream(Encoding.UTF8.GetBytes("{\"@odata.type\":\"#microsoft.graph.secondTestEntity\",\"officeLocation\":\"Montreal\", \"id\": 10}"));
37 | var parseNode = await _parseNodeFactory.GetRootParseNodeAsync(contentType, payload);
38 |
39 | // When
40 | var result = parseNode.GetObjectValue(UnionTypeMock.CreateFromDiscriminator);
41 |
42 | // Then
43 | Assert.NotNull(result);
44 | Assert.NotNull(result.ComposedType2);
45 | Assert.Null(result.ComposedType1);
46 | Assert.Null(result.ComposedType3);
47 | Assert.Null(result.StringValue);
48 | Assert.Equal(10, result.ComposedType2.Id);
49 | }
50 | [Fact]
51 | public async Task ParsesUnionTypeComplexProperty3()
52 | {
53 | // Given
54 | using var payload = new MemoryStream(Encoding.UTF8.GetBytes("[{\"@odata.type\":\"#microsoft.graph.TestEntity\",\"officeLocation\":\"Ottawa\", \"id\": \"11\"}, {\"@odata.type\":\"#microsoft.graph.TestEntity\",\"officeLocation\":\"Montreal\", \"id\": \"10\"}]"));
55 | var parseNode = await _parseNodeFactory.GetRootParseNodeAsync(contentType, payload);
56 |
57 | // When
58 | var result = parseNode.GetObjectValue(UnionTypeMock.CreateFromDiscriminator);
59 |
60 | // Then
61 | Assert.NotNull(result);
62 | Assert.NotNull(result.ComposedType3);
63 | Assert.Null(result.ComposedType2);
64 | Assert.Null(result.ComposedType1);
65 | Assert.Null(result.StringValue);
66 | Assert.Equal(2, result.ComposedType3.Count);
67 | Assert.Equal("11", result.ComposedType3[0].Id);
68 | }
69 | [Fact]
70 | public async Task ParsesUnionTypeStringValue()
71 | {
72 | // Given
73 | using var payload = new MemoryStream(Encoding.UTF8.GetBytes("\"officeLocation\""));
74 | var parseNode = await _parseNodeFactory.GetRootParseNodeAsync(contentType, payload);
75 |
76 | // When
77 | var result = parseNode.GetObjectValue(UnionTypeMock.CreateFromDiscriminator);
78 |
79 | // Then
80 | Assert.NotNull(result);
81 | Assert.Null(result.ComposedType2);
82 | Assert.Null(result.ComposedType1);
83 | Assert.Equal("officeLocation", result.StringValue);
84 | }
85 | [Fact]
86 | public void SerializesUnionTypeStringValue()
87 | {
88 | // Given
89 | using var writer = _serializationWriterFactory.GetSerializationWriter(contentType);
90 | var model = new UnionTypeMock {
91 | StringValue = "officeLocation"
92 | };
93 |
94 | // When
95 | writer.WriteObjectValue(string.Empty, model);
96 | using var resultStream = writer.GetSerializedContent();
97 | using var streamReader = new StreamReader(resultStream);
98 | var result = streamReader.ReadToEnd();
99 |
100 | // Then
101 | Assert.Equal("\"officeLocation\"", result);
102 | }
103 | [Fact]
104 | public void SerializesUnionTypeComplexProperty1()
105 | {
106 | // Given
107 | using var writer = _serializationWriterFactory.GetSerializationWriter(contentType);
108 | var model = new UnionTypeMock {
109 | ComposedType1 = new() {
110 | Id = "opaque",
111 | OfficeLocation = "Montreal",
112 | },
113 | ComposedType2 = new() {
114 | DisplayName = "McGill",
115 | },
116 | };
117 |
118 | // When
119 | writer.WriteObjectValue(string.Empty, model);
120 | using var resultStream = writer.GetSerializedContent();
121 | using var streamReader = new StreamReader(resultStream);
122 | var result = streamReader.ReadToEnd();
123 |
124 | // Then
125 | Assert.Equal("{\"id\":\"opaque\",\"officeLocation\":\"Montreal\"}", result);
126 | }
127 | [Fact]
128 | public void SerializesUnionTypeComplexProperty2()
129 | {
130 | // Given
131 | using var writer = _serializationWriterFactory.GetSerializationWriter(contentType);
132 | var model = new UnionTypeMock {
133 | ComposedType2 = new() {
134 | DisplayName = "McGill",
135 | Id = 10,
136 | },
137 | };
138 |
139 | // When
140 | writer.WriteObjectValue(string.Empty, model);
141 | using var resultStream = writer.GetSerializedContent();
142 | using var streamReader = new StreamReader(resultStream);
143 | var result = streamReader.ReadToEnd();
144 |
145 | // Then
146 | Assert.Equal("{\"displayName\":\"McGill\",\"id\":10}", result);
147 | }
148 |
149 | [Fact]
150 | public void SerializesUnionTypeComplexProperty3()
151 | {
152 | // Given
153 | using var writer = _serializationWriterFactory.GetSerializationWriter(contentType);
154 | var model = new UnionTypeMock {
155 | ComposedType3 = new() {
156 | new() {
157 | OfficeLocation = "Montreal",
158 | Id = "10",
159 | },
160 | new() {
161 | OfficeLocation = "Ottawa",
162 | Id = "11",
163 | }
164 | },
165 | };
166 |
167 | // When
168 | writer.WriteObjectValue(string.Empty, model);
169 | using var resultStream = writer.GetSerializedContent();
170 | using var streamReader = new StreamReader(resultStream);
171 | var result = streamReader.ReadToEnd();
172 |
173 | // Then
174 | Assert.Equal("[{\"id\":\"10\",\"officeLocation\":\"Montreal\"},{\"id\":\"11\",\"officeLocation\":\"Ottawa\"}]", result);
175 | }
176 | }
--------------------------------------------------------------------------------
/Microsoft.Kiota.Serialization.Json.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Kiota.Serialization.Json", "src\Microsoft.Kiota.Serialization.Json.csproj", "{8CC6634E-A611-4FB9-9C31-1BE038D75E1E}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{89E4E773-D199-4D1A-A8D6-24EF61CBACED}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | EndProjectSection
12 | EndProject
13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kiota.Serialization.Json.Tests", "Microsoft.Kiota.Serialization.Json.Tests\Microsoft.Kiota.Serialization.Json.Tests.csproj", "{AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}"
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Debug|x64 = Debug|x64
19 | Debug|x86 = Debug|x86
20 | Release|Any CPU = Release|Any CPU
21 | Release|x64 = Release|x64
22 | Release|x86 = Release|x86
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Debug|x64.ActiveCfg = Debug|Any CPU
28 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Debug|x64.Build.0 = Debug|Any CPU
29 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Debug|x86.ActiveCfg = Debug|Any CPU
30 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Debug|x86.Build.0 = Debug|Any CPU
31 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Release|x64.ActiveCfg = Release|Any CPU
34 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Release|x64.Build.0 = Release|Any CPU
35 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Release|x86.ActiveCfg = Release|Any CPU
36 | {8CC6634E-A611-4FB9-9C31-1BE038D75E1E}.Release|x86.Build.0 = Release|Any CPU
37 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Debug|x64.ActiveCfg = Debug|Any CPU
40 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Debug|x64.Build.0 = Debug|Any CPU
41 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Debug|x86.ActiveCfg = Debug|Any CPU
42 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Debug|x86.Build.0 = Debug|Any CPU
43 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Release|x64.ActiveCfg = Release|Any CPU
46 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Release|x64.Build.0 = Release|Any CPU
47 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Release|x86.ActiveCfg = Release|Any CPU
48 | {AC81733F-EC61-4241-ADCF-E16BA1EDD7AF}.Release|x86.Build.0 = Release|Any CPU
49 | EndGlobalSection
50 | GlobalSection(SolutionProperties) = preSolution
51 | HideSolutionNode = FALSE
52 | EndGlobalSection
53 | GlobalSection(ExtensibilityGlobals) = postSolution
54 | SolutionGuid = {3B30B565-C5DD-47A4-A038-D26FA5BF1706}
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kiota Json Serialization Library for dotnet
2 |
3 | This repository has been archived and source migrated. You can contribute and file issues in the [Kiota Dotnet](https://github.com/microsoft/kiota-dotnet) repository.
4 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # TODO: The maintainer of this repo has not yet edited this file
2 |
3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
4 |
5 | - **No CSS support:** Fill out this template with information about how to file issues and get help.
6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport).
7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide.
8 |
9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
10 |
11 | # Support
12 |
13 | ## How to file issues and get help
14 |
15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing
16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or
17 | feature request as a new Issue.
18 |
19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER
21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**.
22 |
23 | ## Microsoft Support Policy
24 |
25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
26 |
--------------------------------------------------------------------------------
/scripts/EnableSigning.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (c) Microsoft Corporation. All rights reserved.
2 | # Licensed under the MIT License.
3 |
4 | <#
5 | .Synopsis
6 | Sets the project ready for signing.
7 | .Description
8 | This allows us to not have to checkin .csproj files with DelaySign and SignAssembly set to to false.
9 | If the flag is set, then project is not debuggable with SignAssembly set to true.
10 | Assumption: working directory is /src/
11 | .Parameter projectPath
12 | Specifies the path to the project file.
13 | #>
14 |
15 | Param(
16 | [parameter(Mandatory = $true)]
17 | [string]$projectPath
18 | )
19 |
20 | $doc = New-Object System.Xml.XmlDocument
21 | $doc.Load($projectPath)
22 |
23 | # Set the DelaySign element to 'true' so that delay signing is set.
24 | $delaySign = $doc.SelectSingleNode("//DelaySign");
25 | $delaySign.'#text' = "true"
26 |
27 | # Set the SignAssembly element to 'true' so that we can sign the assemblies.
28 | $signAssembly = $doc.SelectSingleNode("//SignAssembly");
29 | $signAssembly.'#text' = "true"
30 |
31 | $doc.Save($projectPath);
32 |
33 | Write-Host "Updated the .csproj file so that we can sign the built assemblies." -ForegroundColor Green
--------------------------------------------------------------------------------
/scripts/GetNugetPackageVersion.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (c) Microsoft Corporation. All rights reserved.
2 | # Licensed under the MIT License.
3 |
4 | <#
5 | .Synopsis
6 | Get the NuGet package version.
7 | .Description
8 | Get the NuGet package version and write the package version to an environment
9 | variable named VERSION_STRING in the Azure DevOps release environment.
10 | VERSION_STRING is used to name a tag for setting a GitHub release. This
11 | script assumes that the NuGet package has been named with correct version number.
12 |
13 | Assumption:
14 | Targets Microsoft.Graph.
15 |
16 | .Parameter packageDirPath
17 | Specifies the fully qualified path to the NuGet package directory.
18 | #>
19 |
20 | Param(
21 | [string]$packageDirPath
22 | )
23 |
24 | Write-Host "Get the NuGet package version and set it in the global variable: VERSION_STRING" -ForegroundColor Magenta
25 |
26 | $nugetPackageName = (Get-ChildItem (Join-Path $packageDirPath *.nupkg) -Exclude *.symbols.nupkg).Name
27 |
28 | Write-Host "Found NuGet package: $nugetPackageName" -ForegroundColor Magenta
29 |
30 | ## Extracts the package version from nupkg file name.
31 | $packageVersion = $nugetPackageName.Replace("Microsoft.Kiota.Serialization.Json.", "").Replace(".nupkg", "")
32 |
33 | Write-Host "##vso[task.setvariable variable=VERSION_STRING]$($packageVersion)";
34 | Write-Host "Updated the VERSION_STRING environment variable with the package version value '$packageVersion'." -ForegroundColor Green
35 |
36 | $isPrerelease = $packageVersion.Contains("preview")
37 | Write-Host "##vso[task.setvariable variable=IS_PRE_RELEASE]$($isPrerelease)";
38 | Write-Host "Updated the IS_PRE_RELEASE environment variable with the pre-release value '$isPrerelease'." -ForegroundColor Green
--------------------------------------------------------------------------------
/scripts/ValidateProjectVersionUpdated.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (c) Microsoft Corporation. All rights reserved.
2 | # Licensed under the MIT License.
3 |
4 | <#
5 | .Synopsis
6 | Gets the latest production release version of the specified NuGet package.
7 |
8 | .Description
9 | Gets the NuGet package version of latest production release and compares the
10 | version to the version set in the specified project file. If they match, this
11 | script will fail and indicate that the version needs to be updated.
12 |
13 | .Parameter packageName
14 | Specifies the package name of the package. For example, 'microsoft.graph.core'
15 | is a valid package name.
16 |
17 | .Parameter projectPath
18 | Specifies the path to the project file.
19 | #>
20 |
21 | Param(
22 | [parameter(Mandatory = $true)]
23 | [string]$packageName,
24 |
25 | [parameter(Mandatory = $true)]
26 | [string]$projectPath
27 | )
28 |
29 | [xml]$xmlDoc = Get-Content $projectPath
30 |
31 | # Assumption: VersionPrefix is set in the first property group.
32 | $versionPrefixString = $xmlDoc.Project.PropertyGroup[0].VersionPrefix
33 | if($xmlDoc.Project.PropertyGroup[0].VersionSuffix){
34 | $versionPrefixString = $versionPrefixString + "-" + $xmlDoc.Project.PropertyGroup[0].VersionSuffix
35 | }
36 |
37 |
38 | # System.Version, get the version prefix.
39 | $currentProjectVersion = [System.Management.Automation.SemanticVersion]"$versionPrefixString"
40 |
41 | # API is case-sensitive
42 | $packageName = $packageName.ToLower()
43 | $url = "https://api.nuget.org/v3/registration5-gz-semver2/$packageName/index.json"
44 |
45 | # Call the NuGet API for the package and get the current published version.
46 | Try {
47 | $nugetIndex = Invoke-RestMethod -Uri $url -Method Get
48 | }
49 | Catch {
50 | if ($_.ErrorDetails.Message && $_.ErrorDetails.Message.Contains("The specified blob does not exist.")) {
51 | Write-Host "No package exists. You will probably be publishing $packageName for the first time."
52 | Exit # exit gracefully
53 | }
54 |
55 | Write-Host $_
56 | Exit 1
57 | }
58 |
59 | $currentPublishedVersion = $nugetIndex.items | Select-Object -ExpandProperty upper | ForEach-Object { [System.Management.Automation.SemanticVersion]$_ } | sort-object | Select-Object -Last 1
60 |
61 | # Validate that the version number has been updated.
62 | if ($currentProjectVersion -le $currentPublishedVersion) {
63 |
64 | Write-Error "The current published version number, $currentPublishedVersion, and the version number `
65 | in the csproj file, $currentProjectVersion, match. You must increment the version"
66 | }
67 | else {
68 | Write-Host "Validated that the version has been updated from $currentPublishedVersion to $currentProjectVersion" -ForegroundColor Green
69 | }
70 |
--------------------------------------------------------------------------------
/src/35MSSharedLib1024.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/kiota-serialization-json-dotnet/76106d552d2ccdb7ff50e2df632560f0456c19ce/src/35MSSharedLib1024.snk
--------------------------------------------------------------------------------
/src/JsonParseNode.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3 | // ------------------------------------------------------------------------------
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Diagnostics;
8 | using System.Reflection;
9 | using System.Runtime.Serialization;
10 | using System.Text.Json;
11 | using System.Xml;
12 | using Microsoft.Kiota.Abstractions.Serialization;
13 | using Microsoft.Kiota.Abstractions;
14 | using Microsoft.Kiota.Abstractions.Extensions;
15 |
16 | #if NET5_0_OR_GREATER
17 | using System.Diagnostics.CodeAnalysis;
18 | #endif
19 |
20 | namespace Microsoft.Kiota.Serialization.Json
21 | {
22 | ///
23 | /// The implementation for the json content type
24 | ///
25 | public class JsonParseNode : IParseNode
26 | {
27 | private readonly JsonElement _jsonNode;
28 | private readonly KiotaJsonSerializationContext _jsonSerializerContext;
29 |
30 | ///
31 | /// The constructor.
32 | ///
33 | /// The JsonElement to initialize the node with
34 | public JsonParseNode(JsonElement node)
35 | : this(node, KiotaJsonSerializationContext.Default)
36 | {
37 | }
38 |
39 | ///
40 | /// The constructor.
41 | ///
42 | /// The JsonElement to initialize the node with
43 | /// The JsonSerializerContext to utilize.
44 | public JsonParseNode(JsonElement node, KiotaJsonSerializationContext jsonSerializerContext)
45 | {
46 | _jsonNode = node;
47 | _jsonSerializerContext = jsonSerializerContext;
48 | }
49 |
50 | ///
51 | /// Get the string value from the json node
52 | ///
53 | /// A string value
54 | public string? GetStringValue() => _jsonNode.ValueKind == JsonValueKind.String
55 | ? _jsonNode.Deserialize(_jsonSerializerContext.String)
56 | : null;
57 |
58 | ///
59 | /// Get the boolean value from the json node
60 | ///
61 | /// A boolean value
62 | public bool? GetBoolValue() =>
63 | _jsonNode.ValueKind == JsonValueKind.True || _jsonNode.ValueKind == JsonValueKind.False
64 | ? _jsonNode.Deserialize(_jsonSerializerContext.Boolean)
65 | : null;
66 |
67 | ///
68 | /// Get the byte value from the json node
69 | ///
70 | /// A byte value
71 | public byte? GetByteValue() => _jsonNode.ValueKind == JsonValueKind.Number
72 | ? _jsonNode.Deserialize(_jsonSerializerContext.Byte)
73 | : null;
74 |
75 | ///
76 | /// Get the sbyte value from the json node
77 | ///
78 | /// A sbyte value
79 | public sbyte? GetSbyteValue() => _jsonNode.ValueKind == JsonValueKind.Number
80 | ? _jsonNode.Deserialize(_jsonSerializerContext.SByte)
81 | : null;
82 |
83 | ///
84 | /// Get the int value from the json node
85 | ///
86 | /// A int value
87 | public int? GetIntValue() => _jsonNode.ValueKind == JsonValueKind.Number
88 | ? _jsonNode.Deserialize(_jsonSerializerContext.Int32)
89 | : null;
90 |
91 | ///
92 | /// Get the float value from the json node
93 | ///
94 | /// A float value
95 | public float? GetFloatValue() => _jsonNode.ValueKind == JsonValueKind.Number
96 | ? _jsonNode.Deserialize(_jsonSerializerContext.Single)
97 | : null;
98 |
99 | ///
100 | /// Get the Long value from the json node
101 | ///
102 | /// A Long value
103 | public long? GetLongValue() => _jsonNode.ValueKind == JsonValueKind.Number
104 | ? _jsonNode.Deserialize(_jsonSerializerContext.Int64)
105 | : null;
106 |
107 | ///
108 | /// Get the double value from the json node
109 | ///
110 | /// A double value
111 | public double? GetDoubleValue() => _jsonNode.ValueKind == JsonValueKind.Number
112 | ? _jsonNode.Deserialize(_jsonSerializerContext.Double)
113 | : null;
114 |
115 | ///
116 | /// Get the decimal value from the json node
117 | ///
118 | /// A decimal value
119 | public decimal? GetDecimalValue() => _jsonNode.ValueKind == JsonValueKind.Number
120 | ? _jsonNode.Deserialize(_jsonSerializerContext.Decimal)
121 | : null;
122 |
123 | ///
124 | /// Get the guid value from the json node
125 | ///
126 | /// A guid value
127 | public Guid? GetGuidValue()
128 | {
129 | if(_jsonNode.ValueKind != JsonValueKind.String)
130 | return null;
131 |
132 | if(_jsonNode.TryGetGuid(out var guid))
133 | return guid;
134 |
135 | if(string.IsNullOrEmpty(_jsonNode.GetString()))
136 | return null;
137 |
138 | return _jsonNode.Deserialize(_jsonSerializerContext.Guid);
139 | }
140 |
141 | ///
142 | /// Get the value from the json node
143 | ///
144 | /// A value
145 | public DateTimeOffset? GetDateTimeOffsetValue()
146 | {
147 | if(_jsonNode.ValueKind != JsonValueKind.String)
148 | return null;
149 |
150 | if(_jsonNode.TryGetDateTimeOffset(out var dateTimeOffset))
151 | return dateTimeOffset;
152 |
153 | var dateTimeOffsetStr = _jsonNode.GetString();
154 | if(string.IsNullOrEmpty(dateTimeOffsetStr))
155 | return null;
156 |
157 | if (DateTimeOffset.TryParse(dateTimeOffsetStr, out dateTimeOffset))
158 | return dateTimeOffset;
159 |
160 | return _jsonNode.Deserialize(_jsonSerializerContext.DateTimeOffset);
161 | }
162 |
163 | ///
164 | /// Get the value from the json node
165 | ///
166 | /// A value
167 | public TimeSpan? GetTimeSpanValue()
168 | {
169 | var jsonString = _jsonNode.GetString();
170 | if(string.IsNullOrEmpty(jsonString))
171 | return null;
172 |
173 | // Parse an ISO8601 duration.http://en.wikipedia.org/wiki/ISO_8601#Durations to a TimeSpan
174 | return XmlConvert.ToTimeSpan(jsonString);
175 | }
176 |
177 | ///
178 | /// Get the value from the json node
179 | ///
180 | /// A value
181 | public Date? GetDateValue()
182 | {
183 | var dateString = _jsonNode.GetString();
184 | if(string.IsNullOrEmpty(dateString))
185 | return null;
186 |
187 | if(DateTime.TryParse(dateString, out var result))
188 | return new Date(result);
189 |
190 | return _jsonNode.Deserialize(_jsonSerializerContext.Date);
191 | }
192 |
193 | ///
194 | /// Get the value from the json node
195 | ///
196 | /// A value
197 | public Time? GetTimeValue()
198 | {
199 | var dateString = _jsonNode.GetString();
200 | if(string.IsNullOrEmpty(dateString))
201 | return null;
202 |
203 | if(DateTime.TryParse(dateString, out var result))
204 | return new Time(result);
205 |
206 | return _jsonNode.Deserialize(_jsonSerializerContext.Time);
207 | }
208 |
209 | ///
210 | /// Get the enumeration value of type from the json node
211 | ///
212 | /// An enumeration value or null
213 | #if NET5_0_OR_GREATER
214 | public T? GetEnumValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>() where T : struct, Enum
215 | #else
216 | public T? GetEnumValue() where T : struct, Enum
217 | #endif
218 | {
219 | var rawValue = _jsonNode.GetString();
220 | if(string.IsNullOrEmpty(rawValue)) return null;
221 |
222 | rawValue = ToEnumRawName(rawValue!);
223 | if (typeof(T).IsDefined(typeof(FlagsAttribute)))
224 | {
225 | ReadOnlySpan valueSpan = rawValue.AsSpan();
226 | int value = 0;
227 | while(valueSpan.Length > 0)
228 | {
229 | int commaIndex = valueSpan.IndexOf(',');
230 | ReadOnlySpan valueNameSpan = commaIndex < 0 ? valueSpan : valueSpan.Slice(0, commaIndex);
231 | #if NET6_0_OR_GREATER
232 | if(Enum.TryParse(valueNameSpan, true, out var result))
233 | #else
234 | if(Enum.TryParse(valueNameSpan.ToString(), true, out var result))
235 | #endif
236 | value |= (int)(object)result;
237 | valueSpan = commaIndex < 0 ? ReadOnlySpan.Empty : valueSpan.Slice(commaIndex + 1);
238 | }
239 | return (T)(object)value;
240 | }
241 | else
242 | return Enum.TryParse(rawValue, true, out var result) ? result : null;
243 | }
244 |
245 | ///
246 | /// Get the collection of type from the json node
247 | ///
248 | /// The factory to use to create the model object.
249 | /// A collection of objects
250 | public IEnumerable GetCollectionOfObjectValues(ParsableFactory factory) where T : IParsable
251 | {
252 | if (_jsonNode.ValueKind == JsonValueKind.Array) {
253 | var enumerator = _jsonNode.EnumerateArray();
254 | while(enumerator.MoveNext())
255 | {
256 | var currentParseNode = new JsonParseNode(enumerator.Current, _jsonSerializerContext)
257 | {
258 | OnAfterAssignFieldValues = OnAfterAssignFieldValues,
259 | OnBeforeAssignFieldValues = OnBeforeAssignFieldValues
260 | };
261 | yield return currentParseNode.GetObjectValue(factory);
262 | }
263 | }
264 | }
265 | ///
266 | /// Gets the collection of enum values of the node.
267 | ///
268 | /// The collection of enum values.
269 | #if NET5_0_OR_GREATER
270 | public IEnumerable GetCollectionOfEnumValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>() where T : struct, Enum
271 | #else
272 | public IEnumerable GetCollectionOfEnumValues() where T : struct, Enum
273 | #endif
274 | {
275 | if (_jsonNode.ValueKind == JsonValueKind.Array) {
276 | var enumerator = _jsonNode.EnumerateArray();
277 | while(enumerator.MoveNext())
278 | {
279 | var currentParseNode = new JsonParseNode(enumerator.Current, _jsonSerializerContext)
280 | {
281 | OnAfterAssignFieldValues = OnAfterAssignFieldValues,
282 | OnBeforeAssignFieldValues = OnBeforeAssignFieldValues
283 | };
284 | yield return currentParseNode.GetEnumValue();
285 | }
286 | }
287 | }
288 | ///
289 | /// Gets the byte array value of the node.
290 | ///
291 | /// The byte array value of the node.
292 | public byte[]? GetByteArrayValue() {
293 | var rawValue = _jsonNode.GetString();
294 | if(string.IsNullOrEmpty(rawValue))
295 | return null;
296 | return Convert.FromBase64String(rawValue);
297 | }
298 | ///
299 | /// Gets the untyped value of the node
300 | ///
301 | /// The untyped value of the node.
302 | private UntypedNode? GetUntypedValue() => GetUntypedValue(_jsonNode);
303 |
304 |
305 | ///
306 | /// Get the collection of primitives of type from the json node
307 | ///
308 | /// A collection of objects
309 | public IEnumerable GetCollectionOfPrimitiveValues()
310 | {
311 | if (_jsonNode.ValueKind == JsonValueKind.Array) {
312 | var genericType = typeof(T);
313 | foreach(var collectionValue in _jsonNode.EnumerateArray())
314 | {
315 | var currentParseNode = new JsonParseNode(collectionValue, _jsonSerializerContext)
316 | {
317 | OnBeforeAssignFieldValues = OnBeforeAssignFieldValues,
318 | OnAfterAssignFieldValues = OnAfterAssignFieldValues
319 | };
320 | if(genericType == TypeConstants.BooleanType)
321 | yield return (T)(object)currentParseNode.GetBoolValue()!;
322 | else if(genericType == TypeConstants.ByteType)
323 | yield return (T)(object)currentParseNode.GetByteValue()!;
324 | else if(genericType == TypeConstants.SbyteType)
325 | yield return (T)(object)currentParseNode.GetSbyteValue()!;
326 | else if(genericType == TypeConstants.StringType)
327 | yield return (T)(object)currentParseNode.GetStringValue()!;
328 | else if(genericType == TypeConstants.IntType)
329 | yield return (T)(object)currentParseNode.GetIntValue()!;
330 | else if(genericType == TypeConstants.FloatType)
331 | yield return (T)(object)currentParseNode.GetFloatValue()!;
332 | else if(genericType == TypeConstants.LongType)
333 | yield return (T)(object)currentParseNode.GetLongValue()!;
334 | else if(genericType == TypeConstants.DoubleType)
335 | yield return (T)(object)currentParseNode.GetDoubleValue()!;
336 | else if(genericType == TypeConstants.GuidType)
337 | yield return (T)(object)currentParseNode.GetGuidValue()!;
338 | else if(genericType == TypeConstants.DateTimeOffsetType)
339 | yield return (T)(object)currentParseNode.GetDateTimeOffsetValue()!;
340 | else if(genericType == TypeConstants.TimeSpanType)
341 | yield return (T)(object)currentParseNode.GetTimeSpanValue()!;
342 | else if(genericType == TypeConstants.DateType)
343 | yield return (T)(object)currentParseNode.GetDateValue()!;
344 | else if(genericType == TypeConstants.TimeType)
345 | yield return (T)(object)currentParseNode.GetTimeValue()!;
346 | else
347 | throw new InvalidOperationException($"unknown type for deserialization {genericType.FullName}");
348 | }
349 | }
350 | }
351 |
352 | ///
353 | /// Gets the collection of untyped values of the node.
354 | ///
355 | /// The collection of untyped values.
356 | private IEnumerable GetCollectionOfUntypedValues(JsonElement jsonNode)
357 | {
358 | if (jsonNode.ValueKind == JsonValueKind.Array)
359 | {
360 | foreach(var collectionValue in jsonNode.EnumerateArray())
361 | {
362 | var currentParseNode = new JsonParseNode(collectionValue)
363 | {
364 | OnBeforeAssignFieldValues = OnBeforeAssignFieldValues,
365 | OnAfterAssignFieldValues = OnAfterAssignFieldValues
366 | };
367 | yield return currentParseNode.GetUntypedValue()!;
368 | }
369 | }
370 | }
371 |
372 | ///
373 | /// Gets the collection of properties in the untyped object.
374 | ///
375 | /// The collection of properties in the untyped object.
376 | private IDictionary GetPropertiesOfUntypedObject(JsonElement jsonNode)
377 | {
378 | var properties = new Dictionary();
379 | if(jsonNode.ValueKind == JsonValueKind.Object)
380 | {
381 | foreach(var objectValue in jsonNode.EnumerateObject())
382 | {
383 | JsonElement property = objectValue.Value;
384 | if(objectValue.Value.ValueKind == JsonValueKind.Object)
385 | {
386 | var childNode = new JsonParseNode(objectValue.Value)
387 | {
388 | OnBeforeAssignFieldValues = OnBeforeAssignFieldValues,
389 | OnAfterAssignFieldValues = OnAfterAssignFieldValues
390 | };
391 | var objectVal = childNode.GetPropertiesOfUntypedObject(childNode._jsonNode);
392 | properties[objectValue.Name] = new UntypedObject(objectVal);
393 | }
394 | else
395 | {
396 | properties[objectValue.Name] = GetUntypedValue(property)!;
397 | }
398 | }
399 | }
400 | return properties;
401 | }
402 |
403 | private UntypedNode? GetUntypedValue(JsonElement jsonNode)
404 | {
405 | UntypedNode? untypedNode = null;
406 | switch(jsonNode.ValueKind)
407 | {
408 | case JsonValueKind.Number:
409 | if(jsonNode.TryGetInt32(out var intValue))
410 | {
411 | untypedNode = new UntypedInteger(intValue);
412 | }
413 | else if(jsonNode.TryGetInt64(out var longValue))
414 | {
415 | untypedNode = new UntypedLong(longValue);
416 | }
417 | else if(jsonNode.TryGetDecimal(out var decimalValue))
418 | {
419 | untypedNode = new UntypedDecimal(decimalValue);
420 | }
421 | else if(jsonNode.TryGetSingle(out var floatValue))
422 | {
423 | untypedNode = new UntypedFloat(floatValue);
424 | }
425 | else if(jsonNode.TryGetDouble(out var doubleValue))
426 | {
427 | untypedNode = new UntypedDouble(doubleValue);
428 | }
429 | else throw new InvalidOperationException("unexpected additional value type during number deserialization");
430 | break;
431 | case JsonValueKind.String:
432 | var stringValue = jsonNode.GetString();
433 | untypedNode = new UntypedString(stringValue);
434 | break;
435 | case JsonValueKind.True:
436 | case JsonValueKind.False:
437 | var boolValue = jsonNode.GetBoolean();
438 | untypedNode = new UntypedBoolean(boolValue);
439 | break;
440 | case JsonValueKind.Array:
441 | var arrayValue = GetCollectionOfUntypedValues(jsonNode);
442 | untypedNode = new UntypedArray(arrayValue);
443 | break;
444 | case JsonValueKind.Object:
445 | var objectValue = GetPropertiesOfUntypedObject(jsonNode);
446 | untypedNode = new UntypedObject(objectValue);
447 | break;
448 | case JsonValueKind.Null:
449 | case JsonValueKind.Undefined:
450 | untypedNode = new UntypedNull();
451 | break;
452 | }
453 |
454 | return untypedNode;
455 | }
456 |
457 | ///
458 | /// The action to perform before assigning field values.
459 | ///
460 | public Action? OnBeforeAssignFieldValues { get; set; }
461 |
462 | ///
463 | /// The action to perform after assigning field values.
464 | ///
465 | public Action? OnAfterAssignFieldValues { get; set; }
466 |
467 | ///
468 | /// Get the object of type from the json node
469 | ///
470 | /// The factory to use to create the model object.
471 | /// A object of the specified type
472 | public T GetObjectValue(ParsableFactory factory) where T : IParsable
473 | {
474 | // until interface exposes GetUntypedValue()
475 | var genericType = typeof(T);
476 | if(genericType == typeof(UntypedNode))
477 | {
478 | return (T)(object)GetUntypedValue()!;
479 | }
480 | var item = factory(this);
481 | OnBeforeAssignFieldValues?.Invoke(item);
482 | AssignFieldValues(item);
483 | OnAfterAssignFieldValues?.Invoke(item);
484 | return item;
485 | }
486 | private void AssignFieldValues(T item) where T : IParsable
487 | {
488 | if(_jsonNode.ValueKind != JsonValueKind.Object) return;
489 | IDictionary? itemAdditionalData = null;
490 | if(item is IAdditionalDataHolder holder)
491 | {
492 | holder.AdditionalData ??= new Dictionary();
493 | itemAdditionalData = holder.AdditionalData;
494 | }
495 | var fieldDeserializers = item.GetFieldDeserializers();
496 |
497 | foreach(var fieldValue in _jsonNode.EnumerateObject())
498 | {
499 | if(fieldDeserializers.ContainsKey(fieldValue.Name))
500 | {
501 | if(fieldValue.Value.ValueKind == JsonValueKind.Null)
502 | continue;// If the property is already null just continue. As calling functions like GetDouble,GetBoolValue do not process JsonValueKind.Null.
503 |
504 | var fieldDeserializer = fieldDeserializers[fieldValue.Name];
505 | Debug.WriteLine($"found property {fieldValue.Name} to deserialize");
506 | fieldDeserializer.Invoke(new JsonParseNode(fieldValue.Value, _jsonSerializerContext)
507 | {
508 | OnBeforeAssignFieldValues = OnBeforeAssignFieldValues,
509 | OnAfterAssignFieldValues = OnAfterAssignFieldValues
510 | });
511 | }
512 | else if (itemAdditionalData != null)
513 | {
514 | Debug.WriteLine($"found additional property {fieldValue.Name} to deserialize");
515 | IDictionaryExtensions.TryAdd(itemAdditionalData, fieldValue.Name, TryGetAnything(fieldValue.Value)!);
516 | }
517 | else
518 | {
519 | Debug.WriteLine($"found additional property {fieldValue.Name} to deserialize but the model doesn't support additional data");
520 | }
521 | }
522 | }
523 | private object? TryGetAnything(JsonElement element)
524 | {
525 | switch(element.ValueKind)
526 | {
527 | case JsonValueKind.Number:
528 | if(element.TryGetDecimal(out var dec)) return dec;
529 | else if(element.TryGetDouble(out var db)) return db;
530 | else if(element.TryGetInt16(out var s)) return s;
531 | else if(element.TryGetInt32(out var i)) return i;
532 | else if(element.TryGetInt64(out var l)) return l;
533 | else if(element.TryGetSingle(out var f)) return f;
534 | else if(element.TryGetUInt16(out var us)) return us;
535 | else if(element.TryGetUInt32(out var ui)) return ui;
536 | else if(element.TryGetUInt64(out var ul)) return ul;
537 | else throw new InvalidOperationException("unexpected additional value type during number deserialization");
538 | case JsonValueKind.String:
539 | if(element.TryGetDateTime(out var dt)) return dt;
540 | else if(element.TryGetDateTimeOffset(out var dto)) return dto;
541 | else if(element.TryGetGuid(out var g)) return g;
542 | else return element.GetString();
543 | case JsonValueKind.Array:
544 | case JsonValueKind.Object:
545 | return GetUntypedValue(element);
546 | case JsonValueKind.True:
547 | return true;
548 | case JsonValueKind.False:
549 | return false;
550 | case JsonValueKind.Null:
551 | case JsonValueKind.Undefined:
552 | return null;
553 | default:
554 | throw new InvalidOperationException($"unexpected additional value type during deserialization json kind : {element.ValueKind}");
555 | }
556 | }
557 |
558 | ///
559 | /// Get the child node of the specified identifier
560 | ///
561 | /// The identifier of the child node
562 | /// An instance of
563 | public IParseNode? GetChildNode(string identifier)
564 | {
565 | if(_jsonNode.ValueKind == JsonValueKind.Object && _jsonNode.TryGetProperty(identifier ?? throw new ArgumentNullException(nameof(identifier)), out var jsonElement))
566 | {
567 | return new JsonParseNode(jsonElement, _jsonSerializerContext)
568 | {
569 | OnBeforeAssignFieldValues = OnBeforeAssignFieldValues,
570 | OnAfterAssignFieldValues = OnAfterAssignFieldValues
571 | };
572 | }
573 |
574 | return default;
575 | }
576 |
577 | #if NET5_0_OR_GREATER
578 | private static string ToEnumRawName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(string value) where T : struct, Enum
579 | #else
580 | private static string ToEnumRawName(string value) where T : struct, Enum
581 | #endif
582 | {
583 | foreach (var field in typeof(T).GetFields())
584 | {
585 | if (field.GetCustomAttribute() is {} attr && value.Equals(attr.Value, StringComparison.Ordinal))
586 | {
587 | return field.Name;
588 | }
589 | }
590 |
591 | return value;
592 | }
593 | }
594 | }
595 |
--------------------------------------------------------------------------------
/src/JsonParseNodeFactory.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3 | // ------------------------------------------------------------------------------
4 |
5 | using System;
6 | using System.IO;
7 | using System.Text.Json;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using Microsoft.Kiota.Abstractions.Serialization;
11 |
12 | namespace Microsoft.Kiota.Serialization.Json
13 | {
14 | ///
15 | /// The implementation for json content types
16 | ///
17 | public class JsonParseNodeFactory : IAsyncParseNodeFactory
18 | {
19 | private readonly KiotaJsonSerializationContext _jsonJsonSerializationContext;
20 |
21 | ///
22 | /// The constructor.
23 | ///
24 | public JsonParseNodeFactory()
25 | : this(KiotaJsonSerializationContext.Default)
26 | {
27 | }
28 |
29 | ///
30 | /// The constructor.
31 | ///
32 | /// The KiotaSerializationContext to utilize.
33 | public JsonParseNodeFactory(KiotaJsonSerializationContext jsonJsonSerializationContext)
34 | {
35 | _jsonJsonSerializationContext = jsonJsonSerializationContext;
36 | }
37 |
38 | ///
39 | /// The valid content type for json
40 | ///
41 | public string ValidContentType { get; } = "application/json";
42 |
43 | ///
44 | /// Gets the root of the json to be read.
45 | ///
46 | /// The content type of the stream to be parsed
47 | /// The containing json to parse.
48 | /// An instance of for json manipulation
49 | [Obsolete("Use GetRootParseNodeAsync instead")]
50 | public IParseNode GetRootParseNode(string contentType, Stream content)
51 | {
52 | if(string.IsNullOrEmpty(contentType))
53 | throw new ArgumentNullException(nameof(contentType));
54 | else if(!ValidContentType.Equals(contentType, StringComparison.OrdinalIgnoreCase))
55 | throw new ArgumentOutOfRangeException($"expected a {ValidContentType} content type");
56 |
57 | _ = content ?? throw new ArgumentNullException(nameof(content));
58 |
59 | using var jsonDocument = JsonDocument.Parse(content);
60 | return new JsonParseNode(jsonDocument.RootElement.Clone(), _jsonJsonSerializationContext);
61 | }
62 | ///
63 | /// Asynchronously gets the root of the json to be read.
64 | ///
65 | /// The content type of the stream to be parsed
66 | /// The containing json to parse.
67 | /// The cancellation token for the task
68 | /// An instance of for json manipulation
69 | public async Task GetRootParseNodeAsync(string contentType, Stream content,
70 | CancellationToken cancellationToken = default)
71 | {
72 | if(string.IsNullOrEmpty(contentType))
73 | throw new ArgumentNullException(nameof(contentType));
74 | else if(!ValidContentType.Equals(contentType, StringComparison.OrdinalIgnoreCase))
75 | throw new ArgumentOutOfRangeException($"expected a {ValidContentType} content type");
76 |
77 | _ = content ?? throw new ArgumentNullException(nameof(content));
78 |
79 | using var jsonDocument = await JsonDocument.ParseAsync(content, cancellationToken: cancellationToken).ConfigureAwait(false);
80 | return new JsonParseNode(jsonDocument.RootElement.Clone(), _jsonJsonSerializationContext);
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/JsonSerializationWriter.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3 | // ------------------------------------------------------------------------------
4 |
5 | using System;
6 | using System.IO;
7 | using System.Text.Json;
8 | using Microsoft.Kiota.Abstractions.Serialization;
9 | using System.Collections.Generic;
10 | using System.Reflection;
11 | using System.Runtime.Serialization;
12 | using Microsoft.Kiota.Abstractions.Extensions;
13 | using Microsoft.Kiota.Abstractions;
14 | using System.Xml;
15 | using System.Text;
16 |
17 | #if NET5_0_OR_GREATER
18 | using System.Diagnostics.CodeAnalysis;
19 | #endif
20 |
21 | namespace Microsoft.Kiota.Serialization.Json
22 | {
23 | ///
24 | /// The implementation for json content types.
25 | ///
26 | public class JsonSerializationWriter : ISerializationWriter, IDisposable
27 | {
28 | private readonly MemoryStream _stream = new MemoryStream();
29 | private readonly KiotaJsonSerializationContext _kiotaJsonSerializationContext;
30 |
31 | ///
32 | /// The instance for writing json content
33 | ///
34 | public readonly Utf8JsonWriter writer;
35 |
36 | ///
37 | /// The constructor
38 | ///
39 | public JsonSerializationWriter()
40 | : this(KiotaJsonSerializationContext.Default)
41 | {
42 | }
43 |
44 | ///
45 | /// The constructor
46 | ///
47 | /// The KiotaJsonSerializationContext to use.
48 | public JsonSerializationWriter(KiotaJsonSerializationContext kiotaJsonSerializationContext)
49 | {
50 | _kiotaJsonSerializationContext = kiotaJsonSerializationContext;
51 | writer = new Utf8JsonWriter(_stream);
52 | }
53 |
54 | ///
55 | /// The action to perform before object serialization
56 | ///
57 | public Action? OnBeforeObjectSerialization { get; set; }
58 |
59 | ///
60 | /// The action to perform after object serialization
61 | ///
62 | public Action? OnAfterObjectSerialization { get; set; }
63 |
64 | ///
65 | /// The action to perform on the start of object serialization
66 | ///
67 | public Action? OnStartObjectSerialization { get; set; }
68 |
69 | ///
70 | /// Get the stream of the serialized content
71 | ///
72 | /// The of the serialized content
73 | public Stream GetSerializedContent()
74 | {
75 | writer.Flush();
76 | _stream.Position = 0;
77 | return _stream;
78 | }
79 |
80 | ///
81 | /// Write the string value
82 | ///
83 | /// The key of the json node
84 | /// The string value
85 | public void WriteStringValue(string? key, string? value)
86 | {
87 | if(value != null)
88 | {
89 | // we want to keep empty string because they are meaningful
90 | if(!string.IsNullOrEmpty(key))
91 | writer.WritePropertyName(key!);
92 | JsonSerializer.Serialize(writer, value, TypeConstants.StringType, _kiotaJsonSerializationContext);
93 | }
94 | }
95 |
96 | ///
97 | /// Write the boolean value
98 | ///
99 | /// The key of the json node
100 | /// The boolean value
101 | public void WriteBoolValue(string? key, bool? value)
102 | {
103 | if(!string.IsNullOrEmpty(key) && value.HasValue)
104 | writer.WritePropertyName(key!);
105 | if(value.HasValue)
106 | JsonSerializer.Serialize(writer, value.Value, TypeConstants.BooleanType, _kiotaJsonSerializationContext);
107 | }
108 |
109 | ///
110 | /// Write the byte value
111 | ///
112 | /// The key of the json node
113 | /// The byte value
114 | public void WriteByteValue(string? key, byte? value)
115 | {
116 | if(!string.IsNullOrEmpty(key) && value.HasValue)
117 | writer.WritePropertyName(key!);
118 | if(value.HasValue)
119 | JsonSerializer.Serialize(writer, value.Value, TypeConstants.ByteType, _kiotaJsonSerializationContext);
120 | }
121 |
122 | ///
123 | /// Write the sbyte value
124 | ///
125 | /// The key of the json node
126 | /// The sbyte value
127 | public void WriteSbyteValue(string? key, sbyte? value)
128 | {
129 | if(!string.IsNullOrEmpty(key) && value.HasValue)
130 | writer.WritePropertyName(key!);
131 | if(value.HasValue)
132 | JsonSerializer.Serialize(writer, value.Value, TypeConstants.SbyteType, _kiotaJsonSerializationContext);
133 | }
134 |
135 | ///
136 | /// Write the int value
137 | ///
138 | /// The key of the json node
139 | /// The int value
140 | public void WriteIntValue(string? key, int? value)
141 | {
142 | if(!string.IsNullOrEmpty(key) && value.HasValue)
143 | writer.WritePropertyName(key!);
144 | if(value.HasValue)
145 | JsonSerializer.Serialize(writer, value, TypeConstants.IntType, _kiotaJsonSerializationContext);
146 | }
147 |
148 | ///
149 | /// Write the float value
150 | ///
151 | /// The key of the json node
152 | /// The float value
153 | public void WriteFloatValue(string? key, float? value)
154 | {
155 | if(!string.IsNullOrEmpty(key) && value.HasValue)
156 | writer.WritePropertyName(key!);
157 | if(value.HasValue)
158 | JsonSerializer.Serialize(writer, value.Value, TypeConstants.FloatType, _kiotaJsonSerializationContext);
159 | }
160 |
161 | ///
162 | /// Write the long value
163 | ///
164 | /// The key of the json node
165 | /// The long value
166 | public void WriteLongValue(string? key, long? value)
167 | {
168 | if(!string.IsNullOrEmpty(key) && value.HasValue)
169 | writer.WritePropertyName(key!);
170 | if(value.HasValue)
171 | JsonSerializer.Serialize(writer, value.Value, TypeConstants.LongType, _kiotaJsonSerializationContext);
172 | }
173 |
174 | ///
175 | /// Write the double value
176 | ///
177 | /// The key of the json node
178 | /// The double value
179 | public void WriteDoubleValue(string? key, double? value)
180 | {
181 | if(!string.IsNullOrEmpty(key) && value.HasValue)
182 | writer.WritePropertyName(key!);
183 | if(value.HasValue)
184 | JsonSerializer.Serialize(writer, value.Value, TypeConstants.DoubleType, _kiotaJsonSerializationContext);
185 |
186 | }
187 |
188 | ///
189 | /// Write the decimal value
190 | ///
191 | /// The key of the json node
192 | /// The decimal value
193 | public void WriteDecimalValue(string? key, decimal? value)
194 | {
195 | if(!string.IsNullOrEmpty(key) && value.HasValue)
196 | writer.WritePropertyName(key!);
197 | if(value.HasValue)
198 | JsonSerializer.Serialize(writer, value.Value, TypeConstants.DecimalType, _kiotaJsonSerializationContext);
199 |
200 | }
201 |
202 | ///
203 | /// Write the Guid value
204 | ///
205 | /// The key of the json node
206 | /// The Guid value
207 | public void WriteGuidValue(string? key, Guid? value)
208 | {
209 | if(!string.IsNullOrEmpty(key) && value.HasValue)
210 | writer.WritePropertyName(key!);
211 | if(value.HasValue)
212 | JsonSerializer.Serialize(writer, value.Value, TypeConstants.GuidType, _kiotaJsonSerializationContext);
213 | }
214 |
215 | ///
216 | /// Write the DateTimeOffset value
217 | ///
218 | /// The key of the json node
219 | /// The DateTimeOffset value
220 | public void WriteDateTimeOffsetValue(string? key, DateTimeOffset? value)
221 | {
222 | if(!string.IsNullOrEmpty(key) && value.HasValue)
223 | writer.WritePropertyName(key!);
224 | if(value.HasValue)
225 | JsonSerializer.Serialize(writer, value.Value, TypeConstants.DateTimeOffsetType, _kiotaJsonSerializationContext);
226 | }
227 |
228 | ///
229 | /// Write the TimeSpan(An ISO8601 duration.For example, PT1M is "period time of 1 minute") value.
230 | ///
231 | /// The key of the json node
232 | /// The TimeSpan value
233 | public void WriteTimeSpanValue(string? key, TimeSpan? value)
234 | {
235 | if(value.HasValue)
236 | WriteStringValue(key, XmlConvert.ToString(value.Value));
237 | }
238 |
239 | ///
240 | /// Write the Date value
241 | ///
242 | /// The key of the json node
243 | /// The Date value
244 | public void WriteDateValue(string? key, Date? value)
245 | => WriteStringValue(key, value?.ToString());
246 |
247 | ///
248 | /// Write the Time value
249 | ///
250 | /// The key of the json node
251 | /// The Time value
252 | public void WriteTimeValue(string? key, Time? value)
253 | => WriteStringValue(key, value?.ToString());
254 |
255 | ///
256 | /// Write the null value
257 | ///
258 | /// The key of the json node
259 | public void WriteNullValue(string? key)
260 | {
261 | if(!string.IsNullOrEmpty(key))
262 | writer.WritePropertyName(key!);
263 | writer.WriteNullValue();
264 | }
265 |
266 | ///
267 | /// Write the enumeration value of type
268 | ///
269 | /// The key of the json node
270 | /// The enumeration value
271 | #if NET5_0_OR_GREATER
272 | public void WriteEnumValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(string? key, T? value) where T : struct, Enum
273 | #else
274 | public void WriteEnumValue(string? key, T? value) where T : struct, Enum
275 | #endif
276 | {
277 | if(!string.IsNullOrEmpty(key) && value.HasValue)
278 | writer.WritePropertyName(key!);
279 | if(value.HasValue)
280 | {
281 | if (typeof(T).IsDefined(typeof(FlagsAttribute)))
282 | {
283 | var values =
284 | #if NET5_0_OR_GREATER
285 | Enum.GetValues();
286 | #else
287 | (T[])Enum.GetValues(typeof(T));
288 | #endif
289 | StringBuilder valueNames = new StringBuilder();
290 | foreach (var x in values)
291 | {
292 | if(value.Value.HasFlag(x) && GetEnumName(x) is string valueName)
293 | {
294 | if (valueNames.Length > 0)
295 | valueNames.Append(",");
296 | valueNames.Append(valueName);
297 | }
298 | }
299 | WriteStringValue(null, valueNames.ToString());
300 | }
301 | else WriteStringValue(null, GetEnumName(value.Value));
302 | }
303 | }
304 |
305 | ///
306 | /// Write the collection of primitives of type
307 | ///
308 | /// The key of the json node
309 | /// The primitive collection
310 | public void WriteCollectionOfPrimitiveValues(string? key, IEnumerable? values)
311 | {
312 | if(values != null)
313 | { //empty array is meaningful
314 | if(!string.IsNullOrEmpty(key))
315 | writer.WritePropertyName(key!);
316 | writer.WriteStartArray();
317 | foreach(var collectionValue in values)
318 | WriteAnyValue(null, collectionValue);
319 | writer.WriteEndArray();
320 | }
321 | }
322 |
323 | ///
324 | /// Write the collection of objects of type
325 | ///
326 | /// The key of the json node
327 | /// The object collection
328 | public void WriteCollectionOfObjectValues(string? key, IEnumerable? values) where T : IParsable
329 | {
330 | if(values != null)
331 | {
332 | // empty array is meaningful
333 | if(!string.IsNullOrEmpty(key))
334 | writer.WritePropertyName(key!);
335 | writer.WriteStartArray();
336 | foreach(var item in values)
337 | WriteObjectValue(null, item);
338 | writer.WriteEndArray();
339 | }
340 | }
341 | ///
342 | /// Writes the specified collection of enum values to the stream with an optional given key.
343 | ///
344 | /// The key to be used for the written value. May be null.
345 | /// The enum values to be written.
346 | #if NET5_0_OR_GREATER
347 | public void WriteCollectionOfEnumValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]T>(string? key, IEnumerable? values) where T : struct, Enum
348 | #else
349 | public void WriteCollectionOfEnumValues(string? key, IEnumerable? values) where T : struct, Enum
350 | #endif
351 | {
352 | if(values != null)
353 | { //empty array is meaningful
354 | if(!string.IsNullOrEmpty(key))
355 | writer.WritePropertyName(key!);
356 | writer.WriteStartArray();
357 | foreach(var item in values)
358 | WriteEnumValue(null, item);
359 | writer.WriteEndArray();
360 | }
361 | }
362 | ///
363 | /// Writes the specified byte array as a base64 string to the stream with an optional given key.
364 | ///
365 | /// The key to be used for the written value. May be null.
366 | /// The byte array to be written.
367 | public void WriteByteArrayValue(string? key, byte[]? value)
368 | {
369 | if(value != null)//empty array is meaningful
370 | WriteStringValue(key, value.Length > 0 ? Convert.ToBase64String(value) : string.Empty);
371 | }
372 |
373 | ///
374 | /// Write the object of type
375 | ///
376 | /// The key of the json node
377 | /// The object instance to write
378 | /// The additional values to merge to the main value when serializing an intersection wrapper.
379 | public void WriteObjectValue(string? key, T? value, params IParsable?[] additionalValuesToMerge) where T : IParsable
380 | {
381 | var filteredAdditionalValuesToMerge = (IParsable[])Array.FindAll(additionalValuesToMerge, static x => x is not null);
382 | if(value != null || filteredAdditionalValuesToMerge.Length > 0)
383 | {
384 | // until interface exposes WriteUntypedValue()
385 | var serializingUntypedNode = value is UntypedNode;
386 | if(!serializingUntypedNode && !string.IsNullOrEmpty(key))
387 | writer.WritePropertyName(key!);
388 | if(value != null)
389 | OnBeforeObjectSerialization?.Invoke(value);
390 |
391 | if(serializingUntypedNode)
392 | {
393 | var untypedNode = value as UntypedNode;
394 | OnStartObjectSerialization?.Invoke(untypedNode!, this);
395 | WriteUntypedValue(key, untypedNode);
396 | OnAfterObjectSerialization?.Invoke(untypedNode!);
397 | }
398 | else
399 | {
400 | var serializingScalarValue = value is IComposedTypeWrapper;
401 | if(!serializingScalarValue)
402 | writer.WriteStartObject();
403 | if(value != null)
404 | {
405 | OnStartObjectSerialization?.Invoke(value, this);
406 | value.Serialize(this);
407 | }
408 | foreach(var additionalValueToMerge in filteredAdditionalValuesToMerge)
409 | {
410 | OnBeforeObjectSerialization?.Invoke(additionalValueToMerge!);
411 | OnStartObjectSerialization?.Invoke(additionalValueToMerge!, this);
412 | additionalValueToMerge!.Serialize(this);
413 | OnAfterObjectSerialization?.Invoke(additionalValueToMerge);
414 | }
415 | if(!serializingScalarValue)
416 | writer.WriteEndObject();
417 | }
418 | if(value != null) OnAfterObjectSerialization?.Invoke(value);
419 | }
420 | }
421 |
422 | ///
423 | /// Write the additional data property bag
424 | ///
425 | /// The additional data dictionary
426 | public void WriteAdditionalData(IDictionary value)
427 | {
428 | if(value == null)
429 | return;
430 |
431 | foreach(var dataValue in value)
432 | WriteAnyValue(dataValue.Key, dataValue.Value);
433 | }
434 |
435 | private void WriteNonParsableObjectValue(string? key, T value)
436 | {
437 | if(!string.IsNullOrEmpty(key))
438 | writer.WritePropertyName(key!);
439 | writer.WriteStartObject();
440 | if(value == null)
441 | writer.WriteNullValue();
442 | else
443 | foreach(var oProp in value.GetType().GetProperties())
444 | WriteAnyValue(oProp.Name, oProp.GetValue(value));
445 | writer.WriteEndObject();
446 | }
447 |
448 | private void WriteAnyValue(string? key, T value)
449 | {
450 | switch(value)
451 | {
452 | case string s:
453 | WriteStringValue(key, s);
454 | break;
455 | case bool b:
456 | WriteBoolValue(key, b);
457 | break;
458 | case byte b:
459 | WriteByteValue(key, b);
460 | break;
461 | case sbyte b:
462 | WriteSbyteValue(key, b);
463 | break;
464 | case int i:
465 | WriteIntValue(key, i);
466 | break;
467 | case float f:
468 | WriteFloatValue(key, f);
469 | break;
470 | case long l:
471 | WriteLongValue(key, l);
472 | break;
473 | case double d:
474 | WriteDoubleValue(key, d);
475 | break;
476 | case decimal dec:
477 | WriteDecimalValue(key, dec);
478 | break;
479 | case Guid g:
480 | WriteGuidValue(key, g);
481 | break;
482 | case DateTimeOffset dto:
483 | WriteDateTimeOffsetValue(key, dto);
484 | break;
485 | case TimeSpan timeSpan:
486 | WriteTimeSpanValue(key, timeSpan);
487 | break;
488 | case IEnumerable