├── .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 coll: 489 | WriteCollectionOfPrimitiveValues(key, coll); 490 | break; 491 | case UntypedNode node: 492 | WriteUntypedValue(key, node); 493 | break; 494 | case IParsable parseable: 495 | WriteObjectValue(key, parseable); 496 | break; 497 | case Date date: 498 | WriteDateValue(key, date); 499 | break; 500 | case DateTime dateTime: 501 | WriteDateTimeOffsetValue(key, new DateTimeOffset(dateTime)); 502 | break; 503 | case Time time: 504 | WriteTimeValue(key, time); 505 | break; 506 | case JsonElement jsonElement: 507 | if(!string.IsNullOrEmpty(key)) 508 | writer.WritePropertyName(key!); 509 | jsonElement.WriteTo(writer); 510 | break; 511 | case object o: 512 | WriteNonParsableObjectValue(key, o); 513 | break; 514 | case null: 515 | WriteNullValue(key); 516 | break; 517 | default: 518 | throw new InvalidOperationException($"error serialization additional data value with key {key}, unknown type {value?.GetType()}"); 519 | } 520 | } 521 | 522 | /// 523 | public void Dispose() 524 | { 525 | writer.Dispose(); 526 | GC.SuppressFinalize(this); 527 | } 528 | #if NET5_0_OR_GREATER 529 | private static string? GetEnumName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(T value) where T : struct, Enum 530 | #else 531 | private static string? GetEnumName(T value) where T : struct, Enum 532 | #endif 533 | { 534 | var type = typeof(T); 535 | 536 | if(Enum.GetName(type, value) is not { } name) 537 | throw new ArgumentException($"Invalid Enum value {value} for enum of type {type}"); 538 | 539 | if(type.GetField(name)?.GetCustomAttribute() is { } attribute) 540 | return attribute.Value; 541 | 542 | return name.ToFirstCharacterLowerCase(); 543 | } 544 | /// 545 | /// Writes a untyped value for the specified key. 546 | /// 547 | /// The key to be used for the written value. May be null. 548 | /// The untyped node. 549 | private void WriteUntypedValue(string? key, UntypedNode? value) 550 | { 551 | switch(value) 552 | { 553 | case UntypedString untypedString: 554 | WriteStringValue(key, untypedString.GetValue()); 555 | break; 556 | case UntypedBoolean untypedBoolean: 557 | WriteBoolValue(key, untypedBoolean.GetValue()); 558 | break; 559 | case UntypedInteger untypedInteger: 560 | WriteIntValue(key, untypedInteger.GetValue()); 561 | break; 562 | case UntypedLong untypedLong: 563 | WriteLongValue(key, untypedLong.GetValue()); 564 | break; 565 | case UntypedDecimal untypedDecimal: 566 | WriteDecimalValue(key, untypedDecimal.GetValue()); 567 | break; 568 | case UntypedFloat untypedFloat: 569 | WriteFloatValue(key, untypedFloat.GetValue()); 570 | break; 571 | case UntypedDouble untypedDouble: 572 | WriteDoubleValue(key, untypedDouble.GetValue()); 573 | break; 574 | case UntypedObject untypedObject: 575 | WriteUntypedObject(key, untypedObject); 576 | break; 577 | case UntypedArray array: 578 | WriteUntypedArray(key, array); 579 | break; 580 | case UntypedNull: 581 | WriteNullValue(key); 582 | break; 583 | } 584 | } 585 | 586 | /// 587 | /// Write a untyped object for the specified key. 588 | /// 589 | /// The key to be used for the written value. May be null. 590 | /// The untyped object. 591 | private void WriteUntypedObject(string? key, UntypedObject? value) 592 | { 593 | if (value != null) 594 | { 595 | if(!string.IsNullOrEmpty(key)) writer.WritePropertyName(key!); 596 | writer.WriteStartObject(); 597 | foreach(var item in value.GetValue()) 598 | WriteUntypedValue(item.Key, item.Value); 599 | writer.WriteEndObject(); 600 | } 601 | } 602 | 603 | /// 604 | /// Writes the specified collection of untyped values. 605 | /// 606 | /// The key to be used for the written value. May be null. 607 | /// The collection of untyped values. 608 | private void WriteUntypedArray(string? key, UntypedArray? array) 609 | { 610 | if (array != null) 611 | { 612 | if(!string.IsNullOrEmpty(key)) writer.WritePropertyName(key!); 613 | writer.WriteStartArray(); 614 | foreach(var item in array.GetValue()) 615 | WriteUntypedValue(null, item); 616 | writer.WriteEndArray(); 617 | } 618 | } 619 | } 620 | } 621 | -------------------------------------------------------------------------------- /src/JsonSerializationWriterFactory.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 Microsoft.Kiota.Abstractions.Serialization; 7 | 8 | namespace Microsoft.Kiota.Serialization.Json 9 | { 10 | /// 11 | /// The implementation for the json content type 12 | /// 13 | public class JsonSerializationWriterFactory : ISerializationWriterFactory 14 | { 15 | private readonly KiotaJsonSerializationContext _kiotaJsonSerializationContext; 16 | 17 | /// 18 | /// The constructor. 19 | /// 20 | public JsonSerializationWriterFactory() 21 | : this(KiotaJsonSerializationContext.Default) 22 | { 23 | } 24 | 25 | /// 26 | /// The constructor. 27 | /// 28 | /// The KiotaJsonSerializationContext to use. 29 | public JsonSerializationWriterFactory(KiotaJsonSerializationContext kiotaJsonSerializationContext) 30 | { 31 | _kiotaJsonSerializationContext = kiotaJsonSerializationContext; 32 | } 33 | 34 | /// 35 | /// The valid content type for json 36 | /// 37 | public string ValidContentType { get; } = "application/json"; 38 | 39 | /// 40 | /// Get a valid for the content type 41 | /// 42 | /// The content type to search for 43 | /// A instance for json writing 44 | public ISerializationWriter GetSerializationWriter(string contentType) 45 | { 46 | if(string.IsNullOrEmpty(contentType)) 47 | throw new ArgumentNullException(nameof(contentType)); 48 | else if(!ValidContentType.Equals(contentType, StringComparison.OrdinalIgnoreCase)) 49 | throw new ArgumentOutOfRangeException($"expected a {ValidContentType} content type"); 50 | 51 | return new JsonSerializationWriter(_kiotaJsonSerializationContext); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/KiotaJsonSerializationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | using Microsoft.Kiota.Abstractions; 4 | 5 | namespace Microsoft.Kiota.Serialization.Json; 6 | 7 | /// 8 | /// Json serialization context for Kiota. 9 | /// 10 | [JsonSerializable(typeof(string))] 11 | [JsonSerializable(typeof(bool))] 12 | [JsonSerializable(typeof(bool?))] 13 | [JsonSerializable(typeof(byte))] 14 | [JsonSerializable(typeof(byte?))] 15 | [JsonSerializable(typeof(sbyte))] 16 | [JsonSerializable(typeof(sbyte?))] 17 | [JsonSerializable(typeof(int))] 18 | [JsonSerializable(typeof(int?))] 19 | [JsonSerializable(typeof(float))] 20 | [JsonSerializable(typeof(float?))] 21 | [JsonSerializable(typeof(long))] 22 | [JsonSerializable(typeof(long?))] 23 | [JsonSerializable(typeof(double))] 24 | [JsonSerializable(typeof(double?))] 25 | [JsonSerializable(typeof(decimal))] 26 | [JsonSerializable(typeof(decimal?))] 27 | [JsonSerializable(typeof(Guid))] 28 | [JsonSerializable(typeof(Guid?))] 29 | [JsonSerializable(typeof(DateTimeOffset))] 30 | [JsonSerializable(typeof(DateTimeOffset?))] 31 | [JsonSerializable(typeof(TimeSpan))] 32 | [JsonSerializable(typeof(TimeSpan?))] 33 | [JsonSerializable(typeof(Date))] 34 | [JsonSerializable(typeof(Date?))] 35 | [JsonSerializable(typeof(Time))] 36 | [JsonSerializable(typeof(Time?))] 37 | public partial class KiotaJsonSerializationContext : JsonSerializerContext; 38 | -------------------------------------------------------------------------------- /src/Microsoft.Kiota.Serialization.Json.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kiota JSON serialization provider implementation with System.Text.Json. 6 | © Microsoft Corporation. All rights reserved. 7 | Kiota JSON Serialization Library for dotnet using System.Text.Json 8 | Microsoft 9 | 10 | netstandard2.0;netstandard2.1;net5.0;net6.0;net8.0 11 | latest 12 | true 13 | http://go.microsoft.com/fwlink/?LinkID=288890 14 | https://github.com/microsoft/kiota-serialization-json-dotnet 15 | https://aka.ms/kiota/docs 16 | true 17 | true 18 | 1.3.3 19 | 20 | true 21 | true 22 | 23 | 24 | false 25 | false 26 | 35MSSharedLib1024.snk 27 | 28 | https://github.com/microsoft/kiota-serialization-json-dotnet/releases 29 | 30 | latest 31 | enable 32 | true 33 | true 34 | MIT 35 | README.md 36 | $(NoWarn);NU5048;NETSDK1138;CS1591 37 | 38 | 39 | true 40 | 41 | 42 | true 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | true 57 | 58 | 59 | 60 | 61 | True 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/TypeConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Kiota.Abstractions; 3 | 4 | namespace Microsoft.Kiota.Serialization.Json; 5 | 6 | static internal class TypeConstants 7 | { 8 | static readonly internal Type BooleanType = typeof(bool?); 9 | static readonly internal Type ByteType = typeof(byte?); 10 | static readonly internal Type SbyteType = typeof(sbyte?); 11 | static readonly internal Type StringType = typeof(string); 12 | static readonly internal Type IntType = typeof(int?); 13 | static readonly internal Type FloatType = typeof(float?); 14 | static readonly internal Type LongType = typeof(long?); 15 | static readonly internal Type DoubleType = typeof(double?); 16 | static readonly internal Type DecimalType = typeof(decimal?); 17 | static readonly internal Type GuidType = typeof(Guid?); 18 | static readonly internal Type DateTimeOffsetType = typeof(DateTimeOffset?); 19 | static readonly internal Type TimeSpanType = typeof(TimeSpan?); 20 | static readonly internal Type DateType = typeof(Date?); 21 | static readonly internal Type TimeType = typeof(Time?); 22 | } --------------------------------------------------------------------------------