From d3922e838b536a7413382f6a07dfead5efb25a0b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 31 Mar 2022 13:34:33 -0400 Subject: [PATCH] - library move --- .github/CODEOWNERS | 1 + .github/dependabot.yml | 12 + .github/workflows/codeql-analysis.yml | 70 +++ .github/workflows/create-release.yml | 21 + .github/workflows/go.yml | 24 + CHANGELOG.md | 18 + README.md | 20 +- go.mod | 17 + go.sum | 40 ++ json_parse_node.go | 474 +++++++++++++++ json_parse_node_factory.go | 35 ++ json_parse_node_test.go | 126 ++++ json_serialization_writer.go | 840 ++++++++++++++++++++++++++ json_serialization_writer_factory.go | 35 ++ json_serialization_writer_test.go | 34 ++ 15 files changed, 1759 insertions(+), 8 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/create-release.yml create mode 100644 .github/workflows/go.yml create mode 100644 CHANGELOG.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 json_parse_node.go create mode 100644 json_parse_node_factory.go create mode 100644 json_parse_node_test.go create mode 100644 json_serialization_writer.go create mode 100644 json_serialization_writer_factory.go create mode 100644 json_serialization_writer_test.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..2329649 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @andrueastman @baywet @darrelmiller @zengin @MichaelMainer @ddyett @peombwa @nikithauc @ramsessanchez @calebkiage diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c0750da --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: gomod + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..4ce3798 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '40 16 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..f6a93e4 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,21 @@ +name: Create GitHub release +on: + push: + tags: ['v*'] + +jobs: + create_release: + name: Create Release + environment: + name: gh_releases + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Release + uses: anton-yurchenko/git-release@v4.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DRAFT_RELEASE: "false" + PRE_RELEASE: "false" + CHANGELOG_FILE: "CHANGELOG.md" + ALLOW_EMPTY_CHANGELOG: "true" diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..1844221 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,24 @@ +name: Go + +on: + workflow_dispatch: + push: + branches: [ main ] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + env: + relativePath: ./ + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v2 + with: + go-version: '^1.17.3' + - name: Install dependencies + run: go install + working-directory: ${{ env.relativePath }} + - name: Build SDK project + run: go build + working-directory: ${{ env.relativePath }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ce10248 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +### Changed + +## [0.1.0] - 2022-03-30 + +### Added + +- Initial tagged release of the library. diff --git a/README.md b/README.md index 5cd7cec..b822e3b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ -# Project +# Kiota Json Serialization Library for dotnet -> This repo has been populated by an initial template to help get you started. Please -> make sure to update the content to build a great experience for community-building. +![Go Serialization Text](https://github.com/microsoft/kiota-serialization-json-go/actions/workflows/go.yml/badge.svg) -As the maintainer of this project, please make a few updates: +The Json Serialization Library for Go is the Go JSON serialization library implementation. -- Improving this README.MD file to provide a great experience -- Updating SUPPORT.MD with content about this project's support experience -- Understanding the security reporting process in SECURITY.MD -- Remove this section from the README +A [Kiota](https://github.com/microsoft/kiota) generated project will need a reference to a json serialization package to handle json payloads from an API endpoint. + +Read more about Kiota [here](https://github.com/microsoft/kiota/blob/main/README.md). + +## Using the Kiota Json Serialization Library + +```Shell + go get github.com/microsoft/kiota-serialization-json-go +``` ## Contributing diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2ecf6e8 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/microsoft/kiota-serialization-json-go + +go 1.17 + +require ( + github.com/google/uuid v1.3.0 + github.com/microsoft/kiota-abstractions-go v0.1.0 + github.com/stretchr/testify v1.7.1 +) + +require ( + github.com/cjlapao/common-go v0.0.19 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/yosida95/uritemplate/v3 v3.0.1 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..966c7af --- /dev/null +++ b/go.sum @@ -0,0 +1,40 @@ +github.com/cjlapao/common-go v0.0.16 h1:3CCMidbHuc+nvKHb1mX/rflMPrpeGQvoVk30b9CC/00= +github.com/cjlapao/common-go v0.0.16/go.mod h1:zGdh2KmXnH4HTRfT7vPpY41cws776KULk44f09OPJgs= +github.com/cjlapao/common-go v0.0.18 h1:j6kT/0pmJ69HmUCs9LrCc7IWyxTOh2WYJyZMzUYTULs= +github.com/cjlapao/common-go v0.0.18/go.mod h1:zGdh2KmXnH4HTRfT7vPpY41cws776KULk44f09OPJgs= +github.com/cjlapao/common-go v0.0.19 h1:dhPllblgZwHn92u8wNleLDCg6+T2rjV57H8eyz82tbQ= +github.com/cjlapao/common-go v0.0.19/go.mod h1:EVLEXIxsBHblPMrhJYOvL4yBCcBj7IYDdW88VlfxpPM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/microsoft/kiota-abstractions-go v0.1.0 h1:oaUAitWTfV/v9nuyF8Et5d74/n8jDTrQ/yGfttFmkW0= +github.com/microsoft/kiota-abstractions-go v0.1.0/go.mod h1:72wco+cRPd6SJf6R2wjEQT/BzbpfCGJQ/MGJixj+AIM= +github.com/microsoft/kiota/abstractions/go v0.0.0-20211129093841-858bd540489b h1:K6kuW1GATRCuYinEFsAdjnTvpRM2FTyefRjkCj/sqIc= +github.com/microsoft/kiota/abstractions/go v0.0.0-20211129093841-858bd540489b/go.mod h1:pIT6aBVb0aHisY52bSbYvb0nyxe+TqdZ8lkpKB7pLIo= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220124135025-cd414c7de3e7 h1:jKAoSM2Hh64v9Gl1houx5RGtWOkZZwDlOUXqDk1iajI= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220124135025-cd414c7de3e7/go.mod h1:kY3tlHIUgSjgTfTIb1D1pfV5ytuRWWeBohRou/JNCSw= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220202150655-0505f19ca2d1 h1:X/Gl5CyFGfxeWluUsKDhw958XKOsx7wfPG82FVH9tH4= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220202150655-0505f19ca2d1/go.mod h1:BUv5PFNuBLFXQlDtarjdgqdqDjee3FgfjrNkfZlA5us= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220302123028-4b9132ed532c h1:4Xj+OwZy68ZGBpANUBaRTyoVk+SthajQa/opc94HZ+M= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220302123028-4b9132ed532c/go.mod h1:BUv5PFNuBLFXQlDtarjdgqdqDjee3FgfjrNkfZlA5us= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220304133504-2b8143da4e8a h1:nw6PxZdeuBjc2q97YJaX36tjpSJyPYpC/Ks/pn0dgFg= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220304133504-2b8143da4e8a/go.mod h1:BUv5PFNuBLFXQlDtarjdgqdqDjee3FgfjrNkfZlA5us= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220304190506-1b038c9cc6cb h1:qrWgSHID0v8AX2UnPATKdQwbG8p6r41I0g2+CVZaJ+E= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220304190506-1b038c9cc6cb/go.mod h1:BUv5PFNuBLFXQlDtarjdgqdqDjee3FgfjrNkfZlA5us= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220314101957-d2823fe62079 h1:M2GmF6QszBwSbAF5xvps1BJCLwRZOYKbPMeOMcFsqJM= +github.com/microsoft/kiota/abstractions/go v0.0.0-20220314101957-d2823fe62079/go.mod h1:BUv5PFNuBLFXQlDtarjdgqdqDjee3FgfjrNkfZlA5us= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yosida95/uritemplate/v3 v3.0.1 h1:+Fs//CsT+x231WmUQhMHWMxZizMvpnkOVWop02mVCfs= +github.com/yosida95/uritemplate/v3 v3.0.1/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/json_parse_node.go b/json_parse_node.go new file mode 100644 index 0000000..4655c15 --- /dev/null +++ b/json_parse_node.go @@ -0,0 +1,474 @@ +// Package jsonserialization is the default Kiota serialization implementation for JSON. +// It relies on the standard Go JSON library. +package jsonserialization + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "io" + "time" + + "github.com/google/uuid" + absser "github.com/microsoft/kiota-abstractions-go/serialization" +) + +// JsonParseNode is a ParseNode implementation for JSON. +type JsonParseNode struct { + value interface{} +} + +// NewJsonParseNode creates a new JsonParseNode. +func NewJsonParseNode(content []byte) (*JsonParseNode, error) { + if len(content) == 0 { + return nil, errors.New("content is empty") + } + decoder := json.NewDecoder(bytes.NewReader(content)) + value, err := loadJsonTree(decoder) + return value, err +} +func loadJsonTree(decoder *json.Decoder) (*JsonParseNode, error) { + for { + token, err := decoder.Token() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + switch token.(type) { + case json.Delim: + switch token.(json.Delim) { + case '{': + v := make(map[string]*JsonParseNode) + for decoder.More() { + key, err := decoder.Token() + if err != nil { + return nil, err + } + keyStr, ok := key.(string) + if !ok { + return nil, errors.New("key is not a string") + } + childNode, err := loadJsonTree(decoder) + if err != nil { + return nil, err + } + v[keyStr] = childNode + } + decoder.Token() // skip the closing curly + result := &JsonParseNode{value: v} + return result, nil + case '[': + v := make([]*JsonParseNode, 0) + for decoder.More() { + childNode, err := loadJsonTree(decoder) + if err != nil { + return nil, err + } + v = append(v, childNode) + } + decoder.Token() // skip the closing bracket + result := &JsonParseNode{value: v} + return result, nil + case ']': + case '}': + } + case json.Number: + number := token.(json.Number) + i, err := number.Int64() + c := &JsonParseNode{} + if err == nil { + c.SetValue(&i) + } else { + f, err := number.Float64() + if err == nil { + c.SetValue(&f) + } else { + return nil, err + } + } + return c, nil + case string: + v := token.(string) + c := &JsonParseNode{} + c.SetValue(&v) + return c, nil + case bool: + c := &JsonParseNode{} + v := token.(bool) + c.SetValue(&v) + return c, nil + case int8: + c := &JsonParseNode{} + v := token.(int8) + c.SetValue(&v) + return c, nil + case byte: + c := &JsonParseNode{} + v := token.(byte) + c.SetValue(&v) + return c, nil + case float64: + c := &JsonParseNode{} + v := token.(float64) + c.SetValue(&v) + return c, nil + case float32: + c := &JsonParseNode{} + v := token.(float32) + c.SetValue(&v) + return c, nil + case int32: + c := &JsonParseNode{} + v := token.(int32) + c.SetValue(&v) + return c, nil + case int64: + c := &JsonParseNode{} + v := token.(int64) + c.SetValue(&v) + return c, nil + case nil: + return nil, nil + default: + } + } + return nil, nil +} + +// SetValue sets the value represented by the node +func (n *JsonParseNode) SetValue(value interface{}) { + n.value = value +} + +// GetChildNode returns a new parse node for the given identifier. +func (n *JsonParseNode) GetChildNode(index string) (absser.ParseNode, error) { + if index == "" { + return nil, errors.New("index is empty") + } + childNodes, ok := n.value.(map[string]*JsonParseNode) + if !ok || len(childNodes) == 0 { + return nil, errors.New("no child node available") + } + return childNodes[index], nil +} + +// GetObjectValue returns the Parsable value from the node. +func (n *JsonParseNode) GetObjectValue(ctor absser.ParsableFactory) (absser.Parsable, error) { + if ctor == nil { + return nil, errors.New("constructor is nil") + } + if n == nil || n.value == nil { + return nil, nil + } + result, err := ctor(n) + if err != nil { + return nil, err + } + //TODO on before when implementing backing store + properties, ok := n.value.(map[string]*JsonParseNode) + if !ok { + return nil, errors.New("value is not an object") + } + fields := result.GetFieldDeserializers() + if len(properties) != 0 { + itemAsHolder, isHolder := result.(absser.AdditionalDataHolder) + var itemAdditionalData map[string]interface{} + if isHolder { + itemAdditionalData = itemAsHolder.GetAdditionalData() + if itemAdditionalData == nil { + itemAdditionalData = make(map[string]interface{}) + itemAsHolder.SetAdditionalData(itemAdditionalData) + } + } + + for key, value := range properties { + field := fields[key] + if field == nil { + if value != nil && isHolder { + itemAdditionalData[key] = value.value + } + } else { + err := field(result, value) + if err != nil { + return nil, err + } + } + } + } + //TODO on after when implementing backing store + return result, nil +} + +// GetCollectionOfObjectValues returns the collection of Parsable values from the node. +func (n *JsonParseNode) GetCollectionOfObjectValues(ctor absser.ParsableFactory) ([]absser.Parsable, error) { + if n == nil || n.value == nil { + return nil, nil + } + if ctor == nil { + return nil, errors.New("ctor is nil") + } + nodes, ok := n.value.([]*JsonParseNode) + if !ok { + return nil, errors.New("value is not a collection") + } + result := make([]absser.Parsable, len(nodes)) + for i, v := range nodes { + val, err := (*v).GetObjectValue(ctor) + if err != nil { + return nil, err + } + result[i] = val + } + return result, nil +} + +// GetCollectionOfPrimitiveValues returns the collection of primitive values from the node. +func (n *JsonParseNode) GetCollectionOfPrimitiveValues(targetType string) ([]interface{}, error) { + if n == nil || n.value == nil { + return nil, nil + } + if targetType == "" { + return nil, errors.New("targetType is empty") + } + nodes, ok := n.value.([]*JsonParseNode) + if !ok { + return nil, errors.New("value is not a collection") + } + result := make([]interface{}, len(nodes)) + for i, v := range nodes { + val, err := v.getPrimitiveValue(targetType) + if err != nil { + return nil, err + } + result[i] = val + } + return result, nil +} +func (n *JsonParseNode) getPrimitiveValue(targetType string) (interface{}, error) { + switch targetType { + case "string": + return n.GetStringValue() + case "bool": + return n.GetBoolValue() + case "uint8": + return n.GetInt8Value() + case "byte": + return n.GetByteValue() + case "float32": + return n.GetFloat32Value() + case "float64": + return n.GetFloat64Value() + case "int32": + return n.GetInt32Value() + case "int64": + return n.GetInt64Value() + case "time": + return n.GetTimeValue() + case "timeonly": + return n.GetTimeOnlyValue() + case "dateonly": + return n.GetDateOnlyValue() + case "isoduration": + return n.GetISODurationValue() + case "uuid": + return n.GetUUIDValue() + case "base64": + return n.GetByteArrayValue() + default: + return nil, errors.New("targetType is not supported") + } +} + +// GetCollectionOfEnumValues returns the collection of Enum values from the node. +func (n *JsonParseNode) GetCollectionOfEnumValues(parser func(string) (interface{}, error)) ([]interface{}, error) { + if n == nil || n.value == nil { + return nil, nil + } + if parser == nil { + return nil, errors.New("parser is nil") + } + nodes, ok := n.value.([]*JsonParseNode) + if !ok { + return nil, errors.New("value is not a collection") + } + result := make([]interface{}, len(nodes)) + for i, v := range nodes { + val, err := v.GetEnumValue(parser) + if err != nil { + return nil, err + } + result[i] = val + } + return result, nil +} + +// GetStringValue returns a String value from the nodes. +func (n *JsonParseNode) GetStringValue() (*string, error) { + if n == nil || n.value == nil { + return nil, nil + } + return n.value.(*string), nil +} + +// GetBoolValue returns a Bool value from the nodes. +func (n *JsonParseNode) GetBoolValue() (*bool, error) { + if n == nil || n.value == nil { + return nil, nil + } + return n.value.(*bool), nil +} + +// GetInt8Value returns a int8 value from the nodes. +func (n *JsonParseNode) GetInt8Value() (*int8, error) { + if n == nil || n.value == nil { + return nil, nil + } + return n.value.(*int8), nil +} + +// GetBoolValue returns a Bool value from the nodes. +func (n *JsonParseNode) GetByteValue() (*byte, error) { + if n == nil || n.value == nil { + return nil, nil + } + return n.value.(*byte), nil +} + +// GetFloat32Value returns a Float32 value from the nodes. +func (n *JsonParseNode) GetFloat32Value() (*float32, error) { + v, err := n.GetFloat64Value() + if err != nil { + return nil, err + } + if v == nil { + return nil, nil + } + cast := float32(*v) + return &cast, nil +} + +// GetFloat64Value returns a Float64 value from the nodes. +func (n *JsonParseNode) GetFloat64Value() (*float64, error) { + if n == nil || n.value == nil { + return nil, nil + } + return n.value.(*float64), nil +} + +// GetInt32Value returns a Int32 value from the nodes. +func (n *JsonParseNode) GetInt32Value() (*int32, error) { + v, err := n.GetFloat64Value() + if err != nil { + return nil, err + } + if v == nil { + return nil, nil + } + cast := int32(*v) + return &cast, nil +} + +// GetInt64Value returns a Int64 value from the nodes. +func (n *JsonParseNode) GetInt64Value() (*int64, error) { + v, err := n.GetFloat64Value() + if err != nil { + return nil, err + } + if v == nil { + return nil, nil + } + cast := int64(*v) + return &cast, nil +} + +// GetTimeValue returns a Time value from the nodes. +func (n *JsonParseNode) GetTimeValue() (*time.Time, error) { + v, err := n.GetStringValue() + if err != nil { + return nil, err + } + if v == nil { + return nil, nil + } + parsed, err := time.Parse(time.RFC3339, *v) + return &parsed, err +} + +// GetISODurationValue returns a ISODuration value from the nodes. +func (n *JsonParseNode) GetISODurationValue() (*absser.ISODuration, error) { + v, err := n.GetStringValue() + if err != nil { + return nil, err + } + if v == nil { + return nil, nil + } + return absser.ParseISODuration(*v) +} + +// GetTimeOnlyValue returns a TimeOnly value from the nodes. +func (n *JsonParseNode) GetTimeOnlyValue() (*absser.TimeOnly, error) { + v, err := n.GetStringValue() + if err != nil { + return nil, err + } + if v == nil { + return nil, nil + } + return absser.ParseTimeOnly(*v) +} + +// GetDateOnlyValue returns a DateOnly value from the nodes. +func (n *JsonParseNode) GetDateOnlyValue() (*absser.DateOnly, error) { + v, err := n.GetStringValue() + if err != nil { + return nil, err + } + if v == nil { + return nil, nil + } + return absser.ParseDateOnly(*v) +} + +// GetUUIDValue returns a UUID value from the nodes. +func (n *JsonParseNode) GetUUIDValue() (*uuid.UUID, error) { + v, err := n.GetStringValue() + if err != nil { + return nil, err + } + if v == nil { + return nil, nil + } + parsed, err := uuid.Parse(*v) + return &parsed, err +} + +// GetEnumValue returns a Enum value from the nodes. +func (n *JsonParseNode) GetEnumValue(parser func(string) (interface{}, error)) (interface{}, error) { + if parser == nil { + return nil, errors.New("parser is nil") + } + s, err := n.GetStringValue() + if err != nil { + return nil, err + } + if s == nil { + return nil, nil + } + return parser(*s) +} + +// GetByteArrayValue returns a ByteArray value from the nodes. +func (n *JsonParseNode) GetByteArrayValue() ([]byte, error) { + s, err := n.GetStringValue() + if err != nil { + return nil, err + } + if s == nil { + return nil, nil + } + return base64.StdEncoding.DecodeString(*s) +} diff --git a/json_parse_node_factory.go b/json_parse_node_factory.go new file mode 100644 index 0000000..ef55ce9 --- /dev/null +++ b/json_parse_node_factory.go @@ -0,0 +1,35 @@ +package jsonserialization + +import ( + "errors" + + absser "github.com/microsoft/kiota-abstractions-go/serialization" +) + +// JsonParseNodeFactory is a ParseNodeFactory implementation for JSON +type JsonParseNodeFactory struct { +} + +// Creates a new JsonParseNodeFactory +func NewJsonParseNodeFactory() *JsonParseNodeFactory { + return &JsonParseNodeFactory{} +} + +// GetValidContentType returns the content type this factory's parse nodes can deserialize. +func (f *JsonParseNodeFactory) GetValidContentType() (string, error) { + return "application/json", nil +} + +// GetRootParseNode return a new ParseNode instance that is the root of the content +func (f *JsonParseNodeFactory) GetRootParseNode(contentType string, content []byte) (absser.ParseNode, error) { + validType, err := f.GetValidContentType() + if err != nil { + return nil, err + } else if contentType == "" { + return nil, errors.New("contentType is empty") + } else if contentType != validType { + return nil, errors.New("contentType is not valid") + } else { + return NewJsonParseNode(content) + } +} diff --git a/json_parse_node_test.go b/json_parse_node_test.go new file mode 100644 index 0000000..6e77731 --- /dev/null +++ b/json_parse_node_test.go @@ -0,0 +1,126 @@ +package jsonserialization + +import ( + testing "testing" +) + +func TestTree(t *testing.T) { + source := "{\"someProp\": \"stringValue\",\"otherProp\": [1,2,3],\"objectProp\": {\"boolProp\": true}}" + sourceArray := []byte(source) + parseNode, err := NewJsonParseNode(sourceArray) + if err != nil { + t.Errorf("Error creating parse node: %s", err.Error()) + } + someProp, err := parseNode.GetChildNode("someProp") + if err != nil { + t.Errorf("Error getting child node: %s", err.Error()) + } + stringValue, err := someProp.GetStringValue() + if err != nil { + t.Errorf("Error getting string value: %s", err.Error()) + } + if *stringValue != "stringValue" { + t.Errorf("Expected value to be 'stringValue', got '%s'", *stringValue) + } + otherProp, err := parseNode.GetChildNode("otherProp") + if err != nil { + t.Errorf("Error getting child node: %s", err.Error()) + } + arrayValue, err := otherProp.GetCollectionOfPrimitiveValues("int32") + if err != nil { + t.Errorf("Error getting array value: %s", err.Error()) + } + if len(arrayValue) != 3 { + t.Errorf("Expected array to have 3 elements, got %d", len(arrayValue)) + } + if *(arrayValue[0].(*int32)) != 1 { + t.Errorf("Expected array element 0 to be 1, got %d", arrayValue[0]) + } + objectProp, err := parseNode.GetChildNode("objectProp") + if err != nil { + t.Errorf("Error getting child node: %s", err.Error()) + } + boolProp, err := objectProp.GetChildNode("boolProp") + if err != nil { + t.Errorf("Error getting child node: %s", err.Error()) + } + boolValue, err := boolProp.GetBoolValue() + if err != nil { + t.Errorf("Error getting boolean value: %s", err.Error()) + } + if !*boolValue { + t.Errorf("Expected value to be true, got false") + } +} + +func TestFunctional(t *testing.T) { + sourceArray := []byte(FunctionalTestSource) + parseNode, err := NewJsonParseNode(sourceArray) + if err != nil { + t.Errorf("Error creating parse node: %s", err.Error()) + } + if parseNode == nil { + t.Errorf("Expected parse node to be non-nil") + } +} + +const FunctionalTestSource = "{" + + "\"@odata.context\": \"https://graph.microsoft.com/v1.0/$metadata#users('vincent%40biret365.onmicrosoft.com')/messages\"," + + "\"@odata.nextLink\": \"https://graph.microsoft.com/v1.0/users/vincent@biret365.onmicrosoft.com/messages?$skip=10\"," + + "\"value\": [" + + "{" + + "\"@odata.etag\": \"W/\\\"CQAAABYAAAAs+XSiyjZdS4Rhtwk0v1pGAAA4Xv0v\\\"\"," + + "\"id\": \"AAMkAGNmMGZiNjM5LTZmMDgtNGU2OS1iYmUwLWYwZDc4M2ZkOGY1ZQBGAAAAAAAK20ulGawAT7z-yx90ohp-BwAs_XSiyjZdS4Rhtwk0v1pGAAAAAAEMAAAs_XSiyjZdS4Rhtwk0v1pGAAA4dw6TAAA=\"," + + "\"createdDateTime\": \"2021-10-14T09:19:01Z\"," + + "\"lastModifiedDateTime\": \"2021-10-14T09:19:03Z\"," + + "\"changeKey\": \"CQAAABYAAAAs+XSiyjZdS4Rhtwk0v1pGAAA4Xv0v\"," + + "\"categories\": []," + + "\"receivedDateTime\": \"2021-10-14T09:19:02Z\"," + + "\"sentDateTime\": \"2021-10-14T09:18:59Z\"," + + "\"hasAttachments\": false," + + "\"internetMessageId\": \"<608fed24166f421aa1e27a6c822074ba-JFBVALKQOJXWILKNK4YVA7CPGM3DKTLFONZWCZ3FINSW45DFOJ6E2ZLTONQWOZKDMVXHIZLSL5GUGMRZGEYDQOD4KNWXI4A=@microsoft.com>\"," + + "\"subject\": \"Major update from Message center\"," + + "\"bodyPreview\": \"(Updated) Microsoft 365 Compliance Center Core eDiscovery - Search by ID list retirementMC291088 · BIRET365Updated October 13, 2021: We have updated this message with additional details for clarity.We will be retiring the option to Search by ID,\"," + + "\"importance\": \"normal\"," + + "\"parentFolderId\": \"AQMkAGNmMGZiNjM5LTZmMDgtNGU2OS1iYgBlMC1mMGQ3ODNmZDhmNWUALgAAAwrbS6UZrABPvP-LH3SiGn8BACz5dKLKNl1LhGG3CTS-WkYAAAIBDAAAAA==\"," + + "\"conversationId\": \"AAQkAGNmMGZiNjM5LTZmMDgtNGU2OS1iYmUwLWYwZDc4M2ZkOGY1ZQAQANari86tqeZDsqpmA19AXLQ=\"," + + "\"conversationIndex\": \"AQHXwNyG1quLzq2p5kOyqmYDX0BctA==\"," + + "\"isDeliveryReceiptRequested\": null," + + "\"isReadReceiptRequested\": false," + + "\"isRead\": false," + + "\"isDraft\": false," + + "\"webLink\": \"https://outlook.office365.com/owa/?ItemID=AAMkAGNmMGZiNjM5LTZmMDgtNGU2OS1iYmUwLWYwZDc4M2ZkOGY1ZQBGAAAAAAAK20ulGawAT7z%2Fyx90ohp%2FBwAs%2BXSiyjZdS4Rhtwk0v1pGAAAAAAEMAAAs%2BXSiyjZdS4Rhtwk0v1pGAAA4dw6TAAA%3D&exvsurl=1&viewmodel=ReadMessageItem\"," + + "\"inferenceClassification\": \"other\"," + + "\"body\": {" + + "\"contentType\": \"html\"," + + "\"content\": \"
\\\"Microsoft\\\"
(Updated) Microsoft 365 Compliance Center Core eDiscovery - Search by ID list retirement
MC291088 · BIRET365

Updated October 13, 2021: We have updated this message with additional details for clarity.

We will be retiring the option to Search by ID list, as it is not functioning to an adequate level and creates significant challenges for organizations who depend on consistent and repeatable results for eDiscovery workflows.

When will this happen:

We will begin making this change in mid-November and expect to complete by the end of November.

How this will affect your organization:

You are receiving this message because our reporting indicates your organization may be using Search by ID list.

Once this change is made, the option to Search by ID list will be removed. We suggest focusing on search by query, condition and/or locations rather that ID.

What you need to do to prepare:

To fix this problem you need to review your eDiscovery search process, and update the workflow to focus on search by Subjects and dates rather than Search by ID list. Upon export from Core eDiscovery you can explore options to refine to only the messages of interest.

Click Additional Information to find out more.

Additional Information
You're subscribed to this email using vincent@biret365.onmicrosoft.com. If you're an IT admin, you're subscribed by default, but you can unsubscribe at any time. If you're not an IT admin, ask your admin to remove your email address from Microsoft 365 message center preferences.

How to view translated messages
This is a mandatory service communication. To set your contact preferences or to unsubcribe from other communications, visit the Promotional Communications Manager. Privacy statement.

Il s’agit de communications obligatoires. Pour configurer vos préférences de contact pour d’autres communications, accédez au gestionnaire de communications promotionnelles. Déclaration de confidentialité.
Microsoft Corporation, One Microsoft Way, Redmond WA 98052 USA
\\\"Microsoft\\\"
\\\"\\\" \"" + + "}," + + "\"sender\": {" + + "\"emailAddress\": {" + + "\"name\": \"Microsoft 365 Message center\"," + + "\"address\": \"o365mc@microsoft.com\"" + + "}" + + "}," + + "\"from\": {" + + "\"emailAddress\": {" + + "\"name\": \"Microsoft 365 Message center\"," + + "\"address\": \"o365mc@microsoft.com\"" + + "}" + + "}," + + "\"toRecipients\": [" + + "{" + + "\"emailAddress\": {" + + "\"name\": \"Vincent BIRET\"," + + "\"address\": \"vincent@biret365.onmicrosoft.com\"" + + "}" + + "}" + + "]," + + "\"ccRecipients\": []," + + "\"bccRecipients\": []," + + "\"replyTo\": []," + + "\"flag\": {" + + "\"flagStatus\": \"notFlagged\"" + + "}" + + "}" + + "]" + + "}" diff --git a/json_serialization_writer.go b/json_serialization_writer.go new file mode 100644 index 0000000..214c544 --- /dev/null +++ b/json_serialization_writer.go @@ -0,0 +1,840 @@ +package jsonserialization + +import ( + "encoding/base64" + "strconv" + "strings" + "time" + + "github.com/google/uuid" + + absser "github.com/microsoft/kiota-abstractions-go/serialization" +) + +// JsonSerializationWriter implements SerializationWriter for JSON. +type JsonSerializationWriter struct { + writer []string +} + +// NewJsonSerializationWriter creates a new instance of the JsonSerializationWriter. +func NewJsonSerializationWriter() *JsonSerializationWriter { + return &JsonSerializationWriter{ + writer: make([]string, 0), + } +} +func (w *JsonSerializationWriter) writeRawValue(value string) { + w.writer = append(w.writer, value) +} +func (w *JsonSerializationWriter) writeStringValue(value string) { + w.writeRawValue("\"" + value + "\"") +} +func (w *JsonSerializationWriter) writePropertyName(key string) { + w.writeRawValue("\"" + key + "\":") +} +func (w *JsonSerializationWriter) writePropertySeparator() { + w.writeRawValue(",") +} +func (w *JsonSerializationWriter) trimLastPropertySeparator() { + writerLen := len(w.writer) + if writerLen > 0 && w.writer[writerLen-1] == "," { + w.writer = w.writer[:writerLen-1] + } +} +func (w *JsonSerializationWriter) writeArrayStart() { + w.writeRawValue("[") +} +func (w *JsonSerializationWriter) writeArrayEnd() { + w.writeRawValue("]") +} +func (w *JsonSerializationWriter) writeObjectStart() { + w.writeRawValue("{") +} +func (w *JsonSerializationWriter) writeObjectEnd() { + w.writeRawValue("}") +} + +// WriteStringValue writes a String value to underlying the byte array. +func (w *JsonSerializationWriter) WriteStringValue(key string, value *string) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + w.writeStringValue(*value) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteBoolValue writes a Bool value to underlying the byte array. +func (w *JsonSerializationWriter) WriteBoolValue(key string, value *bool) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + w.writeRawValue(strconv.FormatBool(*value)) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteByteValue writes a Byte value to underlying the byte array. +func (w *JsonSerializationWriter) WriteByteValue(key string, value *byte) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + cast := int64(*value) + return w.WriteInt64Value(key, &cast) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteInt8Value writes a int8 value to underlying the byte array. +func (w *JsonSerializationWriter) WriteInt8Value(key string, value *int8) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + cast := int64(*value) + return w.WriteInt64Value(key, &cast) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteInt32Value writes a Int32 value to underlying the byte array. +func (w *JsonSerializationWriter) WriteInt32Value(key string, value *int32) error { + if value != nil { + cast := int64(*value) + return w.WriteInt64Value(key, &cast) + } + return nil +} + +// WriteInt64Value writes a Int64 value to underlying the byte array. +func (w *JsonSerializationWriter) WriteInt64Value(key string, value *int64) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + w.writeRawValue(strconv.FormatInt(*value, 10)) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteFloat32Value writes a Float32 value to underlying the byte array. +func (w *JsonSerializationWriter) WriteFloat32Value(key string, value *float32) error { + if value != nil { + cast := float64(*value) + return w.WriteFloat64Value(key, &cast) + } + return nil +} + +// WriteFloat64Value writes a Float64 value to underlying the byte array. +func (w *JsonSerializationWriter) WriteFloat64Value(key string, value *float64) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + w.writeRawValue(strconv.FormatFloat(*value, 'f', -1, 64)) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteTimeValue writes a Time value to underlying the byte array. +func (w *JsonSerializationWriter) WriteTimeValue(key string, value *time.Time) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + w.writeRawValue((*value).String()) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteISODurationValue writes a ISODuration value to underlying the byte array. +func (w *JsonSerializationWriter) WriteISODurationValue(key string, value *absser.ISODuration) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + w.writeRawValue((*value).String()) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteTimeOnlyValue writes a TimeOnly value to underlying the byte array. +func (w *JsonSerializationWriter) WriteTimeOnlyValue(key string, value *absser.TimeOnly) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + w.writeRawValue((*value).String()) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteDateOnlyValue writes a DateOnly value to underlying the byte array. +func (w *JsonSerializationWriter) WriteDateOnlyValue(key string, value *absser.DateOnly) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + w.writeRawValue((*value).String()) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteUUIDValue writes a UUID value to underlying the byte array. +func (w *JsonSerializationWriter) WriteUUIDValue(key string, value *uuid.UUID) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + w.writeStringValue((*value).String()) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteByteArrayValue writes a ByteArray value to underlying the byte array. +func (w *JsonSerializationWriter) WriteByteArrayValue(key string, value []byte) error { + if key != "" && value != nil { + w.writePropertyName(key) + } + if value != nil { + w.writeStringValue(base64.StdEncoding.EncodeToString(value)) + } + if key != "" && value != nil { + w.writePropertySeparator() + } + return nil +} + +// WriteObjectValue writes a Parsable value to underlying the byte array. +func (w *JsonSerializationWriter) WriteObjectValue(key string, item absser.Parsable) error { + if item != nil { + if key != "" { + w.writePropertyName(key) + } + //TODO onBefore for backing store + w.writeObjectStart() + //TODO onStart for backing store + err := item.Serialize(w) + //TODO onAfter for backing store + if err != nil { + return err + } + w.trimLastPropertySeparator() + w.writeObjectEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfObjectValues writes a collection of Parsable values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfObjectValues(key string, collection []absser.Parsable) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteObjectValue("", item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfStringValues writes a collection of String values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfStringValues(key string, collection []string) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteStringValue("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfInt32Values writes a collection of Int32 values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfInt32Values(key string, collection []int32) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteInt32Value("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfInt64Values writes a collection of Int64 values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfInt64Values(key string, collection []int64) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteInt64Value("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfFloat32Values writes a collection of Float32 values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfFloat32Values(key string, collection []float32) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteFloat32Value("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfFloat64Values writes a collection of Float64 values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfFloat64Values(key string, collection []float64) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteFloat64Value("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfTimeValues writes a collection of Time values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfTimeValues(key string, collection []time.Time) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteTimeValue("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfISODurationValues writes a collection of ISODuration values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfISODurationValues(key string, collection []absser.ISODuration) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteISODurationValue("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfTimeOnlyValues writes a collection of TimeOnly values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfTimeOnlyValues(key string, collection []absser.TimeOnly) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteTimeOnlyValue("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfDateOnlyValues writes a collection of DateOnly values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfDateOnlyValues(key string, collection []absser.DateOnly) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteDateOnlyValue("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfUUIDValues writes a collection of UUID values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfUUIDValues(key string, collection []uuid.UUID) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteUUIDValue("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfBoolValues writes a collection of Bool values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfBoolValues(key string, collection []bool) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteBoolValue("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfByteValues writes a collection of Byte values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfByteValues(key string, collection []byte) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteByteValue("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// WriteCollectionOfInt8Values writes a collection of int8 values to underlying the byte array. +func (w *JsonSerializationWriter) WriteCollectionOfInt8Values(key string, collection []int8) error { + if collection != nil { // empty collections are meaningful + if key != "" { + w.writePropertyName(key) + } + w.writeArrayStart() + for _, item := range collection { + err := w.WriteInt8Value("", &item) + if err != nil { + return err + } + w.writePropertySeparator() + } + w.trimLastPropertySeparator() + w.writeArrayEnd() + if key != "" { + w.writePropertySeparator() + } + } + return nil +} + +// GetSerializedContent returns the resulting byte array from the serialization writer. +func (w *JsonSerializationWriter) GetSerializedContent() ([]byte, error) { + resultStr := strings.Join(w.writer, "") + return []byte(resultStr), nil +} + +// WriteAdditionalData writes additional data to underlying the byte array. +func (w *JsonSerializationWriter) WriteAdditionalData(value map[string]interface{}) error { + if len(value) != 0 { + for key, value := range value { + p, ok := value.(absser.Parsable) + if ok { + err := w.WriteObjectValue(key, p) + if err != nil { + return err + } + continue + } + c, ok := value.([]absser.Parsable) + if ok { + err := w.WriteCollectionOfObjectValues(key, c) + if err != nil { + return err + } + continue + } + sc, ok := value.([]string) + if ok { + err := w.WriteCollectionOfStringValues(key, sc) + if err != nil { + return err + } + continue + } + bc, ok := value.([]bool) + if ok { + err := w.WriteCollectionOfBoolValues(key, bc) + if err != nil { + return err + } + continue + } + byc, ok := value.([]byte) + if ok { + err := w.WriteCollectionOfByteValues(key, byc) + if err != nil { + return err + } + continue + } + i8c, ok := value.([]int8) + if ok { + err := w.WriteCollectionOfInt8Values(key, i8c) + if err != nil { + return err + } + continue + } + i32c, ok := value.([]int32) + if ok { + err := w.WriteCollectionOfInt32Values(key, i32c) + if err != nil { + return err + } + continue + } + i64c, ok := value.([]int64) + if ok { + err := w.WriteCollectionOfInt64Values(key, i64c) + if err != nil { + return err + } + continue + } + f32c, ok := value.([]float32) + if ok { + err := w.WriteCollectionOfFloat32Values(key, f32c) + if err != nil { + return err + } + continue + } + f64c, ok := value.([]float64) + if ok { + err := w.WriteCollectionOfFloat64Values(key, f64c) + if err != nil { + return err + } + continue + } + uc, ok := value.([]uuid.UUID) + if ok { + err := w.WriteCollectionOfUUIDValues(key, uc) + if err != nil { + return err + } + continue + } + tc, ok := value.([]time.Time) + if ok { + err := w.WriteCollectionOfTimeValues(key, tc) + if err != nil { + return err + } + continue + } + dc, ok := value.([]absser.ISODuration) + if ok { + err := w.WriteCollectionOfISODurationValues(key, dc) + if err != nil { + return err + } + continue + } + toc, ok := value.([]absser.TimeOnly) + if ok { + err := w.WriteCollectionOfTimeOnlyValues(key, toc) + if err != nil { + return err + } + continue + } + doc, ok := value.([]absser.DateOnly) + if ok { + err := w.WriteCollectionOfDateOnlyValues(key, doc) + if err != nil { + return err + } + continue + } + sv, ok := value.(*string) + if ok { + err := w.WriteStringValue(key, sv) + if err != nil { + return err + } + continue + } + bv, ok := value.(*bool) + if ok { + err := w.WriteBoolValue(key, bv) + if err != nil { + return err + } + continue + } + byv, ok := value.(*byte) + if ok { + err := w.WriteByteValue(key, byv) + if err != nil { + return err + } + continue + } + i8v, ok := value.(*int8) + if ok { + err := w.WriteInt8Value(key, i8v) + if err != nil { + return err + } + continue + } + i32v, ok := value.(*int32) + if ok { + err := w.WriteInt32Value(key, i32v) + if err != nil { + return err + } + continue + } + i64v, ok := value.(*int64) + if ok { + err := w.WriteInt64Value(key, i64v) + if err != nil { + return err + } + continue + } + f32v, ok := value.(*float32) + if ok { + err := w.WriteFloat32Value(key, f32v) + if err != nil { + return err + } + continue + } + f64v, ok := value.(*float64) + if ok { + err := w.WriteFloat64Value(key, f64v) + if err != nil { + return err + } + continue + } + uv, ok := value.(*uuid.UUID) + if ok { + err := w.WriteUUIDValue(key, uv) + if err != nil { + return err + } + continue + } + tv, ok := value.(*time.Time) + if ok { + err := w.WriteTimeValue(key, tv) + if err != nil { + return err + } + continue + } + dv, ok := value.(*absser.ISODuration) + if ok { + err := w.WriteISODurationValue(key, dv) + if err != nil { + return err + } + continue + } + tov, ok := value.(*absser.TimeOnly) + if ok { + err := w.WriteTimeOnlyValue(key, tov) + if err != nil { + return err + } + continue + } + dov, ok := value.(*absser.DateOnly) + if ok { + err := w.WriteDateOnlyValue(key, dov) + if err != nil { + return err + } + continue + } + ba, ok := value.([]byte) + if ok { + err := w.WriteByteArrayValue(key, ba) + if err != nil { + return err + } + continue + } + } + w.trimLastPropertySeparator() + } + return nil +} + +// Close clears the internal buffer. +func (w *JsonSerializationWriter) Close() error { + return nil +} diff --git a/json_serialization_writer_factory.go b/json_serialization_writer_factory.go new file mode 100644 index 0000000..6a5b949 --- /dev/null +++ b/json_serialization_writer_factory.go @@ -0,0 +1,35 @@ +package jsonserialization + +import ( + "errors" + + absser "github.com/microsoft/kiota-abstractions-go/serialization" +) + +// JsonSerializationWriterFactory implements SerializationWriterFactory for JSON. +type JsonSerializationWriterFactory struct { +} + +// NewJsonSerializationWriterFactory creates a new instance of the JsonSerializationWriterFactory. +func NewJsonSerializationWriterFactory() *JsonSerializationWriterFactory { + return &JsonSerializationWriterFactory{} +} + +// GetValidContentType returns the valid content type for the SerializationWriterFactoryRegistry +func (f *JsonSerializationWriterFactory) GetValidContentType() (string, error) { + return "application/json", nil +} + +// GetSerializationWriter returns the relevant SerializationWriter instance for the given content type +func (f *JsonSerializationWriterFactory) GetSerializationWriter(contentType string) (absser.SerializationWriter, error) { + validType, err := f.GetValidContentType() + if err != nil { + return nil, err + } else if contentType == "" { + return nil, errors.New("contentType is empty") + } else if contentType != validType { + return nil, errors.New("contentType is not valid") + } else { + return NewJsonSerializationWriter(), nil + } +} diff --git a/json_serialization_writer_test.go b/json_serialization_writer_test.go new file mode 100644 index 0000000..fdcd9fa --- /dev/null +++ b/json_serialization_writer_test.go @@ -0,0 +1,34 @@ +package jsonserialization + +import ( + assert "github.com/stretchr/testify/assert" + "testing" +) + +func TestItDoesntWriteAnythingForNilAdditionalData(t *testing.T) { + serializer := NewJsonSerializationWriter() + serializer.WriteAdditionalData(nil) + result, err := serializer.GetSerializedContent() + assert.Nil(t, err) + assert.Equal(t, 0, len(result)) +} + +func TestItDoesntWriteAnythingForEmptyAdditionalData(t *testing.T) { + serializer := NewJsonSerializationWriter() + serializer.WriteAdditionalData(make(map[string]interface{})) + result, err := serializer.GetSerializedContent() + assert.Nil(t, err) + assert.Equal(t, 0, len(result)) +} + +func TestItDoesntTrimCommasOnEmptyAdditionalData(t *testing.T) { + serializer := NewJsonSerializationWriter() + value := "value" + serializer.WriteStringValue("key", &value) + serializer.WriteAdditionalData(make(map[string]interface{})) + value2 := "value2" + serializer.WriteStringValue("key2", &value2) + result, err := serializer.GetSerializedContent() + assert.Nil(t, err) + assert.Equal(t, "\"key\":\"value\",\"key2\":\"value2\",", string(result[:])) +}