├── .github ├── auto-approve.yml ├── auto-label.yaml ├── release-please.yml ├── release-trigger.yml ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── processs_request.md │ ├── questions.md │ ├── support_request.md │ ├── feature_request.yml │ ├── documentation_request.yml │ └── bug_report.yml ├── CODEOWNERS ├── scripts │ ├── package.json │ ├── remove-response-label.cjs │ ├── close-unresponsive.cjs │ ├── close-invalid-link.cjs │ ├── fixtures │ │ ├── invalidIssueBody.txt │ │ ├── validIssueBody.txt │ │ └── validIssueBodyDifferentLinkLocation.txt │ └── tests │ │ ├── close-invalid-link.test.cjs │ │ └── close-or-remove-response-label.test.cjs ├── sync-repo-settings.yaml ├── workflows │ ├── issues-no-repro.yaml │ ├── response.yaml │ └── ci.yaml ├── generated-files-bot.yml └── PULL_REQUEST_TEMPLATE.md ├── .eslintrc.json ├── .prettierignore ├── .eslintignore ├── .gitattributes ├── .gitignore ├── tsconfig.json ├── SECURITY.md ├── .nycrc ├── renovate.json ├── samples ├── package.json ├── system-test │ └── test.quickstart.js ├── README.md └── quickstart.js ├── .repo-metadata.json ├── .prettierrc.js ├── typescript ├── src │ ├── index.ts │ ├── bytes.ts │ ├── fieldmask.ts │ ├── types.ts │ ├── util.ts │ ├── enum.ts │ ├── duration.ts │ ├── wrappers.ts │ ├── timestamp.ts │ ├── any.ts │ ├── value.ts │ ├── toproto3json.ts │ └── fromproto3json.ts └── test │ ├── unit │ ├── empty.ts │ ├── fieldmask.ts │ ├── common.ts │ ├── bytes.ts │ ├── enum.ts │ ├── nested.ts │ ├── oneof.ts │ ├── duration.ts │ ├── any.ts │ ├── timestamp.ts │ ├── wrappers.ts │ ├── map.ts │ ├── primitive.ts │ ├── repeated.ts │ ├── error-coverage.ts │ └── value.ts │ └── system │ └── install.ts ├── .mocharc.js ├── .jsdoc.js ├── .trampolinerc ├── package.json ├── CONTRIBUTING.md ├── README.md ├── test-fixtures └── proto │ ├── test.proto │ └── test.json ├── CODE_OF_CONDUCT.md ├── CHANGELOG.md └── LICENSE /.github/auto-approve.yml: -------------------------------------------------------------------------------- 1 | processes: 2 | - "NodeDependency" -------------------------------------------------------------------------------- /.github/auto-label.yaml: -------------------------------------------------------------------------------- 1 | requestsize: 2 | enabled: true 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/gts" 3 | } 4 | -------------------------------------------------------------------------------- /.github/release-please.yml: -------------------------------------------------------------------------------- 1 | handleGHRelease: true 2 | releaseType: node 3 | -------------------------------------------------------------------------------- /.github/release-trigger.yml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | multiScmName: proto3-json-serializer-nodejs -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | test/fixtures 4 | build/ 5 | docs/ 6 | protos/ 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | test/fixtures 4 | build/ 5 | docs/ 6 | protos/ 7 | samples/generated/ 8 | system-test/**/fixtures 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ts text eol=lf 2 | *.js text eol=lf 3 | protos/* linguist-generated 4 | **/api-extractor.json linguist-language=JSON-with-Comments 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .vscode 4 | package-lock.json 5 | samples/node_modules 6 | samples/package-lock.json 7 | samples/coverage 8 | coverage 9 | .coverage 10 | .DS_Store 11 | __pycache__ 12 | .tgz 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Google Cloud Support 3 | url: https://cloud.google.com/support/ 4 | about: If you have a support contract with Google, please use the Google Cloud Support portal. 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/processs_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Process Request 3 | about: Submit a process request to the library. Process requests are any requests related to library infrastructure, for example CI/CD, publishing, releasing, broken links. 4 | --- 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/gts/tsconfig-google.json", 3 | "compilerOptions": { 4 | "rootDir": "typescript", 5 | "outDir": "build", 6 | "lib": ["dom"] 7 | }, 8 | "include": [ 9 | "typescript/**/*.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: If you have a question, please use Discussions 4 | 5 | --- 6 | 7 | If you have a general question that goes beyond the library itself, we encourage you to use [Discussions](https://github.com//discussions) 8 | to engage with fellow community members! 9 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). 4 | 5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz. 6 | 7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners file. 2 | # This file controls who is tagged for review for any given pull request. 3 | # 4 | # For syntax help see: 5 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax 6 | 7 | 8 | # Unless specified, the jsteam is the default owner for nodejs repositories. 9 | * @googleapis/jsteam -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support request 3 | about: If you have a support contract with Google, please create an issue in the Google Cloud Support console. 4 | 5 | --- 6 | 7 | **PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. 8 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "report-dir": "./.coverage", 3 | "reporter": ["text", "lcov"], 4 | "exclude": [ 5 | "**/*-test", 6 | "**/.coverage", 7 | "**/apis", 8 | "**/benchmark", 9 | "**/conformance", 10 | "**/docs", 11 | "**/samples", 12 | "**/scripts", 13 | "**/protos", 14 | "**/test", 15 | "**/*.d.ts", 16 | ".jsdoc.js", 17 | "**/.jsdoc.js", 18 | "karma.conf.js", 19 | "webpack-tests.config.js", 20 | "webpack.config.js" 21 | ], 22 | "exclude-after-remap": false, 23 | "all": true 24 | } 25 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "docker:disable", 5 | ":disableDependencyDashboard" 6 | ], 7 | "constraintsFiltering": "strict", 8 | "pinVersions": false, 9 | "rebaseStalePrs": true, 10 | "schedule": [ 11 | "after 9am and before 3pm" 12 | ], 13 | "gitAuthor": null, 14 | "packageRules": [ 15 | { 16 | "extends": "packages:linters", 17 | "groupName": "linters" 18 | }, 19 | { 20 | "matchManagers": ["github-actions"], 21 | "enabled": false 22 | } 23 | ], 24 | "ignoreDeps": ["typescript"] 25 | } 26 | -------------------------------------------------------------------------------- /.github/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tests", 3 | "private": true, 4 | "description": "tests for script", 5 | "scripts": { 6 | "test": "mocha tests/close-invalid-link.test.cjs && mocha tests/close-or-remove-response-label.test.cjs" 7 | }, 8 | "author": "Google Inc.", 9 | "license": "Apache-2.0", 10 | "engines": { 11 | "node": ">=18" 12 | }, 13 | "dependencies": { 14 | "js-yaml": "^4.1.0" 15 | }, 16 | "devDependencies": { 17 | "@octokit/rest": "^21.0.0", 18 | "mocha": "^10.0.0", 19 | "sinon": "^21.0.0" 20 | } 21 | } -------------------------------------------------------------------------------- /samples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Samples for proto3-json-serializer.", 3 | "license": "Apache-2.0", 4 | "author": "Google LLC", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "repository": "googleapis/proto3-json-serializer-nodejs", 9 | "private": true, 10 | "scripts": { 11 | "test": "c8 mocha system-test" 12 | }, 13 | "files": [ 14 | "*.js" 15 | ], 16 | "dependencies": { 17 | "google-proto-files": "^5.0.0", 18 | "proto3-json-serializer": "^3.0.2", 19 | "protobufjs": "^7.0.0" 20 | }, 21 | "devDependencies": { 22 | "c8": "^9.0.0", 23 | "mocha": "^9.0.0" 24 | } 25 | } -------------------------------------------------------------------------------- /.repo-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proto3-json-serializer", 3 | "name_pretty": "Proto3 JSON serializer", 4 | "product_documentation": "https://googleapis.github.io/proto3-json-serializer-nodejs/", 5 | "client_documentation": "https://cloud.google.com/nodejs/docs/reference/proto3-json-serializer/latest", 6 | "issue_tracker": "https://github.com/googleapis/proto3-json-serializer-nodejs/issues/", 7 | "release_level": "preview", 8 | "language": "nodejs", 9 | "repo": "googleapis/proto3-json-serializer-nodejs", 10 | "distribution_name": "proto3-json-serializer", 11 | "api_id": "", 12 | "requires_billing": false, 13 | "library_type": "OTHER" 14 | } 15 | -------------------------------------------------------------------------------- /.github/sync-repo-settings.yaml: -------------------------------------------------------------------------------- 1 | branchProtectionRules: 2 | - pattern: main 3 | isAdminEnforced: true 4 | requiredApprovingReviewCount: 1 5 | requiresCodeOwnerReviews: true 6 | requiresStrictStatusChecks: true 7 | requiredStatusCheckContexts: 8 | - "ci/kokoro: Samples test" 9 | - "ci/kokoro: System test" 10 | - lint 11 | - test (18) 12 | - test (20) 13 | - test (22) 14 | - cla/google 15 | - windows 16 | - OwlBot Post Processor 17 | permissionRules: 18 | - team: yoshi-admins 19 | permission: admin 20 | - team: jsteam-admins 21 | permission: admin 22 | - team: jsteam 23 | permission: push 24 | -------------------------------------------------------------------------------- /.github/workflows/issues-no-repro.yaml: -------------------------------------------------------------------------------- 1 | name: invalid_link 2 | on: 3 | issues: 4 | types: [opened, reopened] 5 | 6 | jobs: 7 | close: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/checkout@v5 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: 18 17 | - run: npm install 18 | working-directory: ./.github/scripts 19 | - uses: actions/github-script@v7 20 | with: 21 | script: | 22 | const script = require('./.github/scripts/close-invalid-link.cjs') 23 | await script({github, context}) 24 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = { 16 | ...require('gts/.prettierrc.json') 17 | } 18 | -------------------------------------------------------------------------------- /typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export {JSONObject, JSONValue} from './types'; 16 | export {toProto3JSON, ToProto3JSONOptions} from './toproto3json'; 17 | export {fromProto3JSON} from './fromproto3json'; 18 | -------------------------------------------------------------------------------- /typescript/src/bytes.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export function bytesToProto3JSON(obj: Buffer | Uint8Array) { 16 | if (Buffer.isBuffer(obj)) { 17 | return obj.toString('base64'); 18 | } else { 19 | return Buffer.from(obj.buffer, 0, obj.byteLength).toString('base64'); 20 | } 21 | } 22 | 23 | export function bytesFromProto3JSON(json: string) { 24 | return Buffer.from(json, 'base64'); 25 | } 26 | -------------------------------------------------------------------------------- /typescript/src/fieldmask.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | 17 | export interface FieldMask { 18 | paths: string[]; 19 | } 20 | 21 | export function googleProtobufFieldMaskToProto3JSON( 22 | obj: protobuf.Message & FieldMask, 23 | ) { 24 | return obj.paths.join(','); 25 | } 26 | 27 | export function googleProtobufFieldMaskFromProto3JSON(json: string) { 28 | return { 29 | paths: json.split(','), 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/response.yaml: -------------------------------------------------------------------------------- 1 | name: no_response 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' # Run every day at 01:30 5 | workflow_dispatch: 6 | issue_comment: 7 | 8 | jobs: 9 | close: 10 | if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' 11 | runs-on: ubuntu-latest 12 | permissions: 13 | issues: write 14 | pull-requests: write 15 | steps: 16 | - uses: actions/checkout@v5 17 | - uses: actions/github-script@v7 18 | with: 19 | script: | 20 | const script = require('./.github/scripts/close-unresponsive.cjs') 21 | await script({github, context}) 22 | 23 | remove_label: 24 | if: github.event_name == 'issue_comment' 25 | runs-on: ubuntu-latest 26 | permissions: 27 | issues: write 28 | pull-requests: write 29 | steps: 30 | - uses: actions/checkout@v5 31 | - uses: actions/github-script@v7 32 | with: 33 | script: | 34 | const script = require('./.github/scripts/remove-response-label.cjs') 35 | await script({github, context}) 36 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | const config = { 15 | "enable-source-maps": true, 16 | "throw-deprecation": true, 17 | "timeout": 10000, 18 | "recursive": true 19 | } 20 | if (process.env.MOCHA_THROW_DEPRECATION === 'false') { 21 | delete config['throw-deprecation']; 22 | } 23 | if (process.env.MOCHA_REPORTER) { 24 | config.reporter = process.env.MOCHA_REPORTER; 25 | } 26 | if (process.env.MOCHA_REPORTER_OUTPUT) { 27 | config['reporter-option'] = `output=${process.env.MOCHA_REPORTER_OUTPUT}`; 28 | } 29 | module.exports = config 30 | -------------------------------------------------------------------------------- /samples/system-test/test.quickstart.js: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const path = require('path'); 18 | const assert = require('assert'); 19 | const {describe, it} = require('mocha'); 20 | const cp = require('child_process'); 21 | 22 | const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); 23 | 24 | const cwd = path.join(__dirname, '..'); 25 | 26 | describe('Quickstart', () => { 27 | it('should run quickstart sample', async () => { 28 | const stdout = execSync('node quickstart.js', {cwd}); 29 | assert(/Quickstart completed/.test(stdout)); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /typescript/src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export type JSONValue = 16 | | string 17 | | number 18 | | boolean 19 | | null 20 | | JSONValue[] 21 | | {[key: string]: JSONValue}; 22 | 23 | export interface JSONObject { 24 | [key: string]: JSONValue; 25 | } 26 | 27 | export type FromObjectValue = 28 | | string 29 | | number 30 | | boolean 31 | | null 32 | | FromObjectValue[] 33 | | Buffer 34 | | Uint8Array 35 | | {[key: string]: FromObjectValue}; 36 | 37 | // We don't want to import long here, we only need .toString() from it 38 | export interface LongStub { 39 | toString: () => string; 40 | } 41 | -------------------------------------------------------------------------------- /.github/generated-files-bot.yml: -------------------------------------------------------------------------------- 1 | generatedFiles: 2 | - path: '.kokoro/**' 3 | message: '`.kokoro` files are templated and should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 4 | - path: '.github/CODEOWNERS' 5 | message: 'CODEOWNERS should instead be modified via the `codeowner_team` property in .repo-metadata.json' 6 | - path: '.github/workflows/ci.yaml' 7 | message: '`.github/workflows/ci.yaml` (GitHub Actions) should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 8 | - path: '.github/generated-files-bot.+(yml|yaml)' 9 | message: '`.github/generated-files-bot.(yml|yaml)` should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 10 | - path: 'README.md' 11 | message: '`README.md` is managed by [`synthtool`](https://github.com/googleapis/synthtool). However, a partials file can be used to update the README, e.g.: https://github.com/googleapis/nodejs-storage/blob/main/.readme-partials.yaml' 12 | - path: 'samples/README.md' 13 | message: '`samples/README.md` is managed by [`synthtool`](https://github.com/googleapis/synthtool). However, a partials file can be used to update the README, e.g.: https://github.com/googleapis/nodejs-storage/blob/main/.readme-partials.yaml' 14 | ignoreAuthors: 15 | - 'gcf-owl-bot[bot]' 16 | - 'yoshi-automation' 17 | -------------------------------------------------------------------------------- /.github/scripts/remove-response-label.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = async ({ github, context }) => { 16 | const commenter = context.actor; 17 | const issue = await github.rest.issues.get({ 18 | owner: context.repo.owner, 19 | repo: context.repo.repo, 20 | issue_number: context.issue.number, 21 | }); 22 | const author = issue.data.user.login; 23 | const labels = issue.data.labels.map((e) => e.name); 24 | 25 | if (author === commenter && labels.includes("needs more info")) { 26 | await github.rest.issues.removeLabel({ 27 | owner: context.repo.owner, 28 | repo: context.repo.repo, 29 | issue_number: context.issue.number, 30 | name: "needs more info", 31 | }); 32 | } 33 | }; -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | > Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: 2 | 3 | ## Description 4 | 5 | > Please provide a detailed description for the change. 6 | > As much as possible, please try to keep changes separate by purpose. For example, try not to make a one-line bug fix in a feature request, or add an irrelevant README change to a bug fix. 7 | 8 | ## Impact 9 | 10 | > What's the impact of this change? 11 | 12 | ## Testing 13 | 14 | > Have you added unit and integration tests if necessary? 15 | > Were any tests changed? Are any breaking changes necessary? 16 | 17 | ## Additional Information 18 | 19 | > Any additional details that we should be aware of? 20 | 21 | ## Checklist 22 | 23 | - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea 24 | - [ ] Ensure the tests and linter pass 25 | - [ ] Code coverage does not decrease 26 | - [ ] Appropriate docs were updated 27 | - [ ] Appropriate comments were added, particularly in complex areas or places that require background 28 | - [ ] No new warnings or issues will be generated from this change 29 | 30 | Fixes #issue_number_goes_here 🦕 31 | -------------------------------------------------------------------------------- /.jsdoc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | 'use strict'; 17 | 18 | module.exports = { 19 | opts: { 20 | readme: './README.md', 21 | package: './package.json', 22 | template: './node_modules/jsdoc-fresh', 23 | recurse: true, 24 | verbose: true, 25 | destination: './docs/' 26 | }, 27 | plugins: [ 28 | 'plugins/markdown', 29 | 'jsdoc-region-tag' 30 | ], 31 | source: { 32 | excludePattern: '(^|\\/|\\\\)[._]', 33 | include: [ 34 | 'build/src' 35 | ], 36 | includePattern: '\\.js$' 37 | }, 38 | templates: { 39 | copyright: 'Copyright 2021 Google, LLC.', 40 | includeDate: false, 41 | sourceFiles: false, 42 | systemName: 'proto3-json-serializer', 43 | theme: 'lumen' 44 | }, 45 | markdown: { 46 | idInHeadings: true 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /typescript/src/util.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export function getFullyQualifiedTypeName( 16 | type: protobuf.Type | protobuf.Namespace | protobuf.Enum, 17 | ) { 18 | // We assume that the protobuf package tree cannot have cycles. 19 | let fullyQualifiedTypeName = ''; 20 | while (type.parent) { 21 | fullyQualifiedTypeName = `.${type.name}${fullyQualifiedTypeName}`; 22 | type = type.parent; 23 | } 24 | return fullyQualifiedTypeName; 25 | } 26 | 27 | export const wrapperTypes = new Set([ 28 | '.google.protobuf.DoubleValue', 29 | '.google.protobuf.FloatValue', 30 | '.google.protobuf.Int64Value', 31 | '.google.protobuf.UInt64Value', 32 | '.google.protobuf.Int32Value', 33 | '.google.protobuf.UInt32Value', 34 | '.google.protobuf.BoolValue', 35 | '.google.protobuf.StringValue', 36 | '.google.protobuf.BytesValue', 37 | ]); 38 | 39 | export function assert(assertion: boolean, message: string) { 40 | if (!assertion) { 41 | throw new Error(message); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /typescript/test/unit/empty.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testEmpty(root: protobuf.Root) { 23 | const MessageWithEmpty = root.lookupType('test.MessageWithEmpty'); 24 | const message = MessageWithEmpty.fromObject({ 25 | emptyField: {}, 26 | }); 27 | const json = { 28 | emptyField: {}, 29 | }; 30 | 31 | it('serializes to proto3 JSON', () => { 32 | const serialized = toProto3JSON(message); 33 | assert.deepStrictEqual(serialized, json); 34 | }); 35 | 36 | it('deserializes from proto3 JSON', () => { 37 | const deserialized = fromProto3JSON(MessageWithEmpty, json); 38 | assert.deepStrictEqual(deserialized, message); 39 | }); 40 | } 41 | 42 | testTwoTypesOfLoad('google.protobuf.Empty', testEmpty); 43 | -------------------------------------------------------------------------------- /.trampolinerc: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Template for .trampolinerc 16 | 17 | # Add required env vars here. 18 | required_envvars+=( 19 | ) 20 | 21 | # Add env vars which are passed down into the container here. 22 | pass_down_envvars+=( 23 | "AUTORELEASE_PR" 24 | "VERSION" 25 | ) 26 | 27 | # Prevent unintentional override on the default image. 28 | if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \ 29 | [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then 30 | echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image." 31 | exit 1 32 | fi 33 | 34 | # Define the default value if it makes sense. 35 | if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then 36 | TRAMPOLINE_IMAGE_UPLOAD="" 37 | fi 38 | 39 | if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then 40 | TRAMPOLINE_IMAGE="" 41 | fi 42 | 43 | if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then 44 | TRAMPOLINE_DOCKERFILE="" 45 | fi 46 | 47 | if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then 48 | TRAMPOLINE_BUILD_FILE="" 49 | fi 50 | 51 | # Secret Manager secrets. 52 | source ${PROJECT_ROOT}/.kokoro/populate-secrets.sh 53 | -------------------------------------------------------------------------------- /typescript/test/unit/fieldmask.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testFieldMask(root: protobuf.Root) { 23 | const MessageWithFieldMask = root.lookupType('test.MessageWithFieldMask'); 24 | 25 | const message = MessageWithFieldMask.fromObject({ 26 | fieldMaskField: { 27 | paths: ['a.b', 'c.d.e'], 28 | }, 29 | }); 30 | const json = { 31 | fieldMaskField: 'a.b,c.d.e', 32 | }; 33 | 34 | it('serializes to proto3 JSON', () => { 35 | const serialized = toProto3JSON(message); 36 | assert.deepStrictEqual(serialized, json); 37 | }); 38 | 39 | it('deserializes from proto3 JSON', () => { 40 | const deserialized = fromProto3JSON(MessageWithFieldMask, json); 41 | assert.deepStrictEqual(deserialized, message); 42 | }); 43 | } 44 | 45 | testTwoTypesOfLoad('google.protobuf.Timestamp', testFieldMask); 46 | -------------------------------------------------------------------------------- /typescript/test/unit/common.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as path from 'path'; 16 | import * as protobuf from 'protobufjs'; 17 | import {describe} from 'mocha'; 18 | 19 | const fixtures = path.join(__dirname, '..', '..', '..', 'test-fixtures'); 20 | 21 | interface TestFunction { 22 | (root: protobuf.Root): void; 23 | } 24 | 25 | export function testTwoTypesOfLoad( 26 | name: string, 27 | testFunctions: TestFunction | Array, 28 | ) { 29 | const functions: Array = Array.isArray(testFunctions) 30 | ? testFunctions 31 | : [testFunctions]; 32 | describe(name, () => { 33 | describe('loadSync test', () => { 34 | const root = protobuf.loadSync( 35 | path.join(fixtures, 'proto', 'test.proto'), 36 | ); 37 | functions.map(func => { 38 | func(root); 39 | }); 40 | }); 41 | describe('fromJSON test', () => { 42 | const root = protobuf.Root.fromJSON( 43 | require(path.join(fixtures, 'proto', 'test.json')), 44 | ); 45 | functions.map(func => { 46 | func(root); 47 | }); 48 | }); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | [//]: # "This README.md file is auto-generated, all changes to this file will be lost." 2 | [//]: # "To regenerate it, use `python -m synthtool`." 3 | Google Cloud Platform logo 4 | 5 | # [Proto3 JSON serializer: Node.js Samples](https://github.com/googleapis/proto3-json-serializer-nodejs) 6 | 7 | [![Open in Cloud Shell][shell_img]][shell_link] 8 | 9 | 10 | 11 | ## Table of Contents 12 | 13 | * [Before you begin](#before-you-begin) 14 | * [Samples](#samples) 15 | * [Quickstart](#quickstart) 16 | 17 | ## Before you begin 18 | 19 | Before running the samples, make sure you've followed the steps outlined in 20 | [Using the client library](https://github.com/googleapis/proto3-json-serializer-nodejs#using-the-client-library). 21 | 22 | `cd samples` 23 | 24 | `npm install` 25 | 26 | `cd ..` 27 | 28 | ## Samples 29 | 30 | 31 | 32 | ### Quickstart 33 | 34 | View the [source code](https://github.com/googleapis/proto3-json-serializer-nodejs/blob/main/samples/quickstart.js). 35 | 36 | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/proto3-json-serializer-nodejs&page=editor&open_in_editor=samples/quickstart.js,samples/README.md) 37 | 38 | __Usage:__ 39 | 40 | 41 | `node samples/quickstart.js` 42 | 43 | 44 | 45 | 46 | 47 | 48 | [shell_img]: https://gstatic.com/cloudssh/images/open-btn.png 49 | [shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/proto3-json-serializer-nodejs&page=editor&open_in_editor=samples/README.md 50 | [product-docs]: https://googleapis.github.io/proto3-json-serializer-nodejs/ 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proto3-json-serializer", 3 | "version": "3.0.2", 4 | "repository": "googleapis/proto3-json-serializer-nodejs", 5 | "description": "Support for proto3 JSON serialiazation/deserialization for protobuf.js", 6 | "main": "build/src/index.js", 7 | "types": "build/src/index.d.ts", 8 | "files": [ 9 | "build/src" 10 | ], 11 | "license": "Apache-2.0", 12 | "keywords": [ 13 | "protobufjs", 14 | "protobuf.js", 15 | "protobuf", 16 | "proto3", 17 | "json", 18 | "serialization", 19 | "deserialization" 20 | ], 21 | "scripts": { 22 | "test": "c8 mocha build/test/unit", 23 | "system-test": "mocha build/test/system", 24 | "lint": "gts lint", 25 | "clean": "gts clean", 26 | "compile": "tsc", 27 | "fix": "gts fix", 28 | "prepare": "npm run compile", 29 | "pretest": "npm run compile", 30 | "posttest": "npm run lint", 31 | "compile-test-protos": "cd test-fixtures/proto && pbjs -t json test.proto > test.json", 32 | "docs": "jsdoc -c .jsdoc.js", 33 | "docs-test": "linkinator docs", 34 | "predocs-test": "npm run docs", 35 | "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", 36 | "prelint": "cd samples && npm link ../ && npm install" 37 | }, 38 | "dependencies": { 39 | "protobufjs": "^7.4.0" 40 | }, 41 | "devDependencies": { 42 | "@types/mocha": "^10.0.10", 43 | "@types/node": "^22.13.1", 44 | "c8": "^10.1.3", 45 | "google-proto-files": "^5.0.0", 46 | "gts": "^6.0.2", 47 | "jsdoc": "^4.0.4", 48 | "jsdoc-fresh": "^5.0.0", 49 | "jsdoc-region-tag": "^4.0.0", 50 | "linkinator": "^6.1.2", 51 | "mocha": "^11.1.0", 52 | "pack-n-play": "^4.0.0", 53 | "protobufjs-cli": "^1.1.3", 54 | "typescript": "~5.8.2" 55 | }, 56 | "engines": { 57 | "node": ">=18" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /typescript/test/system/install.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {packNTest} from 'pack-n-play'; 16 | import {describe, it} from 'mocha'; 17 | 18 | describe('Packing test', () => { 19 | it('should work in TypeScript code', async function () { 20 | this.timeout(300000); 21 | const options = { 22 | packageDir: process.cwd(), 23 | sample: { 24 | description: 'runs serializer and deserializer in TypeScript', 25 | ts: ` 26 | import * as assert from 'assert'; 27 | import * as protobuf from 'protobufjs'; 28 | import * as serializer from 'proto3-json-serializer'; 29 | const root = protobuf.Root.fromJSON({ 30 | "nested": { 31 | "Test": { 32 | "fields": { 33 | "field": { 34 | "type": "string", 35 | "id": 1 36 | } 37 | } 38 | } 39 | } 40 | }); 41 | const Test = root.lookupType('Test'); 42 | const json = { 43 | field: 'test', 44 | }; 45 | const message = serializer.fromProto3JSON(Test, json); 46 | assert(message); 47 | const serialized = serializer.toProto3JSON(message); 48 | assert.deepStrictEqual(serialized, json); 49 | `, 50 | dependencies: ['protobufjs@^7.4.0'], 51 | }, 52 | }; 53 | await packNTest(options).catch(err => { 54 | console.error(err); 55 | throw err; 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /typescript/test/unit/bytes.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testBytes(root: protobuf.Root) { 23 | const MessageWithBytesField = root.lookupType('test.MessageWithBytesField'); 24 | const buffer = Buffer.from('buffer'); 25 | const message = MessageWithBytesField.fromObject({ 26 | bytesField: buffer, 27 | }); 28 | const uint8Array = new Uint8Array(buffer, 0, buffer.length); 29 | const messageWithUint8Array = MessageWithBytesField.fromObject({ 30 | bytesField: uint8Array, 31 | }); 32 | const json = { 33 | bytesField: buffer.toString('base64'), 34 | }; 35 | 36 | it('serializes bytes as Buffer to proto3 JSON', () => { 37 | const serialized = toProto3JSON(message); 38 | assert.deepStrictEqual(serialized, json); 39 | }); 40 | 41 | it('serializes bytes as Uint8Array to proto3 JSON', () => { 42 | const serialized = toProto3JSON(messageWithUint8Array); 43 | assert.deepStrictEqual(serialized, json); 44 | }); 45 | 46 | it('deserializes from proto3 JSON', () => { 47 | const deserialized = fromProto3JSON(MessageWithBytesField, json); 48 | assert.deepStrictEqual(deserialized, message); 49 | }); 50 | } 51 | 52 | testTwoTypesOfLoad('bytes', testBytes); 53 | -------------------------------------------------------------------------------- /typescript/src/enum.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | import {JSONValue} from './types'; 17 | 18 | export function resolveEnumValueToString( 19 | enumType: protobuf.Enum, 20 | enumValue: JSONValue, 21 | ) { 22 | // for unknown enum values, do not fail and try to do the best we could. 23 | // protobuf.js fromObject() will likely ignore unknown values, but at least 24 | // we won't fail. 25 | if (typeof enumValue === 'number') { 26 | const value = enumType.valuesById[enumValue]; 27 | if (typeof value === 'undefined') { 28 | // unknown value, cannot convert to string, returning number as is 29 | return enumValue; 30 | } 31 | return value; 32 | } 33 | if (typeof enumValue === 'string') { 34 | // for strings, just accept what we got 35 | return enumValue; 36 | } 37 | throw new Error( 38 | 'resolveEnumValueToString: enum value must be a string or a number', 39 | ); 40 | } 41 | 42 | export function resolveEnumValueToNumber( 43 | enumType: protobuf.Enum, 44 | enumValue: JSONValue, 45 | ) { 46 | if (typeof enumValue === 'number') { 47 | // return as is 48 | return enumValue; 49 | } 50 | if (typeof enumValue === 'string') { 51 | const num = enumType.values[enumValue]; 52 | if (typeof num === 'undefined') { 53 | // unknown value, cannot convert to number, returning string as is 54 | return enumValue; 55 | } 56 | return num; 57 | } 58 | throw new Error( 59 | 'resolveEnumValueToNumber: enum value must be a string or a number', 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /typescript/src/duration.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | import {FromObjectValue} from './types'; 17 | 18 | export interface Duration { 19 | seconds: number; // it's technically Long but we don't want to bring it as a dependency for now 20 | nanos?: number; 21 | } 22 | 23 | export function googleProtobufDurationToProto3JSON( 24 | obj: protobuf.Message & Duration, 25 | ) { 26 | // seconds is an instance of Long so it won't be undefined 27 | let durationSeconds = obj.seconds.toString(); 28 | if (typeof obj.nanos === 'number' && obj.nanos > 0) { 29 | // nanosStr should contain 3, 6, or 9 fractional digits. 30 | const nanosStr = obj.nanos 31 | .toString() 32 | .padStart(9, '0') 33 | .replace(/^((?:\d\d\d)+?)(?:0*)$/, '$1'); 34 | durationSeconds += '.' + nanosStr; 35 | } 36 | durationSeconds += 's'; 37 | return durationSeconds; 38 | } 39 | 40 | export function googleProtobufDurationFromProto3JSON(json: string) { 41 | const match = json.match(/^(\d*)(?:\.(\d*))?s$/); 42 | if (!match) { 43 | throw new Error( 44 | `googleProtobufDurationFromProto3JSON: incorrect value ${json} passed as google.protobuf.Duration`, 45 | ); 46 | } 47 | 48 | let seconds = 0; 49 | let nanos = 0; 50 | 51 | if (typeof match[1] === 'string' && match[1].length > 0) { 52 | seconds = parseInt(match[1]); 53 | } 54 | 55 | if (typeof match[2] === 'string' && match[2].length > 0) { 56 | nanos = parseInt(match[2].padEnd(9, '0')); 57 | } 58 | 59 | const result: FromObjectValue = {}; 60 | if (seconds !== 0) { 61 | result.seconds = seconds; 62 | } 63 | if (nanos !== 0) { 64 | result.nanos = nanos; 65 | } 66 | return result; 67 | } 68 | -------------------------------------------------------------------------------- /typescript/test/unit/enum.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testEnum(root: protobuf.Root) { 23 | const MessageWithEnum = root.lookupType('test.MessageWithEnum'); 24 | const message = MessageWithEnum.fromObject({ 25 | enumField: 'KNOWN', 26 | }); 27 | const json = { 28 | enumField: 'KNOWN', 29 | }; 30 | const jsonWithNumber = { 31 | enumField: 1, 32 | }; 33 | 34 | it('serializes to proto3 JSON', () => { 35 | const serialized = toProto3JSON(message); 36 | assert.deepStrictEqual(serialized, json); 37 | }); 38 | 39 | it('serializes to proto3 JSON with numeric enum values', () => { 40 | const serialized = toProto3JSON(message, {numericEnums: true}); 41 | assert.deepStrictEqual(serialized, jsonWithNumber); 42 | console.log(serialized); 43 | }); 44 | 45 | it('deserializes from proto3 JSON', () => { 46 | const deserialized = fromProto3JSON(MessageWithEnum, json); 47 | assert.deepStrictEqual(deserialized, message); 48 | }); 49 | 50 | it('fromProto3JSON allows unknown enum string value', () => { 51 | // the following call should not fail 52 | fromProto3JSON(MessageWithEnum, { 53 | enumField: 'WRONG VALUE', 54 | }); 55 | }); 56 | 57 | it('fromProto3JSON allows unknown enum number value', () => { 58 | // the following call should not fail 59 | fromProto3JSON(MessageWithEnum, {enumField: 42}); 60 | }); 61 | } 62 | 63 | testTwoTypesOfLoad('enum field', testEnum); 64 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | name: ci 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node: [18, 20, 22, 24] 13 | steps: 14 | - uses: actions/checkout@v5 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - run: node --version 19 | # The first installation step ensures that all of our production 20 | # dependencies work on the given Node.js version, this helps us find 21 | # dependencies that don't match our engines field: 22 | - run: npm install --production --engine-strict --ignore-scripts --no-package-lock 23 | # Clean up the production install, before installing dev/production: 24 | - run: rm -rf node_modules 25 | - run: npm install --engine-strict 26 | - run: npm test 27 | env: 28 | MOCHA_THROW_DEPRECATION: false 29 | test-script: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v5 33 | - uses: actions/setup-node@v4 34 | with: 35 | node-version: 18 36 | - run: node --version 37 | - run: npm install --engine-strict 38 | working-directory: .github/scripts 39 | - run: npm test 40 | working-directory: .github/scripts 41 | env: 42 | MOCHA_THROW_DEPRECATION: false 43 | windows: 44 | runs-on: windows-latest 45 | steps: 46 | - uses: actions/checkout@v5 47 | - uses: actions/setup-node@v4 48 | with: 49 | node-version: 18 50 | - run: npm install --engine-strict 51 | - run: npm test 52 | env: 53 | MOCHA_THROW_DEPRECATION: false 54 | lint: 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v5 58 | - uses: actions/setup-node@v4 59 | with: 60 | node-version: 18 61 | - run: npm install 62 | - run: npm run lint 63 | docs: 64 | runs-on: ubuntu-latest 65 | steps: 66 | - uses: actions/checkout@v5 67 | - uses: actions/setup-node@v4 68 | with: 69 | node-version: 18 70 | - run: npm install 71 | - run: npm run docs 72 | - uses: JustinBeckwith/linkinator-action@v1 73 | with: 74 | paths: docs/ 75 | -------------------------------------------------------------------------------- /.github/scripts/close-unresponsive.cjs: -------------------------------------------------------------------------------- 1 | /// Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | function labeledEvent(data) { 16 | return data.event === "labeled" && data.label.name === "needs more info"; 17 | } 18 | 19 | const numberOfDaysLimit = 15; 20 | const close_message = `This has been closed since a request for information has \ 21 | not been answered for ${numberOfDaysLimit} days. It can be reopened when the \ 22 | requested information is provided.`; 23 | 24 | module.exports = async ({ github, context }) => { 25 | const owner = context.repo.owner; 26 | const repo = context.repo.repo; 27 | 28 | const issues = await github.rest.issues.listForRepo({ 29 | owner: owner, 30 | repo: repo, 31 | labels: "needs more info", 32 | }); 33 | const numbers = issues.data.map((e) => e.number); 34 | 35 | for (const number of numbers) { 36 | const events = await github.paginate( 37 | github.rest.issues.listEventsForTimeline, 38 | { 39 | owner: owner, 40 | repo: repo, 41 | issue_number: number, 42 | }, 43 | (response) => response.data.filter(labeledEvent) 44 | ); 45 | 46 | const latest_response_label = events[events.length - 1]; 47 | 48 | const created_at = new Date(latest_response_label.created_at); 49 | const now = new Date(); 50 | const diff = now - created_at; 51 | const diffDays = diff / (1000 * 60 * 60 * 24); 52 | 53 | if (diffDays > numberOfDaysLimit) { 54 | await github.rest.issues.update({ 55 | owner: owner, 56 | repo: repo, 57 | issue_number: number, 58 | state: "closed", 59 | }); 60 | 61 | await github.rest.issues.createComment({ 62 | owner: owner, 63 | repo: repo, 64 | issue_number: number, 65 | body: close_message, 66 | }); 67 | } 68 | } 69 | }; -------------------------------------------------------------------------------- /samples/quickstart.js: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | function main() { 18 | // [START proto3_json_serializer_quickstart] 19 | function quickstart() { 20 | const assert = require('assert'); 21 | const path = require('path'); 22 | const protobuf = require('protobufjs'); 23 | const serializer = require('proto3-json-serializer'); 24 | 25 | // We'll take sample protos from google-proto-files but the code will work with any protos 26 | const protos = require('google-proto-files'); 27 | 28 | // Load some proto file 29 | const rpcProtos = protos.getProtoPath('rpc'); 30 | const root = protobuf.loadSync([ 31 | path.join(rpcProtos, 'status.proto'), 32 | path.join(rpcProtos, 'error_details.proto'), 33 | ]); 34 | const Status = root.lookupType('google.rpc.Status'); 35 | 36 | // If you have a protobuf object that follows proto3 JSON syntax 37 | // https://developers.google.com/protocol-buffers/docs/proto3#json 38 | // (this is an example of google.rpc.Status message in JSON) 39 | const json = { 40 | code: 3, 41 | message: 'Test error message', 42 | details: [ 43 | { 44 | '@type': 'google.rpc.BadRequest', 45 | fieldViolations: [ 46 | { 47 | field: 'field', 48 | description: 'must not be null', 49 | }, 50 | ], 51 | }, 52 | ], 53 | }; 54 | 55 | // You can deserialize it into a protobuf.js object: 56 | const deserialized = serializer.fromProto3JSON(Status, json); 57 | console.log(deserialized); 58 | 59 | // And serialize it back 60 | const serialized = serializer.toProto3JSON(deserialized); 61 | assert.deepStrictEqual(serialized, json); 62 | 63 | console.log('Quickstart completed'); 64 | } 65 | // [END proto3_json_serializer_quickstart] 66 | 67 | quickstart(); 68 | } 69 | 70 | main(); 71 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this library 3 | labels: 4 | - feature request 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **PLEASE READ**: If you have a support contract with Google, please 10 | create an issue in the [support 11 | console](https://cloud.google.com/support/) instead of filing on GitHub. 12 | This will ensure a timely response. Otherwise, please make sure to 13 | follow the steps below. 14 | - type: textarea 15 | attributes: 16 | label: > 17 | A screenshot that you have tested with "Try this API". 18 | description: > 19 | As our client libraries are mostly autogenerated, we kindly request 20 | that you test whether your feature request is with the client library, or with the 21 | API itself. To do so, please search for your API 22 | here: https://developers.google.com/apis-explorer and attempt to 23 | reproduce the issue in the given method. Please include a screenshot of 24 | the response in "Try this API". This response should NOT match the current 25 | behavior you are experiencing. If the behavior is the same, it means 26 | that you are likely requesting a feature for the API itself. In that 27 | case, please submit an issue to the API team, either by submitting an 28 | issue in its issue tracker https://cloud.google.com/support/docs/issue-trackers, or by 29 | submitting an issue in its linked tracker in the .repo-metadata.json 30 | file in the API under packages/* ([example](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/)) 31 | 32 | Example of library specific issues would be: retry strategies, authentication questions, or issues with typings. 33 | Examples of API issues would include: expanding method parameter types, adding functionality to an API. 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: > 39 | What would you like to see in the library? 40 | description: > 41 | Screenshots can be provided in the issue body below. 42 | placeholder: | 43 | 1. Set up authentication like so 44 | 2. Run the program like so 45 | 3. X would be nice to happen 46 | 47 | - type: textarea 48 | attributes: 49 | label: Describe alternatives you've considered 50 | 51 | - type: textarea 52 | attributes: 53 | label: Additional context/notes -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_request.yml: -------------------------------------------------------------------------------- 1 | name: Documentation Requests 2 | description: Requests for more information 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: > 7 | Please use this issue type to log documentation requests against the library itself. 8 | These requests should involve documentation on Github (`.md` files), and should relate to the library 9 | itself. If you have questions or documentation requests for an API, please 10 | reach out to the API tracker itself. 11 | 12 | Please submit an issue to the API team, either by submitting an 13 | issue in its issue tracker https://cloud.google.com/support/docs/issue-trackers), or by 14 | submitting an issue in its linked tracker in the .repo-metadata.json 15 | file in the API under packages/* ([example](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/)). 16 | You can also submit a request to documentation on cloud.google.com itself with the "Send Feedback" 17 | on the bottom of the page. 18 | 19 | 20 | Please note that documentation requests and questions for specific APIs 21 | will be closed. 22 | - type: checkboxes 23 | attributes: 24 | label: Please make sure you have searched for information in the following 25 | guides. 26 | options: 27 | - label: "Search the issues already opened: 28 | https://github.com/GoogleCloudPlatform/google-cloud-node/issues" 29 | required: true 30 | - label: "Check our Troubleshooting guide: 31 | https://googlecloudplatform.github.io/google-cloud-node/#/docs/guid\ 32 | es/troubleshooting" 33 | required: true 34 | - label: "Check our FAQ: 35 | https://googlecloudplatform.github.io/google-cloud-node/#/docs/guid\ 36 | es/faq" 37 | required: true 38 | - label: "Check our libraries HOW-TO: 39 | https://github.com/googleapis/gax-nodejs/blob/main/client-libraries\ 40 | .md" 41 | required: true 42 | - label: "Check out our authentication guide: 43 | https://github.com/googleapis/google-auth-library-nodejs" 44 | required: true 45 | - label: "Check out handwritten samples for many of our APIs: 46 | https://github.com/GoogleCloudPlatform/nodejs-docs-samples" 47 | required: true 48 | - type: textarea 49 | attributes: 50 | label: > 51 | Documentation Request 52 | validations: 53 | required: true 54 | -------------------------------------------------------------------------------- /typescript/src/wrappers.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {bytesFromProto3JSON, bytesToProto3JSON} from './bytes'; 16 | import {LongStub} from './types'; 17 | import {assert} from './util'; 18 | 19 | export interface NumberValue { 20 | value: number | object; // Long can be passed here 21 | } 22 | 23 | export interface StringValue { 24 | value: string; 25 | } 26 | 27 | export interface BoolValue { 28 | value: boolean; 29 | } 30 | 31 | export interface BytesValue { 32 | value: Buffer | Uint8Array; 33 | } 34 | 35 | export function wrapperToProto3JSON( 36 | obj: protobuf.Message & (NumberValue | StringValue | BoolValue | BytesValue), 37 | ) { 38 | if (!Object.prototype.hasOwnProperty.call(obj, 'value')) { 39 | return null; 40 | } 41 | if (Buffer.isBuffer(obj.value) || obj.value instanceof Uint8Array) { 42 | return bytesToProto3JSON(obj.value); 43 | } 44 | if (typeof obj.value === 'object') { 45 | assert( 46 | obj.value.constructor.name === 'Long', 47 | `wrapperToProto3JSON: expected to see a number, a string, a boolean, or a Long, but got ${obj.value}`, 48 | ); 49 | return (obj.value as LongStub).toString(); 50 | } 51 | // JSON accept special string values "NaN", "Infinity", and "-Infinity". 52 | if (typeof obj.value === 'number' && !Number.isFinite(obj.value)) { 53 | return obj.value.toString(); 54 | } 55 | return obj.value; 56 | } 57 | 58 | export function wrapperFromProto3JSON( 59 | typeName: string, 60 | json: number | string | boolean | null, 61 | ) { 62 | if (json === null) { 63 | return { 64 | value: null, 65 | }; 66 | } 67 | if (typeName === '.google.protobuf.BytesValue') { 68 | if (typeof json !== 'string') { 69 | throw new Error( 70 | `numberWrapperFromProto3JSON: expected to get a string for google.protobuf.BytesValue but got ${typeof json}`, 71 | ); 72 | } 73 | return { 74 | value: bytesFromProto3JSON(json), 75 | }; 76 | } 77 | return { 78 | value: json, 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /typescript/src/timestamp.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | import {FromObjectValue} from './types'; 17 | 18 | export interface Timestamp { 19 | seconds: number; // it's technically Long but we don't want to bring it as a dependency for now 20 | nanos?: number; 21 | } 22 | 23 | export function googleProtobufTimestampToProto3JSON( 24 | obj: protobuf.Message & Timestamp, 25 | ) { 26 | // seconds is an instance of Long so it won't be undefined 27 | const durationSeconds = obj.seconds; 28 | const date = new Date(durationSeconds * 1000).toISOString(); 29 | // Pad leading zeros if nano string length is less than 9. 30 | let nanos = obj.nanos?.toString().padStart(9, '0'); 31 | // Trim the unsignificant zeros and keep 3, 6, or 9 decimal digits. 32 | while (nanos && nanos.length > 3 && nanos.endsWith('000')) { 33 | nanos = nanos.slice(0, -3); 34 | } 35 | return date.replace(/(?:\.\d{0,9})/, '.' + nanos); 36 | } 37 | 38 | export function googleProtobufTimestampFromProto3JSON(json: string) { 39 | const match = json.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?/); 40 | if (!match) { 41 | throw new Error( 42 | `googleProtobufDurationFromProto3JSON: incorrect value ${json} passed as google.protobuf.Duration`, 43 | ); 44 | } 45 | const date = new Date(json); 46 | const millisecondsSinceEpoch = date.getTime(); 47 | const seconds = Math.floor(millisecondsSinceEpoch / 1000); 48 | // The fractional seconds in the JSON timestamps can go up to 9 digits (i.e. up to 1 nanosecond resolution). 49 | // However, Javascript Date object represent any date and time to millisecond precision. 50 | // To keep the precision, we extract the fractional seconds and append 0 until the length is equal to 9. 51 | let nanos = 0; 52 | const secondsFromDate = json.split('.')[1]; 53 | if (secondsFromDate) { 54 | nanos = parseInt(secondsFromDate.slice(0, -1).padEnd(9, '0')); 55 | } 56 | const result: FromObjectValue = {}; 57 | if (seconds !== 0) { 58 | result.seconds = seconds; 59 | } 60 | if (nanos !== 0) { 61 | result.nanos = nanos; 62 | } 63 | return result; 64 | } 65 | -------------------------------------------------------------------------------- /typescript/test/unit/nested.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | import {JSONObject} from '../../src'; 22 | 23 | function testNestedMessage(root: protobuf.Root) { 24 | const MessageWithNestedMessage = root.lookupType( 25 | 'test.MessageWithNestedMessage', 26 | ); 27 | const message = MessageWithNestedMessage.fromObject({ 28 | innerMessage: { 29 | stringField: 'test', 30 | }, 31 | }); 32 | const json = { 33 | innerMessage: { 34 | stringField: 'test', 35 | }, 36 | }; 37 | 38 | const emptyMessage = MessageWithNestedMessage.fromObject({}); 39 | const messageWithNull = MessageWithNestedMessage.fromObject({}); 40 | (messageWithNull as unknown as JSONObject)['innerMessage'] = null; 41 | const jsonWithNull = { 42 | innerMessage: null, 43 | }; 44 | const emptyJson = {}; 45 | 46 | it('serializes to proto3 JSON', () => { 47 | const serialized = toProto3JSON(message); 48 | assert.deepStrictEqual(serialized, json); 49 | }); 50 | 51 | it('deserializes from proto3 JSON', () => { 52 | const deserialized = fromProto3JSON(MessageWithNestedMessage, json); 53 | assert.deepStrictEqual(deserialized, message); 54 | }); 55 | 56 | it('serialized empty message results in empty proto3 JSON', () => { 57 | const serialized = toProto3JSON(emptyMessage); 58 | assert.deepStrictEqual(serialized, emptyJson); 59 | }); 60 | 61 | it('serialized null inner message results in JSON with null', () => { 62 | const serialized = toProto3JSON(messageWithNull); 63 | assert.deepStrictEqual(serialized, jsonWithNull); 64 | }); 65 | 66 | it('deserialized null inner message from proto3 JSON', () => { 67 | const deserialized = fromProto3JSON(MessageWithNestedMessage, jsonWithNull); 68 | assert.deepStrictEqual(deserialized, emptyMessage); 69 | }); 70 | } 71 | 72 | testTwoTypesOfLoad('nested messages', testNestedMessage); 73 | -------------------------------------------------------------------------------- /typescript/test/unit/oneof.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testOneof(root: protobuf.Root) { 23 | const MessageWithOneof = root.lookupType('test.MessageWithOneof'); 24 | const message1 = MessageWithOneof.fromObject({ 25 | stringField: 'test', 26 | }); 27 | const message2 = MessageWithOneof.fromObject({ 28 | integerField: 42, 29 | }); 30 | const message3 = MessageWithOneof.fromObject({ 31 | messageField: { 32 | stringField: 'test', 33 | }, 34 | }); 35 | const json1 = { 36 | stringField: 'test', 37 | }; 38 | const json2 = { 39 | integerField: 42, 40 | }; 41 | const json3 = { 42 | messageField: { 43 | stringField: 'test', 44 | }, 45 | }; 46 | 47 | it('serializes oneof 1 to proto3 JSON', () => { 48 | const serialized = toProto3JSON(message1); 49 | assert.deepStrictEqual(serialized, json1); 50 | }); 51 | 52 | it('deserializes oneof 1 from proto3 JSON', () => { 53 | const deserialized = fromProto3JSON(MessageWithOneof, json1); 54 | assert.deepStrictEqual(deserialized, message1); 55 | }); 56 | 57 | it('serializes oneof 2 to proto3 JSON', () => { 58 | const serialized = toProto3JSON(message2); 59 | assert.deepStrictEqual(serialized, json2); 60 | }); 61 | 62 | it('deserializes oneof 2 from proto3 JSON', () => { 63 | const deserialized = fromProto3JSON(MessageWithOneof, json2); 64 | assert.deepStrictEqual(deserialized, message2); 65 | }); 66 | 67 | it('serializes oneof 3 to proto3 JSON', () => { 68 | const serialized = toProto3JSON(message3); 69 | assert.deepStrictEqual(serialized, json3); 70 | }); 71 | 72 | it('deserializes oneof 3 from proto3 JSON', () => { 73 | const deserialized = fromProto3JSON(MessageWithOneof, json3); 74 | assert.deepStrictEqual(deserialized, message3); 75 | }); 76 | } 77 | 78 | testTwoTypesOfLoad('oneof', testOneof); 79 | -------------------------------------------------------------------------------- /typescript/test/unit/duration.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testDuration(root: protobuf.Root) { 23 | const MessageWithDuration = root.lookupType('test.MessageWithDuration'); 24 | // note the zero padding according to the spec: 25 | // the number of the digits in the fractional part must be 0, 3, 6, or 9 26 | const testMapping = [ 27 | {duration: {seconds: 3}, value: '3s'}, 28 | {duration: {nanos: 100000000}, value: '0.100s'}, 29 | {duration: {}, value: '0s'}, 30 | {duration: {seconds: 3, nanos: 5}, value: '3.000000005s'}, 31 | {duration: {seconds: 3, nanos: 5000}, value: '3.000005s'}, 32 | {duration: {seconds: 3, nanos: 5000000}, value: '3.005s'}, 33 | {duration: {nanos: 1}, value: '0.000000001s'}, 34 | {duration: {nanos: 10}, value: '0.000000010s'}, 35 | {duration: {nanos: 100}, value: '0.000000100s'}, 36 | {duration: {nanos: 1000}, value: '0.000001s'}, 37 | {duration: {nanos: 10000}, value: '0.000010s'}, 38 | {duration: {nanos: 100000}, value: '0.000100s'}, 39 | {duration: {nanos: 1000000}, value: '0.001s'}, 40 | {duration: {nanos: 10000000}, value: '0.010s'}, 41 | {duration: {nanos: 100000000}, value: '0.100s'}, 42 | ]; 43 | 44 | for (const mapping of testMapping) { 45 | const message = MessageWithDuration.fromObject({ 46 | durationField: mapping.duration, 47 | }); 48 | const json = { 49 | durationField: mapping.value, 50 | }; 51 | 52 | it(`serializes ${JSON.stringify(mapping.duration)} to proto3 JSON`, () => { 53 | const serialized = toProto3JSON(message); 54 | assert.deepStrictEqual(serialized, json); 55 | }); 56 | 57 | it(`deserializes "${mapping.value}" from proto3 JSON`, () => { 58 | const deserialized = fromProto3JSON(MessageWithDuration, json); 59 | assert.deepStrictEqual(deserialized, message); 60 | }); 61 | } 62 | } 63 | 64 | testTwoTypesOfLoad('google.protobuf.Duration', testDuration); 65 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | **Table of contents** 4 | 5 | * [Contributor License Agreements](#contributor-license-agreements) 6 | * [Contributing a patch](#contributing-a-patch) 7 | * [Running the tests](#running-the-tests) 8 | * [Releasing the library](#releasing-the-library) 9 | 10 | ## Contributor License Agreements 11 | 12 | We'd love to accept your sample apps and patches! Before we can take them, we 13 | have to jump a couple of legal hurdles. 14 | 15 | Please fill out either the individual or corporate Contributor License Agreement 16 | (CLA). 17 | 18 | * If you are an individual writing original source code and you're sure you 19 | own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). 20 | * If you work for a company that wants to allow you to contribute your work, 21 | then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). 22 | 23 | Follow either of the two links above to access the appropriate CLA and 24 | instructions for how to sign and return it. Once we receive it, we'll be able to 25 | accept your pull requests. 26 | 27 | ## Contributing A Patch 28 | 29 | 1. Submit an issue describing your proposed change to the repo in question. 30 | 1. The repo owner will respond to your issue promptly. 31 | 1. If your proposed change is accepted, and you haven't already done so, sign a 32 | Contributor License Agreement (see details above). 33 | 1. Fork the desired repo, develop and test your code changes. 34 | 1. Ensure that your code adheres to the existing style in the code to which 35 | you are contributing. 36 | 1. Ensure that your code has an appropriate set of tests which all pass. 37 | 1. Title your pull request following [Conventional Commits](https://www.conventionalcommits.org/) styling. 38 | 1. Submit a pull request. 39 | 40 | ### Before you begin 41 | 42 | 1. [Select or create a Cloud Platform project][projects]. 43 | 1. [Set up authentication with a service account][auth] so you can access the 44 | API from your local workstation. 45 | 46 | 47 | ## Running the tests 48 | 49 | 1. [Prepare your environment for Node.js setup][setup]. 50 | 51 | 1. Install dependencies: 52 | 53 | npm install 54 | 55 | 1. Run the tests: 56 | 57 | # Run unit tests. 58 | npm test 59 | 60 | # Run sample integration tests. 61 | npm run samples-test 62 | 63 | # Run all system tests. 64 | npm run system-test 65 | 66 | 1. Lint (and maybe fix) any changes: 67 | 68 | npm run fix 69 | 70 | [setup]: https://cloud.google.com/nodejs/docs/setup 71 | [projects]: https://console.cloud.google.com/project 72 | [billing]: https://support.google.com/cloud/answer/6293499#enable-billing 73 | 74 | [auth]: https://cloud.google.com/docs/authentication/getting-started -------------------------------------------------------------------------------- /typescript/test/unit/any.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testRegularAny(root: protobuf.Root) { 23 | const MessageWithAny = root.lookupType('test.MessageWithAny'); 24 | const AnyContent = root.lookupType('test.AnyContent'); 25 | const any = AnyContent.fromObject({ 26 | stringField: 'string', 27 | }); 28 | const message = MessageWithAny.fromObject({ 29 | anyField: { 30 | type_url: 'types.googleapis.com/test.AnyContent', // note: snake case here because of protobuf.js bug/feature 31 | value: Buffer.from(AnyContent.encode(any).finish()), 32 | }, 33 | }); 34 | const json = { 35 | anyField: { 36 | '@type': 'types.googleapis.com/test.AnyContent', 37 | stringField: 'string', 38 | }, 39 | }; 40 | 41 | it('serializes to proto3 JSON', () => { 42 | const serialized = toProto3JSON(message); 43 | assert.deepStrictEqual(serialized, json); 44 | }); 45 | 46 | it('deserializes from proto3 JSON', () => { 47 | const deserialized = fromProto3JSON(MessageWithAny, json); 48 | assert.deepStrictEqual(deserialized, message); 49 | }); 50 | } 51 | 52 | function testAnyWithSpecialType(root: protobuf.Root) { 53 | const MessageWithAny = root.lookupType('test.MessageWithAny'); 54 | const Value = root.lookupType('google.protobuf.Value'); 55 | const value = Value.fromObject({ 56 | stringValue: 'test', 57 | }); 58 | const message = MessageWithAny.fromObject({ 59 | anyField: { 60 | type_url: 'types.googleapis.com/google.protobuf.Value', 61 | value: Buffer.from(Value.encode(value).finish()), 62 | }, 63 | }); 64 | const json = { 65 | anyField: { 66 | '@type': 'types.googleapis.com/google.protobuf.Value', 67 | value: 'test', 68 | }, 69 | }; 70 | 71 | it('serializes google.protobuf.Any with a special type to proto3 JSON', () => { 72 | const serialized = toProto3JSON(message); 73 | assert.deepStrictEqual(serialized, json); 74 | }); 75 | 76 | it('deserializes google.protobuf.Any with a special type from proto3 JSON', () => { 77 | const deserialized = fromProto3JSON(MessageWithAny, json); 78 | assert.deepStrictEqual(deserialized, message); 79 | }); 80 | } 81 | 82 | testTwoTypesOfLoad('google.protobuf.Any', [ 83 | testRegularAny, 84 | testAnyWithSpecialType, 85 | ]); 86 | -------------------------------------------------------------------------------- /.github/scripts/close-invalid-link.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const fs = require('fs'); 16 | const yaml = require('js-yaml'); 17 | const path = require('path'); 18 | const TEMPLATE_FILE_PATH = path.resolve(__dirname, '../ISSUE_TEMPLATE/bug_report.yml') 19 | 20 | async function closeIssue(github, owner, repo, number) { 21 | await github.rest.issues.createComment({ 22 | owner: owner, 23 | repo: repo, 24 | issue_number: number, 25 | body: "Issue was opened with an invalid reproduction link. Please make sure the repository is a valid, publicly-accessible github repository, and make sure the url is complete (example: https://github.com/googleapis/google-cloud-node)" 26 | }); 27 | await github.rest.issues.update({ 28 | owner: owner, 29 | repo: repo, 30 | issue_number: number, 31 | state: "closed" 32 | }); 33 | } 34 | module.exports = async ({ github, context }) => { 35 | const owner = context.repo.owner; 36 | const repo = context.repo.repo; 37 | const number = context.issue.number; 38 | 39 | const issue = await github.rest.issues.get({ 40 | owner: owner, 41 | repo: repo, 42 | issue_number: number, 43 | }); 44 | 45 | const yamlData = fs.readFileSync(TEMPLATE_FILE_PATH, 'utf8'); 46 | const obj = yaml.load(yamlData); 47 | const linkMatchingText = (obj.body.find(x => {return x.type === 'input' && x.validations.required === true && x.attributes.label.includes('link')})).attributes.label; 48 | const isBugTemplate = issue.data.body.includes(linkMatchingText); 49 | 50 | if (isBugTemplate) { 51 | console.log(`Issue ${number} is a bug template`) 52 | try { 53 | const text = issue.data.body; 54 | const match = text.indexOf(linkMatchingText); 55 | if (match !== -1) { 56 | const nextLineIndex = text.indexOf('http', match); 57 | if (nextLineIndex == -1) { 58 | await closeIssue(github, owner, repo, number); 59 | return; 60 | } 61 | const link = text.substring(nextLineIndex, text.indexOf('\n', nextLineIndex)); 62 | console.log(`Issue ${number} contains this link: ${link}`); 63 | const isValidLink = (await fetch(link)).ok; 64 | console.log(`Issue ${number} has a ${isValidLink ? "valid" : "invalid"} link`) 65 | if (!isValidLink) { 66 | await closeIssue(github, owner, repo, number); 67 | } 68 | } 69 | } catch (err) { 70 | await closeIssue(github, owner, repo, number); 71 | } 72 | } 73 | }; -------------------------------------------------------------------------------- /.github/scripts/fixtures/invalidIssueBody.txt: -------------------------------------------------------------------------------- 1 | ### Please make sure you have searched for information in the following guides. 2 | 3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues 4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js 5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting 6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq 7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md 8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs 9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples 10 | 11 | ### A screenshot that you have tested with "Try this API". 12 | 13 | 14 | N/A 15 | 16 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal reproduction. 17 | 18 | not-a-link 19 | 20 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction. 21 | 22 | 23 | Change MY_PROJECT to your project name, add credentials if needed and run. 24 | 25 | ### A clear and concise description of what the bug is, and what you expected to happen. 26 | 27 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling. 28 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object 29 | at _write (node:internal/streams/writable:474:13) 30 | at Writable.write (node:internal/streams/writable:502:10) 31 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22) 32 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 33 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 34 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 35 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27) 36 | at Object.onceWrapper (node:events:633:26) 37 | at Pumpify.emit (node:events:518:28) 38 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22) 39 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 40 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 41 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 42 | at PassThrough.ondata (node:internal/streams/readable:1007:22) 43 | at PassThrough.emit (node:events:518:28) 44 | at addChunk (node:internal/streams/readable:559:12) { 45 | code: 'ERR_INVALID_ARG_TYPE' 46 | 47 | 48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 49 | 50 | No library should crash an application this way. -------------------------------------------------------------------------------- /.github/scripts/fixtures/validIssueBody.txt: -------------------------------------------------------------------------------- 1 | ### Please make sure you have searched for information in the following guides. 2 | 3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues 4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js 5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting 6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq 7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md 8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs 9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples 10 | 11 | ### A screenshot that you have tested with "Try this API". 12 | 13 | 14 | N/A 15 | 16 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal reproduction. 17 | 18 | https://gist.github.com/orgads/13cbf44c91923da27d8772b5f10489c9 19 | 20 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction. 21 | 22 | 23 | Change MY_PROJECT to your project name, add credentials if needed and run. 24 | 25 | ### A clear and concise description of what the bug is, and what you expected to happen. 26 | 27 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling. 28 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object 29 | at _write (node:internal/streams/writable:474:13) 30 | at Writable.write (node:internal/streams/writable:502:10) 31 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22) 32 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 33 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 34 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 35 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27) 36 | at Object.onceWrapper (node:events:633:26) 37 | at Pumpify.emit (node:events:518:28) 38 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22) 39 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 40 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 41 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 42 | at PassThrough.ondata (node:internal/streams/readable:1007:22) 43 | at PassThrough.emit (node:events:518:28) 44 | at addChunk (node:internal/streams/readable:559:12) { 45 | code: 'ERR_INVALID_ARG_TYPE' 46 | 47 | 48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 49 | 50 | No library should crash an application this way. -------------------------------------------------------------------------------- /.github/scripts/fixtures/validIssueBodyDifferentLinkLocation.txt: -------------------------------------------------------------------------------- 1 | ### Please make sure you have searched for information in the following guides. 2 | 3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues 4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js 5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting 6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq 7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md 8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs 9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples 10 | 11 | ### A screenshot that you have tested with "Try this API". 12 | 13 | 14 | N/A 15 | 16 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction. 17 | 18 | 19 | Change MY_PROJECT to your project name, add credentials if needed and run. 20 | 21 | ### A clear and concise description of what the bug is, and what you expected to happen. 22 | 23 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling. 24 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object 25 | at _write (node:internal/streams/writable:474:13) 26 | at Writable.write (node:internal/streams/writable:502:10) 27 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22) 28 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 29 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 30 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 31 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27) 32 | at Object.onceWrapper (node:events:633:26) 33 | at Pumpify.emit (node:events:518:28) 34 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22) 35 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 36 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 37 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 38 | at PassThrough.ondata (node:internal/streams/readable:1007:22) 39 | at PassThrough.emit (node:events:518:28) 40 | at addChunk (node:internal/streams/readable:559:12) { 41 | code: 'ERR_INVALID_ARG_TYPE' 42 | 43 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository with a minimal reproduction. 44 | 45 | 46 | https://gist.github.com/orgads/13cbf44c91923da27d8772b5f10489c9 47 | 48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 49 | 50 | No library should crash an application this way. -------------------------------------------------------------------------------- /typescript/test/unit/timestamp.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testTimestamp(root: protobuf.Root) { 23 | const MessageWithTimestamp = root.lookupType('test.MessageWithTimestamp'); 24 | const testMapping = [ 25 | // example from google/protobuf/timestamp.proto 26 | { 27 | timestamp: {seconds: 1484443815, nanos: 10000000}, 28 | value: '2017-01-15T01:30:15.010Z', 29 | }, 30 | {timestamp: {nanos: 10000000}, value: '1970-01-01T00:00:00.010Z'}, 31 | {timestamp: {seconds: 1484443815}, value: '2017-01-15T01:30:15.000Z'}, 32 | { 33 | timestamp: {seconds: 1642115149, nanos: 91148000}, 34 | value: '2022-01-13T23:05:49.091148Z', 35 | }, 36 | { 37 | timestamp: {seconds: 1642121565, nanos: 10000}, 38 | value: '2022-01-14T00:52:45.000010Z', 39 | }, 40 | { 41 | timestamp: {seconds: 1642121565, nanos: 123456789}, 42 | value: '2022-01-14T00:52:45.123456789Z', 43 | }, 44 | { 45 | timestamp: {seconds: 1640995200}, 46 | value: '2022-01-01T00:00:00.000Z', 47 | }, 48 | ]; 49 | 50 | for (const mapping of testMapping) { 51 | const message = MessageWithTimestamp.fromObject({ 52 | timestampField: mapping.timestamp, 53 | }); 54 | const json = { 55 | timestampField: mapping.value, 56 | }; 57 | 58 | it(`serializes ${JSON.stringify(mapping.timestamp)} to proto3 JSON`, () => { 59 | const serialized = toProto3JSON(message); 60 | assert.deepStrictEqual(serialized, json); 61 | }); 62 | 63 | it(`deserializes "${mapping.value}" from proto3 JSON`, () => { 64 | const deserialized = fromProto3JSON(MessageWithTimestamp, json); 65 | assert.deepStrictEqual(deserialized, message); 66 | }); 67 | } 68 | 69 | describe('Timestamp has no millisecond', () => { 70 | const message = MessageWithTimestamp.fromObject({ 71 | timestampField: {seconds: 1640995200}, 72 | }); 73 | it('serialized date has no second to proto3 JSON', () => { 74 | const serialized = toProto3JSON(message); 75 | assert.deepStrictEqual(serialized, { 76 | timestampField: '2022-01-01T00:00:00.000Z', 77 | }); 78 | }); 79 | 80 | it('deserializes timestamp has no second from proto3 JSON', () => { 81 | const deserialized = fromProto3JSON(MessageWithTimestamp, { 82 | timestampField: '2022-01-01T00:00:00Z', 83 | }); 84 | assert.deepStrictEqual(deserialized, message); 85 | }); 86 | }); 87 | } 88 | 89 | testTwoTypesOfLoad('google.protobuf.Timestamp', testTimestamp); 90 | -------------------------------------------------------------------------------- /.github/scripts/tests/close-invalid-link.test.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const { describe, it } = require('mocha'); 18 | const closeInvalidLink = require('../close-invalid-link.cjs'); 19 | const fs = require('fs'); 20 | const sinon = require('sinon'); 21 | 22 | describe('close issues with invalid links', () => { 23 | let octokitStub; 24 | let issuesStub; 25 | 26 | beforeEach(() => { 27 | issuesStub = { 28 | get: sinon.stub(), 29 | createComment: sinon.stub(), 30 | update: sinon.stub(), 31 | }; 32 | octokitStub = { 33 | rest: { 34 | issues: issuesStub, 35 | }, 36 | }; 37 | }); 38 | 39 | afterEach(() => { 40 | sinon.restore(); 41 | }); 42 | 43 | it('does not do anything if it is not a bug', async () => { 44 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 45 | issuesStub.get.resolves({ data: { body: "I'm having a problem with this." } }); 46 | 47 | await closeInvalidLink({ github: octokitStub, context }); 48 | 49 | sinon.assert.calledOnce(issuesStub.get); 50 | sinon.assert.notCalled(issuesStub.createComment); 51 | sinon.assert.notCalled(issuesStub.update); 52 | }); 53 | 54 | it('does not do anything if it is a bug with an appropriate link', async () => { 55 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 56 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/validIssueBody.txt', 'utf-8') } }); 57 | 58 | await closeInvalidLink({ github: octokitStub, context }); 59 | 60 | sinon.assert.calledOnce(issuesStub.get); 61 | sinon.assert.notCalled(issuesStub.createComment); 62 | sinon.assert.notCalled(issuesStub.update); 63 | }); 64 | 65 | it('does not do anything if it is a bug with an appropriate link and the template changes', async () => { 66 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 67 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/validIssueBodyDifferentLinkLocation.txt', 'utf-8') } }); 68 | 69 | await closeInvalidLink({ github: octokitStub, context }); 70 | 71 | sinon.assert.calledOnce(issuesStub.get); 72 | sinon.assert.notCalled(issuesStub.createComment); 73 | sinon.assert.notCalled(issuesStub.update); 74 | }); 75 | 76 | it('closes the issue if the link is invalid', async () => { 77 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 78 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/invalidIssueBody.txt', 'utf-8') } }); 79 | 80 | await closeInvalidLink({ github: octokitStub, context }); 81 | 82 | sinon.assert.calledOnce(issuesStub.get); 83 | sinon.assert.calledOnce(issuesStub.createComment); 84 | sinon.assert.calledOnce(issuesStub.update); 85 | }); 86 | }); -------------------------------------------------------------------------------- /typescript/test/unit/wrappers.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testWrapperTypes(root: protobuf.Root) { 23 | const MessageWithWrappers = root.lookupType('test.MessageWithWrappers'); 24 | const buffer = Buffer.from('buffer'); 25 | const message = MessageWithWrappers.fromObject({ 26 | doubleValueField: {value: 3.14}, 27 | floatValueField: {value: 3.14}, 28 | int64ValueField: {value: -42}, 29 | uint64ValueField: {value: 42}, 30 | int32ValueField: {value: -43}, 31 | uint32ValueField: {value: 43}, 32 | boolValueField: {value: true}, 33 | stringValueField: {value: 'test'}, 34 | bytesValueField: {value: buffer}, 35 | nanValueField: {value: NaN}, 36 | infinityValueField: {value: Infinity}, 37 | negativeInfinityValueField: {value: -Infinity}, 38 | }); 39 | const json = { 40 | doubleValueField: 3.14, 41 | floatValueField: 3.14, 42 | int64ValueField: '-42', 43 | uint64ValueField: '42', 44 | int32ValueField: -43, 45 | uint32ValueField: 43, 46 | boolValueField: true, 47 | stringValueField: 'test', 48 | bytesValueField: buffer.toString('base64'), 49 | nanValueField: 'NaN', 50 | infinityValueField: 'Infinity', 51 | negativeInfinityValueField: '-Infinity', 52 | }; 53 | 54 | const messageWithNulls = MessageWithWrappers.fromObject({ 55 | doubleValueField: {}, 56 | floatValueField: {}, 57 | int64ValueField: {}, 58 | uint64ValueField: {}, 59 | int32ValueField: {}, 60 | uint32ValueField: {}, 61 | boolValueField: {}, 62 | stringValueField: {}, 63 | bytesValueField: {}, 64 | }); 65 | const jsonWithNulls = { 66 | doubleValueField: null, 67 | floatValueField: null, 68 | int64ValueField: null, 69 | uint64ValueField: null, 70 | int32ValueField: null, 71 | uint32ValueField: null, 72 | boolValueField: null, 73 | stringValueField: null, 74 | bytesValueField: null, 75 | }; 76 | 77 | it('serializes to proto3 JSON', () => { 78 | const serialized = toProto3JSON(message); 79 | assert.deepStrictEqual(serialized, json); 80 | }); 81 | 82 | it('deserializes from proto3 JSON', () => { 83 | const deserialized = fromProto3JSON(MessageWithWrappers, json); 84 | assert.deepStrictEqual(deserialized, message); 85 | }); 86 | 87 | it('serializes nulls to proto3 JSON', () => { 88 | const serialized = toProto3JSON(messageWithNulls); 89 | assert.deepStrictEqual(serialized, jsonWithNulls); 90 | }); 91 | 92 | it('deserializes nulls from proto3 JSON', () => { 93 | const deserialized = fromProto3JSON(MessageWithWrappers, jsonWithNulls); 94 | assert.deepStrictEqual(deserialized, messageWithNulls); 95 | }); 96 | } 97 | 98 | testTwoTypesOfLoad('wrapper types', testWrapperTypes); 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **_THIS REPOSITORY IS DEPRECATED. ALL OF ITS CONTENT AND HISTORY HAS BEEN MOVED TO [GOOGLE-CLOUD-NODE-CORE](https://github.com/googleapis/google-cloud-node-core/tree/main/packages/proto3-json-serializer-nodejs)_** 2 | 3 | 4 | # proto3 JSON serializer for TypeScript / JavaScript 5 | 6 | This library implements proto3 JSON serialization and deserialization for 7 | [protobuf.js](https://www.npmjs.com/package/protobufjs) protobuf objects 8 | according to the [spec](https://developers.google.com/protocol-buffers/docs/proto3#json). 9 | 10 | Note that the spec requires special representation of some `google.protobuf.*` types 11 | (`Value`, `Struct`, `Timestamp`, `Duration`, etc.), so you cannot just use `.toObject()` 12 | since the result won't be understood by protobuf in other languages. Hence this module. 13 | 14 | JavaScript: 15 | 16 | ```js 17 | const serializer = require('proto3-json-serializer'); 18 | ``` 19 | 20 | TypeScript: 21 | 22 | ```ts 23 | import * as serializer from 'proto3-json-serializer'; 24 | ``` 25 | 26 | ## Serialization: protobuf.js object to proto3 JSON 27 | 28 | ```js 29 | const root = protobuf.loadSync('test.proto'); 30 | const Type = root.lookupType('test.Message'); 31 | const message = Type.fromObject({...}); 32 | 33 | const serialized = serializer.toProto3JSON(message); 34 | ``` 35 | 36 | Serialization works with any object created by calling `.create()`, `.decode()`, or `.fromObject()` 37 | for a loaded protobuf type. It relies on the `$type` field so it will not work with a static object. 38 | 39 | ## Deserialization: proto3 JSON to protobuf.js object 40 | 41 | To deserialize an object from proto3 JSON, we must know its type (as returned by `root.lookupType('...')`). 42 | Pass this type as the first parameter to `.fromProto3JSON`: 43 | 44 | ```js 45 | const root = protobuf.loadSync('test.proto'); 46 | const Type = root.lookupType('test.Message'); 47 | const json = {...}; 48 | 49 | const deserialized = serializer.fromProto3JSON(Type, json); 50 | ``` 51 | 52 | ## Complete example 53 | ```js 54 | const assert = require('assert'); 55 | const path = require('path'); 56 | const protobuf = require('protobufjs'); 57 | const serializer = require('proto3-json-serializer'); 58 | 59 | // We'll take sample protos from google-proto-files but the code will work with any protos 60 | const protos = require('google-proto-files'); 61 | 62 | // Load some proto file 63 | const rpcProtos = protos.getProtoPath('rpc'); 64 | const root = protobuf.loadSync([ 65 | path.join(rpcProtos, 'status.proto'), 66 | path.join(rpcProtos, 'error_details.proto'), 67 | ]); 68 | const Status = root.lookupType('google.rpc.Status'); 69 | 70 | // If you have a protobuf object that follows proto3 JSON syntax 71 | // https://developers.google.com/protocol-buffers/docs/proto3#json 72 | // (this is an example of google.rpc.Status message in JSON) 73 | const json = { 74 | code: 3, 75 | message: 'Test error message', 76 | details: [ 77 | { 78 | '@type': 'google.rpc.BadRequest', 79 | fieldViolations: [ 80 | { 81 | field: 'field', 82 | description: 'must not be null', 83 | }, 84 | ], 85 | }, 86 | ], 87 | }; 88 | 89 | // You can deserialize it into a protobuf.js object: 90 | const deserialized = serializer.fromProto3JSON(Status, json); 91 | console.log(deserialized); 92 | 93 | // And serialize it back 94 | const serialized = serializer.toProto3JSON(deserialized); 95 | assert.deepStrictEqual(serialized, json); 96 | ``` 97 | 98 | ## Disclaimer 99 | 100 | This is not an officially supported Google project. 101 | -------------------------------------------------------------------------------- /typescript/test/unit/map.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testMap(root: protobuf.Root) { 23 | const MessageWithMap = root.lookupType('test.MessageWithMap'); 24 | const message = MessageWithMap.fromObject({ 25 | mapField: { 26 | key1: { 27 | stringField: 'value1', 28 | }, 29 | key2: { 30 | stringField: 'value2', 31 | }, 32 | }, 33 | stringMapField: { 34 | key1: 'string value 1', 35 | key2: 'string value 2', 36 | }, 37 | longMapField: { 38 | '2^63-1': '9223372036854775807', 39 | one: '1', 40 | 'minus one': '-1', 41 | zero: '0', 42 | }, 43 | }); 44 | const json = { 45 | mapField: { 46 | key1: { 47 | stringField: 'value1', 48 | }, 49 | key2: { 50 | stringField: 'value2', 51 | }, 52 | }, 53 | stringMapField: { 54 | key1: 'string value 1', 55 | key2: 'string value 2', 56 | }, 57 | longMapField: { 58 | '2^63-1': '9223372036854775807', 59 | one: '1', 60 | 'minus one': '-1', 61 | zero: '0', 62 | }, 63 | enumMapField: {}, 64 | }; 65 | 66 | it('serializes to proto3 JSON', () => { 67 | const serialized = toProto3JSON(message); 68 | assert.deepStrictEqual(serialized, json); 69 | }); 70 | 71 | it('deserializes from proto3 JSON', () => { 72 | const deserialized = fromProto3JSON(MessageWithMap, json); 73 | assert.deepStrictEqual(deserialized, message); 74 | }); 75 | } 76 | 77 | function testEnumMap(root: protobuf.Root) { 78 | const MessageWithMap = root.lookupType('test.MessageWithMap'); 79 | const message = MessageWithMap.fromObject({ 80 | enumMapField: { 81 | key1: 'UNKNOWN', 82 | key2: 'KNOWN', 83 | }, 84 | }); 85 | const jsonWithStringEnums = { 86 | mapField: {}, 87 | stringMapField: {}, 88 | longMapField: {}, 89 | enumMapField: { 90 | key1: 'UNKNOWN', 91 | key2: 'KNOWN', 92 | }, 93 | }; 94 | const jsonWithNumericEnums = { 95 | mapField: {}, 96 | stringMapField: {}, 97 | longMapField: {}, 98 | enumMapField: { 99 | key1: 0, 100 | key2: 1, 101 | }, 102 | }; 103 | 104 | it('serializes to proto3 JSON with string enums', () => { 105 | const serialized = toProto3JSON(message, {numericEnums: false}); 106 | assert.deepStrictEqual(serialized, jsonWithStringEnums); 107 | }); 108 | 109 | it('serializes to proto3 JSON with numeric enums', () => { 110 | const serialized = toProto3JSON(message, {numericEnums: true}); 111 | assert.deepStrictEqual(serialized, jsonWithNumericEnums); 112 | }); 113 | 114 | it('deserializes from proto3 JSON with string enums', () => { 115 | const deserialized = fromProto3JSON(MessageWithMap, jsonWithStringEnums); 116 | assert.deepStrictEqual(deserialized, message); 117 | }); 118 | 119 | it('deserializes from proto3 JSON with numeric enums', () => { 120 | const deserialized = fromProto3JSON(MessageWithMap, jsonWithNumericEnums); 121 | assert.deepStrictEqual(deserialized, message); 122 | }); 123 | } 124 | 125 | testTwoTypesOfLoad('map fields', testMap); 126 | testTwoTypesOfLoad('enum map fields', testEnumMap); 127 | -------------------------------------------------------------------------------- /test-fixtures/proto/test.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | import "google/protobuf/any.proto"; 18 | import "google/protobuf/duration.proto"; 19 | import "google/protobuf/empty.proto"; 20 | import "google/protobuf/field_mask.proto"; 21 | import "google/protobuf/struct.proto"; 22 | import "google/protobuf/timestamp.proto"; 23 | import "google/protobuf/wrappers.proto"; 24 | 25 | package test; 26 | 27 | message PrimitiveTypes { 28 | int32 integer_field = 1; 29 | uint32 unsigned_integer_field = 2; 30 | fixed32 fixed_integer_field = 3; 31 | string string_field = 4; 32 | bool bool_field = 5; 33 | int64 int64_field = 6; 34 | uint64 uint64_field = 7; 35 | double double_field = 8; 36 | } 37 | 38 | message MessageWithNestedMessage { 39 | AnyContent inner_message = 1; 40 | } 41 | 42 | message MessageWithAny { 43 | google.protobuf.Any any_field = 3; 44 | } 45 | 46 | message AnyContent { 47 | string string_field = 1; 48 | } 49 | 50 | message MessageWithBytesField { 51 | bytes bytes_field = 3; 52 | } 53 | 54 | message MessageWithMap { 55 | map map_field = 3; 56 | map string_map_field = 4; 57 | map long_map_field = 5; 58 | map enum_map_field = 6; 59 | } 60 | 61 | message MapValue { 62 | string string_field = 1; 63 | } 64 | 65 | message MessageWithRepeated { 66 | repeated string repeated_string = 3; 67 | repeated RepeatedValue repeated_message = 4; 68 | repeated string one_more_repeated_string = 5; 69 | repeated int64 repeated_long = 6; 70 | repeated Enum repeated_enum = 7; 71 | } 72 | 73 | message RepeatedValue { 74 | string string_field = 1; 75 | } 76 | 77 | enum Enum { 78 | UNKNOWN = 0; 79 | KNOWN = 1; 80 | } 81 | 82 | message MessageWithEnum { 83 | Enum enum_field = 3; 84 | } 85 | 86 | message MessageWithOneof { 87 | oneof _oneof_field { 88 | string string_field = 1; 89 | int32 integer_field = 2; 90 | AnyContent message_field = 3; 91 | } 92 | } 93 | 94 | message MessageWithValue { 95 | google.protobuf.Value value_field = 1; 96 | } 97 | 98 | message MessageWithStruct { 99 | google.protobuf.Struct struct_field = 1; 100 | } 101 | 102 | message MessageWithListValue { 103 | google.protobuf.ListValue list_value_field = 1; 104 | } 105 | 106 | message MessageWithNullValue { 107 | google.protobuf.NullValue null_value_field = 1; 108 | } 109 | 110 | message MessageWithDuration { 111 | google.protobuf.Duration duration_field = 1; 112 | } 113 | 114 | message MessageWithTimestamp { 115 | google.protobuf.Timestamp timestamp_field = 1; 116 | } 117 | 118 | message MessageWithEmpty { 119 | google.protobuf.Empty empty_field = 1; 120 | } 121 | 122 | message MessageWithWrappers { 123 | google.protobuf.DoubleValue double_value_field = 1; 124 | google.protobuf.FloatValue float_value_field = 2; 125 | google.protobuf.Int64Value int64_value_field = 3; 126 | google.protobuf.UInt64Value uint64_value_field = 4; 127 | google.protobuf.Int32Value int32_value_field = 5; 128 | google.protobuf.UInt32Value uint32_value_field = 6; 129 | google.protobuf.BoolValue bool_value_field = 7; 130 | google.protobuf.StringValue string_value_field = 8; 131 | google.protobuf.BytesValue bytes_value_field = 9; 132 | google.protobuf.DoubleValue nan_value_field = 10; 133 | google.protobuf.DoubleValue infinity_value_field = 11; 134 | google.protobuf.DoubleValue negative_infinity_value_field = 12; 135 | } 136 | 137 | message MessageWithFieldMask { 138 | google.protobuf.FieldMask field_mask_field = 1; 139 | } 140 | -------------------------------------------------------------------------------- /.github/scripts/tests/close-or-remove-response-label.test.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const { describe, it, beforeEach, afterEach } = require('mocha'); 18 | const removeResponseLabel = require('../remove-response-label.cjs'); 19 | const closeUnresponsive = require('../close-unresponsive.cjs'); 20 | const sinon = require('sinon'); 21 | 22 | function getISODateDaysAgo(days) { 23 | const today = new Date(); 24 | const daysAgo = new Date(today.setDate(today.getDate() - days)); 25 | return daysAgo.toISOString(); 26 | } 27 | 28 | describe('close issues or remove needs more info labels', () => { 29 | let octokitStub; 30 | let issuesStub; 31 | let paginateStub; 32 | 33 | beforeEach(() => { 34 | issuesStub = { 35 | listForRepo: sinon.stub(), 36 | update: sinon.stub(), 37 | createComment: sinon.stub(), 38 | get: sinon.stub(), 39 | removeLabel: sinon.stub(), 40 | }; 41 | paginateStub = sinon.stub(); 42 | octokitStub = { 43 | rest: { 44 | issues: issuesStub, 45 | }, 46 | paginate: paginateStub, 47 | }; 48 | }); 49 | 50 | afterEach(() => { 51 | sinon.restore(); 52 | }); 53 | 54 | it('closes the issue if the OP has not responded within the allotted time and there is a needs-more-info label', async () => { 55 | const context = { owner: 'testOrg', repo: 'testRepo' }; 56 | const issuesInRepo = [{ user: { login: 'OP' }, labels: [{ name: 'needs more info' }] }]; 57 | const eventsInIssue = [{ event: 'labeled', label: { name: 'needs more info' }, created_at: getISODateDaysAgo(16) }]; 58 | 59 | issuesStub.listForRepo.resolves({ data: issuesInRepo }); 60 | paginateStub.resolves(eventsInIssue); 61 | 62 | await closeUnresponsive({ github: octokitStub, context }); 63 | 64 | sinon.assert.calledOnce(issuesStub.listForRepo); 65 | sinon.assert.calledOnce(paginateStub); 66 | sinon.assert.calledOnce(issuesStub.update); 67 | sinon.assert.calledOnce(issuesStub.createComment); 68 | }); 69 | 70 | it('does nothing if not enough time has passed and there is a needs-more-info label', async () => { 71 | const context = { owner: 'testOrg', repo: 'testRepo' }; 72 | const issuesInRepo = [{ user: { login: 'OP' }, labels: [{ name: 'needs more info' }] }]; 73 | const eventsInIssue = [{ event: 'labeled', label: { name: 'needs more info' }, created_at: getISODateDaysAgo(14) }]; 74 | 75 | issuesStub.listForRepo.resolves({ data: issuesInRepo }); 76 | paginateStub.resolves(eventsInIssue); 77 | 78 | await closeUnresponsive({ github: octokitStub, context }); 79 | 80 | sinon.assert.calledOnce(issuesStub.listForRepo); 81 | sinon.assert.calledOnce(paginateStub); 82 | sinon.assert.notCalled(issuesStub.update); 83 | sinon.assert.notCalled(issuesStub.createComment); 84 | }); 85 | 86 | it('removes the label if OP responded', async () => { 87 | const context = { actor: 'OP', repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 88 | const issueContext = { user: {login: 'OP'}, labels: [{ name: 'needs more info' }] }; 89 | 90 | issuesStub.get.resolves({ data: issueContext }); 91 | 92 | await removeResponseLabel({ github: octokitStub, context }); 93 | 94 | sinon.assert.calledOnce(issuesStub.get); 95 | sinon.assert.calledOnce(issuesStub.removeLabel); 96 | }); 97 | 98 | it('does not remove the label if author responded', async () => { 99 | const context = { actor: 'repo-maintainer', repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 100 | const issueContext = { user: {login: 'OP'}, labels: [{ name: 'needs more info' }] }; 101 | 102 | issuesStub.get.resolves({ data: issueContext }); 103 | 104 | await removeResponseLabel({ github: octokitStub, context }); 105 | 106 | sinon.assert.calledOnce(issuesStub.get); 107 | sinon.assert.notCalled(issuesStub.removeLabel); 108 | }); 109 | }); -------------------------------------------------------------------------------- /typescript/src/any.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Types that have special JSON representation according to https://developers.google.com/protocol-buffers/docs/proto3#json 16 | // (this set is used for encoding google.protobuf.Any to/from proto3 JSON properly) 17 | 18 | import * as protobuf from 'protobufjs'; 19 | import {fromProto3JSON} from './fromproto3json'; 20 | import {toProto3JSON, ToProto3JSONOptions} from './toproto3json'; 21 | import {JSONObject, JSONValue} from './types'; 22 | 23 | // https://github.com/protocolbuffers/protobuf/blob/ba3836703b4a9e98e474aea2bac8c5b49b6d3b5c/python/google/protobuf/json_format.py#L850 24 | const specialJSON = new Set([ 25 | 'google.protobuf.Any', 26 | 'google.protobuf.Duration', 27 | 'google.protobuf.FieldMask', 28 | 'google.protobuf.ListValue', 29 | 'google.protobuf.Struct', 30 | 'google.protobuf.Timestamp', 31 | 'google.protobuf.Value', 32 | ]); 33 | 34 | export interface Any { 35 | type_url: string; 36 | value: Buffer | Uint8Array; 37 | } 38 | 39 | export function googleProtobufAnyToProto3JSON( 40 | obj: protobuf.Message & Any, 41 | options?: ToProto3JSONOptions, 42 | ): JSONObject { 43 | // https://developers.google.com/protocol-buffers/docs/proto3#json 44 | // If the Any contains a value that has a special JSON mapping, it will be converted as follows: 45 | // {"@type": xxx, "value": yyy}. 46 | // Otherwise, the value will be converted into a JSON object, and the "@type" field will be inserted 47 | // to indicate the actual data type. 48 | 49 | const typeName = obj.type_url.replace(/^.*\//, ''); 50 | let type: protobuf.Type; 51 | try { 52 | type = obj.$type.root.lookupType(typeName); 53 | } catch (err) { 54 | throw new Error( 55 | `googleProtobufAnyToProto3JSON: cannot find type ${typeName}: ${err}`, 56 | ); 57 | } 58 | const valueMessage = type.decode(obj.value); 59 | const valueProto3JSON = toProto3JSON(valueMessage, options); 60 | if (specialJSON.has(typeName)) { 61 | return { 62 | '@type': obj.type_url, 63 | value: valueProto3JSON, 64 | }; 65 | } 66 | (valueProto3JSON as JSONObject)['@type'] = obj.type_url; 67 | return valueProto3JSON as JSONObject; 68 | } 69 | 70 | export function googleProtobufAnyFromProto3JSON( 71 | root: protobuf.Root, 72 | json: JSONValue, 73 | ) { 74 | // Not all possible JSON values can hold Any, only real objects. 75 | if (json === null || typeof json !== 'object' || Array.isArray(json)) { 76 | throw new Error( 77 | 'googleProtobufAnyFromProto3JSON: must be an object to decode google.protobuf.Any', 78 | ); 79 | } 80 | 81 | const typeUrl = json['@type']; 82 | if (!typeUrl || typeof typeUrl !== 'string') { 83 | throw new Error( 84 | 'googleProtobufAnyFromProto3JSON: JSON serialization of google.protobuf.Any must contain @type field', 85 | ); 86 | } 87 | 88 | const typeName = typeUrl.replace(/^.*\//, ''); 89 | let type: protobuf.Type; 90 | try { 91 | type = root.lookupType(typeName); 92 | } catch (err) { 93 | throw new Error( 94 | `googleProtobufAnyFromProto3JSON: cannot find type ${typeName}: ${err}`, 95 | ); 96 | } 97 | 98 | let value: JSONValue = json; 99 | if (specialJSON.has(typeName)) { 100 | if (!('value' in json)) { 101 | throw new Error( 102 | `googleProtobufAnyFromProto3JSON: JSON representation of google.protobuf.Any with type ${typeName} must contain the value field`, 103 | ); 104 | } 105 | value = json.value; 106 | } 107 | 108 | const valueMessage = fromProto3JSON(type, value); 109 | if (valueMessage === null) { 110 | return { 111 | type_url: typeUrl, 112 | value: null, 113 | }; 114 | } 115 | 116 | const uint8array = type.encode(valueMessage).finish(); 117 | const buffer = Buffer.from(uint8array, 0, uint8array.byteLength); 118 | const base64 = buffer.toString('base64'); 119 | 120 | return { 121 | type_url: typeUrl, 122 | value: base64, 123 | }; 124 | } 125 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | labels: 4 | - bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **PLEASE READ**: If you have a support contract with Google, please 10 | create an issue in the [support 11 | console](https://cloud.google.com/support/) instead of filing on GitHub. 12 | This will ensure a timely response. Otherwise, please make sure to 13 | follow the steps below. 14 | - type: checkboxes 15 | attributes: 16 | label: Please make sure you have searched for information in the following 17 | guides. 18 | options: 19 | - label: "Search the issues already opened: 20 | https://github.com/GoogleCloudPlatform/google-cloud-node/issues" 21 | required: true 22 | - label: "Search StackOverflow: 23 | http://stackoverflow.com/questions/tagged/google-cloud-platform+nod\ 24 | e.js" 25 | required: true 26 | - label: "Check our Troubleshooting guide: 27 | https://github.com/googleapis/google-cloud-node/blob/main/docs/trou\ 28 | bleshooting.md" 29 | required: true 30 | - label: "Check our FAQ: 31 | https://github.com/googleapis/google-cloud-node/blob/main/docs/faq.\ 32 | md" 33 | required: true 34 | - label: "Check our libraries HOW-TO: 35 | https://github.com/googleapis/gax-nodejs/blob/main/client-libraries\ 36 | .md" 37 | required: true 38 | - label: "Check out our authentication guide: 39 | https://github.com/googleapis/google-auth-library-nodejs" 40 | required: true 41 | - label: "Check out handwritten samples for many of our APIs: 42 | https://github.com/GoogleCloudPlatform/nodejs-docs-samples" 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: > 47 | A screenshot that you have tested with "Try this API". 48 | description: > 49 | As our client libraries are mostly autogenerated, we kindly request 50 | that you test whether your issue is with the client library, or with the 51 | API itself. To do so, please search for your API 52 | here: https://developers.google.com/apis-explorer and attempt to 53 | reproduce the issue in the given method. Please include a screenshot of 54 | the response in "Try this API". This response should NOT match the current 55 | behavior you are experiencing. If the behavior is the same, it means 56 | that you are likely experiencing a bug with the API itself. In that 57 | case, please submit an issue to the API team, either by submitting an 58 | issue in its issue tracker (https://cloud.google.com/support/docs/issue-trackers), or by 59 | submitting an issue in its linked tracker in the .repo-metadata.json 60 | file https://github.com/googleapis/proto3-json-serializer-nodejs/issues/ 61 | validations: 62 | required: true 63 | - type: input 64 | attributes: 65 | label: > 66 | Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal 67 | reproduction. 68 | description: > 69 | **Skipping this or providing an invalid link will result in the issue being closed** 70 | validations: 71 | required: true 72 | - type: textarea 73 | attributes: 74 | label: > 75 | A step-by-step description of how to reproduce the issue, based on 76 | the linked reproduction. 77 | description: > 78 | Screenshots can be provided in the issue body below. 79 | placeholder: | 80 | 1. Start the application in development (next dev) 81 | 2. Click X 82 | 3. Y will happen 83 | validations: 84 | required: true 85 | - type: textarea 86 | attributes: 87 | label: A clear and concise description of what the bug is, and what you 88 | expected to happen. 89 | placeholder: Following the steps from the previous section, I expected A to 90 | happen, but I observed B instead 91 | validations: 92 | required: true 93 | 94 | - type: textarea 95 | attributes: 96 | label: A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 97 | placeholder: 'Documentation here(link) states that B should happen instead of A' 98 | validations: 99 | required: true 100 | -------------------------------------------------------------------------------- /typescript/test/unit/primitive.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testPrimitiveTypes(root: protobuf.Root) { 23 | const PrimitiveTypes = root.lookupType('test.PrimitiveTypes'); 24 | const message = PrimitiveTypes.fromObject({ 25 | integerField: -42, 26 | unsignedIntegerField: 42, 27 | fixedIntegerField: 128, 28 | stringField: 'test', 29 | boolField: true, 30 | int64Field: -43, 31 | uint64Field: 43, 32 | }); 33 | const json = { 34 | integerField: -42, 35 | unsignedIntegerField: 42, 36 | fixedIntegerField: 128, 37 | stringField: 'test', 38 | boolField: true, 39 | int64Field: '-43', 40 | uint64Field: '43', 41 | }; 42 | 43 | it('serializes to proto3 JSON', () => { 44 | const serialized = toProto3JSON(message); 45 | assert.deepStrictEqual(serialized, json); 46 | }); 47 | 48 | it('deserializes from proto3 JSON', () => { 49 | const deserialized = fromProto3JSON(PrimitiveTypes, json); 50 | assert.deepStrictEqual(deserialized, message); 51 | }); 52 | } 53 | 54 | function testLongIntegers(root: protobuf.Root) { 55 | const PrimitiveTypes = root.lookupType('test.PrimitiveTypes'); 56 | const message = PrimitiveTypes.fromObject({ 57 | int64Field: '-5011754511478056813', 58 | uint64Field: '5011754511478056813', 59 | }); 60 | const json = { 61 | int64Field: '-5011754511478056813', 62 | uint64Field: '5011754511478056813', 63 | }; 64 | 65 | it('serializes uint64 to proto3 JSON', () => { 66 | const serialized = toProto3JSON(message); 67 | assert.deepStrictEqual(serialized, json); 68 | }); 69 | 70 | it('deserializes uint64 from proto3 JSON', () => { 71 | const deserialized = fromProto3JSON(PrimitiveTypes, json); 72 | assert.deepStrictEqual(deserialized, message); 73 | }); 74 | } 75 | 76 | function testNotFiniteNumber(root: protobuf.Root) { 77 | const PrimitiveTypes = root.lookupType('test.PrimitiveTypes'); 78 | const messageNaN = PrimitiveTypes.fromObject({ 79 | doubleField: NaN, 80 | }); 81 | const jsonNaN = { 82 | doubleField: 'NaN', 83 | }; 84 | const messageInfinity = PrimitiveTypes.fromObject({ 85 | doubleField: Infinity, 86 | }); 87 | const jsonInfinity = { 88 | doubleField: 'Infinity', 89 | }; 90 | const messageNegInfinity = PrimitiveTypes.fromObject({ 91 | doubleField: Infinity, 92 | }); 93 | const jsonNegInfinity = { 94 | doubleField: 'Infinity', 95 | }; 96 | it('serializes NaN to proto3 JSON', () => { 97 | const serialized = toProto3JSON(messageNaN); 98 | assert.deepStrictEqual(serialized, jsonNaN); 99 | }); 100 | it('deserializes NaN from proto3 JSON', () => { 101 | const deserialized = fromProto3JSON(PrimitiveTypes, jsonNaN); 102 | assert.deepStrictEqual(deserialized, messageNaN); 103 | }); 104 | it('serializes Infinity to proto3 JSON', () => { 105 | const serialized = toProto3JSON(messageInfinity); 106 | assert.deepStrictEqual(serialized, jsonInfinity); 107 | }); 108 | it('deserializes Infinity from proto3 JSON', () => { 109 | const deserialized = fromProto3JSON(PrimitiveTypes, jsonInfinity); 110 | assert.deepStrictEqual(deserialized, messageInfinity); 111 | }); 112 | it('serializes negative Infinity to proto3 JSON', () => { 113 | const serialized = toProto3JSON(messageNegInfinity); 114 | assert.deepStrictEqual(serialized, jsonNegInfinity); 115 | }); 116 | it('deserializes negative Infinity from proto3 JSON', () => { 117 | const deserialized = fromProto3JSON(PrimitiveTypes, jsonNegInfinity); 118 | assert.deepStrictEqual(deserialized, messageNegInfinity); 119 | }); 120 | } 121 | 122 | testTwoTypesOfLoad('primitive types', [ 123 | testPrimitiveTypes, 124 | testLongIntegers, 125 | testNotFiniteNumber, 126 | ]); 127 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to making participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, gender identity and expression, level of 10 | experience, education, socio-economic status, nationality, personal appearance, 11 | race, religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or reject 42 | comments, commits, code, wiki edits, issues, and other contributions that are 43 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 44 | contributor for other behaviors that they deem inappropriate, threatening, 45 | offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies both within project spaces and in public spaces 50 | when an individual is representing the project or its community. Examples of 51 | representing a project or community include using an official project e-mail 52 | address, posting via an official social media account, or acting as an appointed 53 | representative at an online or offline event. Representation of a project may be 54 | further defined and clarified by project maintainers. 55 | 56 | This Code of Conduct also applies outside the project spaces when the Project 57 | Steward has a reasonable belief that an individual's behavior may have a 58 | negative impact on the project or its community. 59 | 60 | ## Conflict Resolution 61 | 62 | We do not believe that all conflict is bad; healthy debate and disagreement 63 | often yield positive results. However, it is never okay to be disrespectful or 64 | to engage in behavior that violates the project’s code of conduct. 65 | 66 | If you see someone violating the code of conduct, you are encouraged to address 67 | the behavior directly with those involved. Many issues can be resolved quickly 68 | and easily, and this gives people more control over the outcome of their 69 | dispute. If you are unable to resolve the matter for any reason, or if the 70 | behavior is threatening or harassing, report it. We are dedicated to providing 71 | an environment where participants feel welcome and safe. 72 | 73 | Reports should be directed to *googleapis-stewards@google.com*, the 74 | Project Steward(s) for *Google Cloud Client Libraries*. It is the Project Steward’s duty to 75 | receive and address reported violations of the code of conduct. They will then 76 | work with a committee consisting of representatives from the Open Source 77 | Programs Office and the Google Open Source Strategy team. If for any reason you 78 | are uncomfortable reaching out to the Project Steward, please email 79 | opensource@google.com. 80 | 81 | We will investigate every complaint, but you may not receive a direct response. 82 | We will use our discretion in determining when and how to follow up on reported 83 | incidents, which may range from not taking action to permanent expulsion from 84 | the project and project-sponsored spaces. We will notify the accused of the 85 | report and provide them an opportunity to discuss it before any action is taken. 86 | The identity of the reporter will be omitted from the details of the report 87 | supplied to the accused. In potentially harmful situations, such as ongoing 88 | harassment or threats to anyone's safety, we may take action without notice. 89 | 90 | ## Attribution 91 | 92 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 93 | available at 94 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html -------------------------------------------------------------------------------- /typescript/src/value.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {assert} from './util'; 16 | import {FromObjectValue, JSONObject, JSONValue} from './types'; 17 | 18 | export interface Struct { 19 | fields: { 20 | [key: string]: Value; 21 | }; 22 | } 23 | 24 | export interface ListValue { 25 | values: Array; 26 | } 27 | 28 | export interface Value { 29 | nullValue?: 0; 30 | numberValue?: number; 31 | stringValue?: string; 32 | boolValue?: boolean; 33 | listValue?: ListValue; 34 | structValue?: Struct; 35 | } 36 | 37 | export function googleProtobufStructToProto3JSON( 38 | obj: protobuf.Message & Struct, 39 | ) { 40 | const result: JSONObject = {}; 41 | const fields = obj.fields; 42 | for (const [key, value] of Object.entries(fields)) { 43 | result[key] = googleProtobufValueToProto3JSON( 44 | value as protobuf.Message & Value, 45 | ); 46 | } 47 | return result; 48 | } 49 | 50 | export function googleProtobufListValueToProto3JSON( 51 | obj: protobuf.Message & ListValue, 52 | ) { 53 | assert( 54 | Array.isArray(obj.values), 55 | 'ListValue internal representation must contain array of values', 56 | ); 57 | return (obj.values as Array).map( 58 | googleProtobufValueToProto3JSON, 59 | ); 60 | } 61 | 62 | export function googleProtobufValueToProto3JSON( 63 | obj: protobuf.Message & Value, 64 | ): JSONValue { 65 | if (Object.prototype.hasOwnProperty.call(obj, 'nullValue')) { 66 | return null; 67 | } 68 | 69 | if ( 70 | Object.prototype.hasOwnProperty.call(obj, 'numberValue') && 71 | typeof obj.numberValue === 'number' 72 | ) { 73 | if (!Number.isFinite(obj.numberValue)) { 74 | return obj.numberValue.toString(); 75 | } 76 | return obj.numberValue; 77 | } 78 | 79 | if ( 80 | Object.prototype.hasOwnProperty.call(obj, 'stringValue') && 81 | typeof obj.stringValue === 'string' 82 | ) { 83 | return obj.stringValue; 84 | } 85 | 86 | if ( 87 | Object.prototype.hasOwnProperty.call(obj, 'boolValue') && 88 | typeof obj.boolValue === 'boolean' 89 | ) { 90 | return obj.boolValue; 91 | } 92 | 93 | if ( 94 | Object.prototype.hasOwnProperty.call(obj, 'structValue') && 95 | typeof obj.structValue === 'object' 96 | ) { 97 | return googleProtobufStructToProto3JSON( 98 | obj.structValue as protobuf.Message & Struct, 99 | ); 100 | } 101 | 102 | if ( 103 | Object.prototype.hasOwnProperty.call(obj, 'listValue') && 104 | typeof obj === 'object' && 105 | typeof obj.listValue === 'object' 106 | ) { 107 | return googleProtobufListValueToProto3JSON( 108 | obj.listValue as protobuf.Message & ListValue, 109 | ); 110 | } 111 | 112 | // Assuming empty Value to be null 113 | return null; 114 | } 115 | 116 | export function googleProtobufStructFromProto3JSON( 117 | json: JSONObject, 118 | ): FromObjectValue { 119 | const fields: FromObjectValue = {}; 120 | for (const [key, value] of Object.entries(json)) { 121 | fields[key] = googleProtobufValueFromProto3JSON(value); 122 | } 123 | return {fields}; 124 | } 125 | 126 | export function googleProtobufListValueFromProto3JSON( 127 | json: JSONValue[], 128 | ): FromObjectValue { 129 | return { 130 | values: json.map(element => googleProtobufValueFromProto3JSON(element)), 131 | }; 132 | } 133 | 134 | export function googleProtobufValueFromProto3JSON( 135 | json: JSONValue, 136 | ): FromObjectValue { 137 | if (json === null) { 138 | return {nullValue: 'NULL_VALUE'}; 139 | } 140 | 141 | if (typeof json === 'number') { 142 | return {numberValue: json}; 143 | } 144 | 145 | if (typeof json === 'string') { 146 | return {stringValue: json}; 147 | } 148 | 149 | if (typeof json === 'boolean') { 150 | return {boolValue: json}; 151 | } 152 | 153 | if (Array.isArray(json)) { 154 | return { 155 | listValue: googleProtobufListValueFromProto3JSON(json), 156 | }; 157 | } 158 | 159 | if (typeof json === 'object') { 160 | return { 161 | structValue: googleProtobufStructFromProto3JSON(json), 162 | }; 163 | } 164 | 165 | throw new Error( 166 | `googleProtobufValueFromProto3JSON: incorrect parameter type: ${typeof json}`, 167 | ); 168 | } 169 | -------------------------------------------------------------------------------- /typescript/test/unit/repeated.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testRepeated(root: protobuf.Root) { 23 | const MessageWithRepeated = root.lookupType('test.MessageWithRepeated'); 24 | const message = MessageWithRepeated.fromObject({ 25 | repeatedString: ['value1', 'value2', 'value3'], 26 | repeatedMessage: [ 27 | { 28 | stringField: 'value1', 29 | }, 30 | { 31 | stringField: 'value2', 32 | }, 33 | ], 34 | oneMoreRepeatedString: [], 35 | repeatedLong: ['9223372036854775807', '1', '-1', '0'], 36 | }); 37 | const jsonWithNull = { 38 | repeatedString: ['value1', 'value2', 'value3'], 39 | repeatedMessage: [ 40 | { 41 | stringField: 'value1', 42 | }, 43 | { 44 | stringField: 'value2', 45 | }, 46 | ], 47 | oneMoreRepeatedString: null, 48 | repeatedLong: ['9223372036854775807', '1', '-1', '0'], 49 | }; 50 | const jsonWithEmptyArray = { 51 | repeatedString: ['value1', 'value2', 'value3'], 52 | repeatedMessage: [ 53 | { 54 | stringField: 'value1', 55 | }, 56 | { 57 | stringField: 'value2', 58 | }, 59 | ], 60 | oneMoreRepeatedString: null, 61 | repeatedLong: ['9223372036854775807', '1', '-1', '0'], 62 | }; 63 | const jsonWithoutEmptyArrays = { 64 | repeatedString: ['value1', 'value2', 'value3'], 65 | repeatedMessage: [ 66 | { 67 | stringField: 'value1', 68 | }, 69 | { 70 | stringField: 'value2', 71 | }, 72 | ], 73 | repeatedLong: ['9223372036854775807', '1', '-1', '0'], 74 | }; 75 | 76 | it('serializes to proto3 JSON', () => { 77 | const serialized = toProto3JSON(message); 78 | assert.deepStrictEqual(serialized, jsonWithoutEmptyArrays); 79 | }); 80 | 81 | it('deserializes from proto3 JSON with null', () => { 82 | const deserialized = fromProto3JSON(MessageWithRepeated, jsonWithNull); 83 | assert.deepStrictEqual(deserialized, message); 84 | }); 85 | 86 | it('deserializes from proto3 JSON with an empty array', () => { 87 | const deserialized = fromProto3JSON( 88 | MessageWithRepeated, 89 | jsonWithEmptyArray, 90 | ); 91 | assert.deepStrictEqual(deserialized, message); 92 | }); 93 | 94 | it('deserializes from proto3 JSON with an omitted array field', () => { 95 | const deserialized = fromProto3JSON( 96 | MessageWithRepeated, 97 | jsonWithoutEmptyArrays, 98 | ); 99 | assert.deepStrictEqual(deserialized, message); 100 | }); 101 | } 102 | 103 | function testEmptyRepeated(root: protobuf.Root) { 104 | const MessageWithRepeated = root.lookupType('test.MessageWithRepeated'); 105 | const message = MessageWithRepeated.fromObject({ 106 | repeatedString: [], 107 | repeatedMessage: [], 108 | oneMoreRepeatedString: [], 109 | repeatedEnum: [], 110 | }); 111 | const json = {}; 112 | 113 | it('serializes to proto3 JSON', () => { 114 | const serialized = toProto3JSON(message); 115 | assert.deepStrictEqual(serialized, json); 116 | }); 117 | 118 | it('deserializes from proto3 JSON', () => { 119 | const deserialized = fromProto3JSON(MessageWithRepeated, json); 120 | assert.deepStrictEqual(deserialized, message); 121 | }); 122 | } 123 | 124 | function testRepeatedEnum(root: protobuf.Root) { 125 | const MessageWithRepeated = root.lookupType('test.MessageWithRepeated'); 126 | const message = MessageWithRepeated.fromObject({ 127 | repeatedEnum: ['UNKNOWN', 'KNOWN'], 128 | }); 129 | const jsonWithNumericEnums = { 130 | repeatedEnum: [0, 1], 131 | }; 132 | const jsonWithStringEnums = { 133 | repeatedEnum: ['UNKNOWN', 'KNOWN'], 134 | }; 135 | 136 | it('serializes to proto3 JSON with numeric enums', () => { 137 | const serialized = toProto3JSON(message, {numericEnums: true}); 138 | assert.deepStrictEqual(serialized, jsonWithNumericEnums); 139 | }); 140 | 141 | it('serializes to proto3 JSON with string enums', () => { 142 | const serialized = toProto3JSON(message, {numericEnums: false}); 143 | assert.deepStrictEqual(serialized, jsonWithStringEnums); 144 | }); 145 | 146 | it('deserializes from proto3 JSON with numeric enums', () => { 147 | const deserialized = fromProto3JSON( 148 | MessageWithRepeated, 149 | jsonWithNumericEnums, 150 | ); 151 | assert.deepStrictEqual(deserialized, message); 152 | }); 153 | 154 | it('deserializes from proto3 JSON with string enums', () => { 155 | const deserialized = fromProto3JSON( 156 | MessageWithRepeated, 157 | jsonWithStringEnums, 158 | ); 159 | assert.deepStrictEqual(deserialized, message); 160 | }); 161 | } 162 | 163 | testTwoTypesOfLoad('repeated fields', testRepeated); 164 | testTwoTypesOfLoad('empty repeated fields', testEmptyRepeated); 165 | testTwoTypesOfLoad('repeated enums', testRepeatedEnum); 166 | -------------------------------------------------------------------------------- /typescript/src/toproto3json.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | import {JSONObject, JSONValue, LongStub} from './types'; 17 | import {Any, googleProtobufAnyToProto3JSON} from './any'; 18 | import {bytesToProto3JSON} from './bytes'; 19 | import {getFullyQualifiedTypeName, wrapperTypes} from './util'; 20 | import {resolveEnumValueToNumber, resolveEnumValueToString} from './enum'; 21 | import { 22 | googleProtobufListValueToProto3JSON, 23 | googleProtobufStructToProto3JSON, 24 | googleProtobufValueToProto3JSON, 25 | ListValue, 26 | Struct, 27 | Value, 28 | } from './value'; 29 | import {Duration, googleProtobufDurationToProto3JSON} from './duration'; 30 | import {googleProtobufTimestampToProto3JSON, Timestamp} from './timestamp'; 31 | import { 32 | BoolValue, 33 | BytesValue, 34 | NumberValue, 35 | StringValue, 36 | wrapperToProto3JSON, 37 | } from './wrappers'; 38 | import {FieldMask, googleProtobufFieldMaskToProto3JSON} from './fieldmask'; 39 | 40 | export interface ToProto3JSONOptions { 41 | numericEnums: boolean; 42 | } 43 | 44 | // Convert a single value, which might happen to be an instance of Long, to JSONValue 45 | function convertSingleValue(value: JSONValue | object): JSONValue { 46 | if (typeof value === 'object') { 47 | if (value?.constructor?.name === 'Long') { 48 | return (value as LongStub).toString(); 49 | } 50 | throw new Error(`toProto3JSON: don't know how to convert value ${value}`); 51 | } 52 | return value; 53 | } 54 | 55 | // Convert a value within a repeated or map field 56 | function convertRepeatedOrMapValue( 57 | type: protobuf.Type | protobuf.Enum | null, 58 | value: protobuf.Message | JSONValue | null, 59 | options?: ToProto3JSONOptions, 60 | ): JSONValue { 61 | if (type && 'values' in type) { 62 | return convertEnum(type, value as JSONValue, options); 63 | } 64 | if (type) { 65 | return toProto3JSON(value as protobuf.Message, options); 66 | } 67 | return convertSingleValue(value); 68 | } 69 | 70 | // Convert an enum type to its value 71 | function convertEnum( 72 | type: protobuf.Enum, 73 | value: JSONValue, 74 | options?: ToProto3JSONOptions, 75 | ): JSONValue { 76 | if (options?.numericEnums) { 77 | return resolveEnumValueToNumber(type, value as JSONValue); 78 | } else { 79 | return resolveEnumValueToString(type, value as JSONValue); 80 | } 81 | } 82 | 83 | export function toProto3JSON( 84 | obj: protobuf.Message, 85 | options?: ToProto3JSONOptions, 86 | ): JSONValue { 87 | const objType = obj.$type; 88 | if (!objType) { 89 | throw new Error( 90 | 'Cannot serialize object to proto3 JSON since its .$type is unknown. Use Type.fromObject(obj) before calling toProto3JSON.', 91 | ); 92 | } 93 | 94 | objType.resolveAll(); 95 | const typeName = getFullyQualifiedTypeName(objType); 96 | 97 | // Types that require special handling according to 98 | // https://developers.google.com/protocol-buffers/docs/proto3#json 99 | if (typeName === '.google.protobuf.Any') { 100 | return googleProtobufAnyToProto3JSON( 101 | obj as protobuf.Message & Any, 102 | options, 103 | ); 104 | } 105 | 106 | if (typeName === '.google.protobuf.Value') { 107 | return googleProtobufValueToProto3JSON(obj as protobuf.Message & Value); 108 | } 109 | 110 | if (typeName === '.google.protobuf.Struct') { 111 | return googleProtobufStructToProto3JSON(obj as protobuf.Message & Struct); 112 | } 113 | 114 | if (typeName === '.google.protobuf.ListValue') { 115 | return googleProtobufListValueToProto3JSON( 116 | obj as protobuf.Message & ListValue, 117 | ); 118 | } 119 | 120 | if (typeName === '.google.protobuf.Duration') { 121 | return googleProtobufDurationToProto3JSON( 122 | obj as protobuf.Message & Duration, 123 | ); 124 | } 125 | 126 | if (typeName === '.google.protobuf.Timestamp') { 127 | return googleProtobufTimestampToProto3JSON( 128 | obj as protobuf.Message & Timestamp, 129 | ); 130 | } 131 | 132 | if (typeName === '.google.protobuf.FieldMask') { 133 | return googleProtobufFieldMaskToProto3JSON( 134 | obj as protobuf.Message & FieldMask, 135 | ); 136 | } 137 | 138 | if (wrapperTypes.has(typeName)) { 139 | return wrapperToProto3JSON( 140 | obj as protobuf.Message & 141 | (NumberValue | StringValue | BoolValue | BytesValue), 142 | ); 143 | } 144 | 145 | const result: JSONObject = {}; 146 | for (const [key, value] of Object.entries(obj)) { 147 | const field = objType.fields[key]; 148 | const fieldResolvedType = field.resolvedType; 149 | const fieldFullyQualifiedTypeName = fieldResolvedType 150 | ? getFullyQualifiedTypeName(fieldResolvedType) 151 | : null; 152 | if (value === null) { 153 | result[key] = null; 154 | continue; 155 | } 156 | if (Array.isArray(value)) { 157 | if (value.length === 0) { 158 | // ignore repeated fields with no values 159 | continue; 160 | } 161 | result[key] = value.map(element => { 162 | return convertRepeatedOrMapValue(fieldResolvedType, element, options); 163 | }); 164 | continue; 165 | } 166 | if (field.map) { 167 | const map: JSONObject = {}; 168 | for (const [mapKey, mapValue] of Object.entries(value)) { 169 | map[mapKey] = convertRepeatedOrMapValue( 170 | fieldResolvedType, 171 | mapValue as JSONValue, 172 | options, 173 | ); 174 | } 175 | result[key] = map; 176 | continue; 177 | } 178 | if (fieldFullyQualifiedTypeName === '.google.protobuf.NullValue') { 179 | result[key] = null; 180 | continue; 181 | } 182 | if (fieldResolvedType && 'values' in fieldResolvedType && value !== null) { 183 | result[key] = convertEnum(fieldResolvedType, value, options); 184 | continue; 185 | } 186 | if (fieldResolvedType) { 187 | result[key] = toProto3JSON(value, options); 188 | continue; 189 | } 190 | if ( 191 | typeof value === 'string' || 192 | typeof value === 'number' || 193 | typeof value === 'boolean' || 194 | value === null 195 | ) { 196 | if (typeof value === 'number' && !Number.isFinite(value)) { 197 | result[key] = value.toString(); 198 | continue; 199 | } 200 | result[key] = value; 201 | continue; 202 | } 203 | if (Buffer.isBuffer(value) || value instanceof Uint8Array) { 204 | result[key] = bytesToProto3JSON(value); 205 | continue; 206 | } 207 | result[key] = convertSingleValue(value); 208 | continue; 209 | } 210 | return result; 211 | } 212 | -------------------------------------------------------------------------------- /typescript/src/fromproto3json.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | import {googleProtobufAnyFromProto3JSON} from './any'; 17 | import {bytesFromProto3JSON} from './bytes'; 18 | import {resolveEnumValueToString} from './enum'; 19 | import {FromObjectValue, JSONValue} from './types'; 20 | import { 21 | googleProtobufListValueFromProto3JSON, 22 | googleProtobufStructFromProto3JSON, 23 | googleProtobufValueFromProto3JSON, 24 | } from './value'; 25 | import {assert, getFullyQualifiedTypeName, wrapperTypes} from './util'; 26 | import {googleProtobufDurationFromProto3JSON} from './duration'; 27 | import {googleProtobufTimestampFromProto3JSON} from './timestamp'; 28 | import {wrapperFromProto3JSON} from './wrappers'; 29 | import {googleProtobufFieldMaskFromProto3JSON} from './fieldmask'; 30 | 31 | export function fromProto3JSONToInternalRepresentation( 32 | type: protobuf.Type | protobuf.Enum | string, 33 | json: JSONValue, 34 | ): FromObjectValue { 35 | const fullyQualifiedTypeName = 36 | typeof type === 'string' ? type : getFullyQualifiedTypeName(type); 37 | 38 | if (typeof type !== 'string' && 'values' in type) { 39 | // type is an Enum 40 | if (fullyQualifiedTypeName === '.google.protobuf.NullValue') { 41 | return 'NULL_VALUE'; 42 | } 43 | 44 | return resolveEnumValueToString(type, json); 45 | } 46 | 47 | if (typeof type !== 'string') { 48 | type.resolveAll(); 49 | } 50 | 51 | if (typeof type === 'string') { 52 | return json; 53 | } 54 | 55 | // Types that require special handling according to 56 | // https://developers.google.com/protocol-buffers/docs/proto3#json 57 | 58 | // Types that can have meaningful "null" value 59 | if (fullyQualifiedTypeName === '.google.protobuf.Value') { 60 | return googleProtobufValueFromProto3JSON(json); 61 | } 62 | 63 | if (wrapperTypes.has(fullyQualifiedTypeName)) { 64 | if ((json !== null && typeof json === 'object') || Array.isArray(json)) { 65 | throw new Error( 66 | `fromProto3JSONToInternalRepresentation: JSON representation for ${fullyQualifiedTypeName} expects a string, a number, or a boolean, but got ${typeof json}`, 67 | ); 68 | } 69 | return wrapperFromProto3JSON(fullyQualifiedTypeName, json); 70 | } 71 | 72 | if (json === null) { 73 | return null; 74 | } 75 | 76 | // Types that cannot be "null" 77 | if (fullyQualifiedTypeName === '.google.protobuf.Any') { 78 | return googleProtobufAnyFromProto3JSON(type.root, json); 79 | } 80 | 81 | if (fullyQualifiedTypeName === '.google.protobuf.Struct') { 82 | if (typeof json !== 'object') { 83 | throw new Error( 84 | `fromProto3JSONToInternalRepresentation: google.protobuf.Struct must be an object but got ${typeof json}`, 85 | ); 86 | } 87 | if (Array.isArray(json)) { 88 | throw new Error( 89 | 'fromProto3JSONToInternalRepresentation: google.protobuf.Struct must be an object but got an array', 90 | ); 91 | } 92 | return googleProtobufStructFromProto3JSON(json); 93 | } 94 | 95 | if (fullyQualifiedTypeName === '.google.protobuf.ListValue') { 96 | if (!Array.isArray(json)) { 97 | throw new Error( 98 | `fromProto3JSONToInternalRepresentation: google.protobuf.ListValue must be an array but got ${typeof json}`, 99 | ); 100 | } 101 | return googleProtobufListValueFromProto3JSON(json); 102 | } 103 | 104 | if (fullyQualifiedTypeName === '.google.protobuf.Duration') { 105 | if (typeof json !== 'string') { 106 | throw new Error( 107 | `fromProto3JSONToInternalRepresentation: google.protobuf.Duration must be a string but got ${typeof json}`, 108 | ); 109 | } 110 | return googleProtobufDurationFromProto3JSON(json); 111 | } 112 | 113 | if (fullyQualifiedTypeName === '.google.protobuf.Timestamp') { 114 | if (typeof json !== 'string') { 115 | throw new Error( 116 | `fromProto3JSONToInternalRepresentation: google.protobuf.Timestamp must be a string but got ${typeof json}`, 117 | ); 118 | } 119 | return googleProtobufTimestampFromProto3JSON(json); 120 | } 121 | 122 | if (fullyQualifiedTypeName === '.google.protobuf.FieldMask') { 123 | if (typeof json !== 'string') { 124 | throw new Error( 125 | `fromProto3JSONToInternalRepresentation: google.protobuf.FieldMask must be a string but got ${typeof json}`, 126 | ); 127 | } 128 | return googleProtobufFieldMaskFromProto3JSON(json); 129 | } 130 | 131 | const result: FromObjectValue = {}; 132 | for (const [key, value] of Object.entries(json)) { 133 | const field = type.fields[key]; 134 | if (!field) { 135 | continue; 136 | } 137 | 138 | const resolvedType = field.resolvedType; 139 | const fieldType = field.type; 140 | 141 | if (field.repeated) { 142 | if (value === null) { 143 | result[key] = []; 144 | } else { 145 | if (!Array.isArray(value)) { 146 | throw new Error( 147 | `fromProto3JSONToInternalRepresentation: expected an array for field ${key}`, 148 | ); 149 | } 150 | result[key] = value.map(element => 151 | fromProto3JSONToInternalRepresentation( 152 | resolvedType || fieldType, 153 | element, 154 | ), 155 | ); 156 | } 157 | } else if (field.map) { 158 | const map: FromObjectValue = {}; 159 | for (const [mapKey, mapValue] of Object.entries(value)) { 160 | map[mapKey] = fromProto3JSONToInternalRepresentation( 161 | resolvedType || fieldType, 162 | mapValue as JSONValue, 163 | ); 164 | } 165 | result[key] = map; 166 | } else if ( 167 | fieldType.match(/^(?:(?:(?:u?int|fixed)(?:32|64))|float|double)$/) 168 | ) { 169 | if (typeof value !== 'number' && typeof value !== 'string') { 170 | throw new Error( 171 | `fromProto3JSONToInternalRepresentation: field ${key} of type ${field.type} cannot contain value ${value}`, 172 | ); 173 | } 174 | result[key] = value; 175 | } else if (fieldType === 'string') { 176 | if (typeof value !== 'string') { 177 | throw new Error( 178 | `fromProto3JSONToInternalRepresentation: field ${key} of type ${field.type} cannot contain value ${value}`, 179 | ); 180 | } 181 | result[key] = value; 182 | } else if (fieldType === 'bool') { 183 | if (typeof value !== 'boolean') { 184 | throw new Error( 185 | `fromProto3JSONToInternalRepresentation: field ${key} of type ${field.type} cannot contain value ${value}`, 186 | ); 187 | } 188 | result[key] = value; 189 | } else if (fieldType === 'bytes') { 190 | if (typeof value !== 'string') { 191 | throw new Error( 192 | `fromProto3JSONToInternalRepresentation: field ${key} of type ${field.type} cannot contain value ${value}`, 193 | ); 194 | } 195 | result[key] = bytesFromProto3JSON(value); 196 | } else { 197 | // Message type 198 | assert( 199 | resolvedType !== null, 200 | `Expected to be able to resolve type for field ${field.name}`, 201 | ); 202 | const deserializedValue = fromProto3JSONToInternalRepresentation( 203 | resolvedType!, 204 | value, 205 | ); 206 | result[key] = deserializedValue; 207 | } 208 | } 209 | 210 | return result; 211 | } 212 | 213 | export function fromProto3JSON(type: protobuf.Type, json: JSONValue) { 214 | const internalRepr = fromProto3JSONToInternalRepresentation(type, json); 215 | if (internalRepr === null) { 216 | return null; 217 | } 218 | // We only expect a real object here sine all special cases should be already resolved. Everything else is an internal error 219 | assert( 220 | typeof internalRepr === 'object' && !Array.isArray(internalRepr), 221 | `fromProto3JSON: expected an object, not ${json}`, 222 | ); 223 | return type.fromObject(internalRepr as {}); 224 | } 225 | -------------------------------------------------------------------------------- /typescript/test/unit/error-coverage.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | import {JSONValue} from '../../src'; 22 | 23 | function testAnyErrorCoverage(root: protobuf.Root) { 24 | const Any = root.lookupType('google.protobuf.Any'); 25 | const message = Any.create({ 26 | type_url: 'types.googleapis.com/UnknownType', 27 | value: Buffer.from(''), 28 | }); 29 | const json = { 30 | '@type': 'types.googleapis.com/UnknownType', 31 | stringField: 'string', 32 | }; 33 | const noTypeUrlJson = { 34 | stringField: 'string', 35 | }; 36 | const incorrectSpecialValueJson = { 37 | '@type': 'types.googleapis.com/google.protobuf.Value', 38 | stringField: 'string', 39 | }; 40 | 41 | it('Any catches unknown type when deserializing', () => { 42 | assert.throws(() => { 43 | fromProto3JSON(Any, json); 44 | }); 45 | }); 46 | 47 | it('Any catches unknown type when serializing', () => { 48 | assert.throws(() => { 49 | toProto3JSON(message); 50 | }); 51 | }); 52 | 53 | it('Any requires @type field when deserializing', () => { 54 | assert.throws(() => { 55 | fromProto3JSON(Any, noTypeUrlJson); 56 | }); 57 | }); 58 | 59 | it('Any does not accept incorrect special JSON', () => { 60 | assert.throws(() => { 61 | fromProto3JSON(Any, incorrectSpecialValueJson); 62 | }); 63 | }); 64 | } 65 | 66 | function testTypeMismatch(root: protobuf.Root) { 67 | const PrimitiveTypes = root.lookupType('test.PrimitiveTypes'); 68 | const MessageWithBytesField = root.lookupType('test.MessageWithBytesField'); 69 | const MessageWithRepeated = root.lookupType('test.MessageWithRepeated'); 70 | const MessageWithAny = root.lookupType('test.MessageWithAny'); 71 | const MessageWithEnum = root.lookupType('test.MessageWithEnum'); 72 | const MessageWithStruct = root.lookupType('test.MessageWithStruct'); 73 | const MessageWithValue = root.lookupType('test.MessageWithValue'); 74 | const MessageWithListValue = root.lookupType('test.MessageWithListValue'); 75 | const MessageWithDuration = root.lookupType('test.MessageWithDuration'); 76 | const MessageWithTimestamp = root.lookupType('test.MessageWithTimestamp'); 77 | const MessageWithWrappers = root.lookupType('test.MessageWithWrappers'); 78 | const MessageWithFieldMask = root.lookupType('test.MessageWithFieldMask'); 79 | 80 | it('fromProto3JSON catches wrong value for integer fields', () => { 81 | assert.throws(() => { 82 | fromProto3JSON(PrimitiveTypes, {integerField: true}); 83 | }); 84 | }); 85 | 86 | it('fromProto3JSON catches wrong value for string fields', () => { 87 | assert.throws(() => { 88 | fromProto3JSON(PrimitiveTypes, {stringField: 42}); 89 | }); 90 | }); 91 | 92 | it('fromProto3JSON catches wrong value for bool fields', () => { 93 | assert.throws(() => { 94 | fromProto3JSON(PrimitiveTypes, {boolField: 'true'}); 95 | }); 96 | }); 97 | 98 | it('fromProto3JSON catches wrong value for bytes fields', () => { 99 | assert.throws(() => { 100 | fromProto3JSON(MessageWithBytesField, {bytesField: 1234}); 101 | }); 102 | }); 103 | 104 | it('fromProto3JSON requires an array for repeated fields', () => { 105 | assert.throws(() => { 106 | fromProto3JSON(MessageWithRepeated, {repeatedString: 'test'}); 107 | }); 108 | }); 109 | 110 | it('fromProto3JSON requires Any to be an object', () => { 111 | assert.throws(() => { 112 | fromProto3JSON(MessageWithAny, {anyField: 'zzz'}); 113 | }); 114 | }); 115 | 116 | it('fromProto3JSON requires enum to be a string or a number', () => { 117 | assert.throws(() => { 118 | fromProto3JSON(MessageWithEnum, {enumField: {}}); 119 | }); 120 | }); 121 | 122 | it('fromProto3JSON does not allow primitive types for Struct values', () => { 123 | assert.throws(() => { 124 | fromProto3JSON(MessageWithStruct, {structField: 42}); 125 | }); 126 | }); 127 | 128 | it('fromProto3JSON does not allow arrays for Struct values', () => { 129 | assert.throws(() => { 130 | fromProto3JSON(MessageWithStruct, {structField: [41, 42]}); 131 | }); 132 | }); 133 | 134 | it('fromProto3JSON does not allow objects for ListValue values', () => { 135 | assert.throws(() => { 136 | fromProto3JSON(MessageWithListValue, {listValueField: {}}); 137 | }); 138 | }); 139 | 140 | it('fromProto3JSON does not allow non-JSON types', () => { 141 | const func = () => {}; 142 | // calling it just for coverage :) 143 | func(); 144 | assert.throws(() => { 145 | fromProto3JSON(MessageWithValue, { 146 | valueField: func, 147 | } as unknown as JSONValue); 148 | }); 149 | }); 150 | 151 | it('fromProto3JSON does not allow non-string types for google.protobuf.Duration', () => { 152 | assert.throws(() => { 153 | fromProto3JSON(MessageWithDuration, { 154 | durationField: {seconds: 3, nanos: 0}, 155 | }); 156 | }); 157 | }); 158 | 159 | it('fromProto3JSON does not allow incorrect strings for google.protobuf.Duration', () => { 160 | assert.throws(() => { 161 | fromProto3JSON(MessageWithDuration, { 162 | durationField: '12345', 163 | }); 164 | }); 165 | }); 166 | 167 | it('fromProto3JSON does not allow non-string types for google.protobuf.Timestamp', () => { 168 | assert.throws(() => { 169 | fromProto3JSON(MessageWithTimestamp, { 170 | timestampField: {seconds: 3, nanos: 0}, 171 | }); 172 | }); 173 | }); 174 | 175 | it('fromProto3JSON does not allow incorrect strings for google.protobuf.Timestamp', () => { 176 | assert.throws(() => { 177 | fromProto3JSON(MessageWithTimestamp, { 178 | timestampField: '12345', 179 | }); 180 | }); 181 | }); 182 | 183 | it('fromProto3JSON does not allow object for wrapper types', () => { 184 | assert.throws(() => { 185 | fromProto3JSON(MessageWithWrappers, { 186 | doubleValueField: {}, 187 | }); 188 | }); 189 | }); 190 | 191 | it('fromProto3JSON does not allow non-strings for BytesValue', () => { 192 | assert.throws(() => { 193 | fromProto3JSON(MessageWithWrappers, { 194 | bytesValueField: 42, 195 | }); 196 | }); 197 | }); 198 | 199 | it('fromProto3JSON does not allow non-strings for FieldMask', () => { 200 | assert.throws(() => { 201 | fromProto3JSON(MessageWithFieldMask, { 202 | fieldMaskField: 42, 203 | }); 204 | }); 205 | }); 206 | } 207 | 208 | function testNull(root: protobuf.Root) { 209 | const PrimitiveTypes = root.lookupType('test.PrimitiveTypes'); 210 | const MessageWithValue = root.lookupType('test.MessageWithValue'); 211 | const MessageWithAny = root.lookupType('test.MessageWithAny'); 212 | 213 | it('fromProto3JSON returns null for null input', () => { 214 | assert.strictEqual(fromProto3JSON(PrimitiveTypes, null), null); 215 | }); 216 | 217 | it('toProto3JSON returns null for empty Value', () => { 218 | const emptyValue = MessageWithValue.fromObject({ 219 | valueField: {}, 220 | }); 221 | const emptyValueJson = { 222 | valueField: null, 223 | }; 224 | assert.deepStrictEqual(toProto3JSON(emptyValue), emptyValueJson); 225 | }); 226 | 227 | it('fromProto3JSON returns null for empty google.protobuf.Any', () => { 228 | const emptyAny = MessageWithAny.fromObject({}); 229 | const emptyAnyJson = { 230 | anyField: null, 231 | }; 232 | assert.deepStrictEqual( 233 | fromProto3JSON(MessageWithAny, emptyAnyJson), 234 | emptyAny, 235 | ); 236 | }); 237 | 238 | it('fromProto3JSON returns null for google.protobuf.Any with null value', () => { 239 | const nullAny = MessageWithAny.fromObject({ 240 | anyField: { 241 | type_url: 'types.googleapis.com/google.protobuf.Struct', 242 | value: null, 243 | }, 244 | }); 245 | const nullAnyJson = { 246 | anyField: { 247 | '@type': 'types.googleapis.com/google.protobuf.Struct', 248 | value: null, 249 | }, 250 | }; 251 | assert.deepStrictEqual( 252 | fromProto3JSON(MessageWithAny, nullAnyJson), 253 | nullAny, 254 | ); 255 | }); 256 | } 257 | 258 | function testRegularObject() { 259 | it('toProto3JSON does not accept non-Message object', () => { 260 | assert.throws(() => { 261 | toProto3JSON({ 262 | stringField: 'test', 263 | } as unknown as protobuf.Message); 264 | }); 265 | }); 266 | } 267 | 268 | testTwoTypesOfLoad('error coverage', [ 269 | testAnyErrorCoverage, 270 | testTypeMismatch, 271 | testNull, 272 | testRegularObject, 273 | ]); 274 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [3.0.2](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v3.0.1...v3.0.2) (2025-08-12) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * Switch typescript to tilde notation ([#137](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/137)) ([4ec0dd0](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/4ec0dd09201b07f30e5fcf5712198604d47d0cf9)) 9 | 10 | ## [3.0.1](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v3.0.0...v3.0.1) (2025-06-27) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **deps:** Update dependency google-proto-files to v5 ([#124](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/124)) ([716bb43](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/716bb43b1c762b9d22b6d3097ba5f13169889dcc)) 16 | * Fix enum serialization in maps and repeated fields ([#129](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/129)) ([d84540a](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/d84540a956d598d2977f5b98718a55cd8b5b2f15)) 17 | 18 | ## [3.0.0](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v2.0.2...v3.0.0) (2025-02-15) 19 | 20 | 21 | ### ⚠ BREAKING CHANGES 22 | 23 | * upgrade to node 18 ([#113](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/113)) 24 | 25 | ### Miscellaneous Chores 26 | 27 | * Upgrade to node 18 ([#113](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/113)) ([f28a826](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/f28a826b838faaddb9ab93a52179e251517fad4d)) 28 | 29 | ## [2.0.2](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v2.0.1...v2.0.2) (2024-05-22) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * Properly convert repeated int64 and maps of int64 ([#96](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/96)) ([1ec05fb](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/1ec05fb59edfdff7531b9372dcfe14c0fe36562c)) 35 | 36 | ## [2.0.1](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v2.0.0...v2.0.1) (2024-01-16) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * **deps:** Update dependency google-proto-files to v4 ([#82](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/82)) ([72623e0](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/72623e04e1043353ef952178714ced733001a06d)) 42 | * **deps:** Update protobufjs to 7.2.5 ([a0f5c83](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/a0f5c833cba654949ec5e624fad1849020cf899d)) 43 | 44 | ## [2.0.0](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.1.1...v2.0.0) (2023-08-07) 45 | 46 | 47 | ### ⚠ BREAKING CHANGES 48 | 49 | * require Node 14 ([#72](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/72)) 50 | 51 | ### Miscellaneous Chores 52 | 53 | * Require Node 14 ([#72](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/72)) ([8681834](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/8681834d8bc204c40857e299570e1e5df4bc5618)) 54 | 55 | ## [1.1.1](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.1.0...v1.1.1) (2023-04-25) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * Repeated field can be null in JSON ([#66](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/66)) ([f81d3ab](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/f81d3ab202e2a674be73db8a9b74d3eecf3bbed4)) 61 | 62 | ## [1.1.0](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.0.3...v1.1.0) (2022-08-26) 63 | 64 | 65 | ### Features 66 | 67 | * option to serialize enum values as numbers ([#60](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/60)) ([456b771](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/456b771d5fef06d914c6e201fd9f17251e55d4d9)) 68 | 69 | 70 | ### Bug Fixes 71 | 72 | * remove pip install statements ([#1546](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/1546)) ([#58](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/58)) ([741d070](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/741d0704f49332dd5d66206fcdf2111464fb8759)) 73 | 74 | ## [1.0.3](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.0.2...v1.0.3) (2022-07-10) 75 | 76 | 77 | ### Bug Fixes 78 | 79 | * **deps:** update dependency protobufjs to v7 ([#56](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/56)) ([038fea5](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/038fea537c8809dc272c2352b832b5301c7b79d2)) 80 | 81 | ## [1.0.2](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.0.1...v1.0.2) (2022-06-15) 82 | 83 | 84 | ### Bug Fixes 85 | 86 | * **deps:** update dependency google-proto-files to v3 ([#53](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/53)) ([40fd527](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/40fd527109838a21887f7a5058406244acabe938)) 87 | 88 | ## [1.0.1](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.0.0...v1.0.1) (2022-06-03) 89 | 90 | 91 | ### Bug Fixes 92 | 93 | * **deps:** bump protobuf.js to ^6.11.3 ([#46](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/46)) ([af8a14a](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/af8a14a35c04cbef49bff806b7d906287d1d2c0d)) 94 | 95 | ## [1.0.0](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.9...v1.0.0) (2022-05-12) 96 | 97 | 98 | ### ⚠ BREAKING CHANGES 99 | 100 | * make Node 12 minimum language version (#38) 101 | 102 | ### Features 103 | 104 | * make Node 12 minimum language version ([#38](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/38)) ([658d29e](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/658d29e531c2d04d4007e5843aa62d9d8ee0dae8)) 105 | 106 | ### [0.1.9](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.8...v0.1.9) (2022-05-11) 107 | 108 | 109 | ### Bug Fixes 110 | 111 | * do not use Node.js assert ([#37](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/37)) ([dccfeca](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/dccfeca6f3bbeec29d88319f375a734ec48aadf7)) 112 | 113 | ### [0.1.8](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.7...v0.1.8) (2022-01-21) 114 | 115 | 116 | ### Bug Fixes 117 | 118 | * timestamp without millisecond ([#30](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/30)) ([a55d0b6](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/a55d0b6f98f6d1c8b7d971d0a583bbd82ea66983)) 119 | 120 | ### [0.1.7](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.6...v0.1.7) (2022-01-14) 121 | 122 | 123 | ### Bug Fixes 124 | 125 | * keep nano second precision when maps between JSON and proto3 ([#28](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/28)) ([eaa01ce](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/eaa01ce92c4eefa816d1d6f8ef6ed11bd2a6364b)) 126 | 127 | ### [0.1.6](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.5...v0.1.6) (2021-11-15) 128 | 129 | 130 | ### Bug Fixes 131 | 132 | * **deps:** protobufjs is a dependency for the types ([#23](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/23)) ([06470c1](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/06470c1df501439ec3f8bc546cd23d798604f3bd)) 133 | 134 | ### [0.1.5](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.4...v0.1.5) (2021-10-26) 135 | 136 | 137 | ### Bug Fixes 138 | 139 | * JSON accept special string for NaN, Infinity ([#19](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/19)) ([01a345b](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/01a345b7b1d62ee65a8673737975980d274fa22a)) 140 | 141 | ### [0.1.4](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.3...v0.1.4) (2021-09-20) 142 | 143 | 144 | ### Bug Fixes 145 | 146 | * do not emit empty lists to JSON ([#15](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/15)) ([af9dfd6](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/af9dfd65efb84cfb31af0faca805f53b0ffa9874)) 147 | 148 | ### [0.1.3](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.2...v0.1.3) (2021-08-18) 149 | 150 | 151 | ### Bug Fixes 152 | 153 | * do not fail for unknown enum values ([#11](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/11)) ([ff9f0f1](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/ff9f0f1881b1aafacd693b4e24eaee9e56aff79c)) 154 | 155 | ### [0.1.2](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.1...v0.1.2) (2021-08-17) 156 | 157 | 158 | ### Bug Fixes 159 | 160 | * use imported protobufjs in toproto3json.ts ([#9](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/9)) ([f6c86c7](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/f6c86c777d567d8430b09dea3282e52af24d890f)) 161 | 162 | ### [0.1.1](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.0...v0.1.1) (2021-08-04) 163 | 164 | 165 | ### Bug Fixes 166 | 167 | * accept and return strings for int64 and uint64 ([#7](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/7)) ([35689ec](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/35689ecee55dbe6e4cf3327c535514d7fcb8332d)) 168 | 169 | ## 0.1.0 (2021-08-03) 170 | 171 | 172 | ### ⚠ BREAKING CHANGES 173 | 174 | * proto3 JSON serializer and deserializer (#2) 175 | 176 | ### Features 177 | 178 | * proto3 JSON serializer and deserializer ([#2](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/2)) ([96255a7](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/96255a77c7714f33cae547db9160615d7f80a233)) 179 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /typescript/test/unit/value.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | import {JSONObject} from '../../src'; 22 | 23 | function testGoogleProtobufValue(root: protobuf.Root) { 24 | const MessageWithValue = root.lookupType('test.MessageWithValue'); 25 | const messageNull = MessageWithValue.fromObject({ 26 | valueField: { 27 | nullValue: 'NULL_VALUE', 28 | }, 29 | }); 30 | const jsonNull = {valueField: null}; 31 | const messageNumber = MessageWithValue.fromObject({ 32 | valueField: { 33 | numberValue: 42, 34 | }, 35 | }); 36 | const messageNaN = MessageWithValue.fromObject({ 37 | valueField: { 38 | numberValue: NaN, 39 | }, 40 | }); 41 | const messageInfinity = MessageWithValue.fromObject({ 42 | valueField: { 43 | numberValue: Infinity, 44 | }, 45 | }); 46 | const messageNegInfinity = MessageWithValue.fromObject({ 47 | valueField: { 48 | numberValue: -Infinity, 49 | }, 50 | }); 51 | const jsonNumber = {valueField: 42}; 52 | const jsonNaN = {valueField: 'NaN'}; 53 | const jsonInfinity = {valueField: 'Infinity'}; 54 | const jsonNegInfinity = {valueField: '-Infinity'}; 55 | const messageString = MessageWithValue.fromObject({ 56 | valueField: { 57 | stringValue: 'test', 58 | }, 59 | }); 60 | const jsonString = {valueField: 'test'}; 61 | const messageBool = MessageWithValue.fromObject({ 62 | valueField: { 63 | boolValue: true, 64 | }, 65 | }); 66 | const jsonBool = {valueField: true}; 67 | const messageStruct = MessageWithValue.fromObject({ 68 | valueField: { 69 | structValue: { 70 | fields: { 71 | nestedNull: (messageNull as unknown as JSONObject).valueField, 72 | nestedNumber: (messageNumber as unknown as JSONObject).valueField, 73 | nestedString: (messageString as unknown as JSONObject).valueField, 74 | nestedBool: (messageBool as unknown as JSONObject).valueField, 75 | nestedStruct: { 76 | structValue: { 77 | fields: { 78 | stringField: { 79 | stringValue: 'test', 80 | }, 81 | }, 82 | }, 83 | }, 84 | nestedList: { 85 | listValue: { 86 | values: [ 87 | { 88 | stringValue: 'test', 89 | }, 90 | ], 91 | }, 92 | }, 93 | }, 94 | }, 95 | }, 96 | }); 97 | const jsonStruct = { 98 | valueField: { 99 | nestedNull: jsonNull.valueField, 100 | nestedNumber: jsonNumber.valueField, 101 | nestedString: jsonString.valueField, 102 | nestedBool: jsonBool.valueField, 103 | nestedStruct: { 104 | stringField: 'test', 105 | }, 106 | nestedList: ['test'], 107 | }, 108 | }; 109 | const messageList = MessageWithValue.fromObject({ 110 | valueField: { 111 | listValue: { 112 | values: [ 113 | (messageNull as unknown as JSONObject).valueField, 114 | (messageNumber as unknown as JSONObject).valueField, 115 | (messageString as unknown as JSONObject).valueField, 116 | (messageBool as unknown as JSONObject).valueField, 117 | (messageStruct as unknown as JSONObject).valueField, 118 | { 119 | listValue: { 120 | values: [ 121 | { 122 | stringValue: 'test', 123 | }, 124 | ], 125 | }, 126 | }, 127 | ], 128 | }, 129 | }, 130 | }); 131 | const jsonList = { 132 | valueField: [ 133 | jsonNull.valueField, 134 | jsonNumber.valueField, 135 | jsonString.valueField, 136 | jsonBool.valueField, 137 | jsonStruct.valueField, 138 | ['test'], 139 | ], 140 | }; 141 | 142 | it('serializes NullValue to proto3 JSON', () => { 143 | const serialized = toProto3JSON(messageNull); 144 | assert.deepStrictEqual(serialized, jsonNull); 145 | }); 146 | 147 | it('deserializes NullValue from proto3 JSON', () => { 148 | const deserialized = fromProto3JSON(MessageWithValue, jsonNull); 149 | assert.deepStrictEqual(deserialized, messageNull); 150 | }); 151 | 152 | it('serializes NumberValue to proto3 JSON', () => { 153 | const serialized = toProto3JSON(messageNumber); 154 | assert.deepStrictEqual(serialized, jsonNumber); 155 | }); 156 | 157 | it('deserializes NumberValue from proto3 JSON', () => { 158 | const deserialized = fromProto3JSON(MessageWithValue, jsonNumber); 159 | assert.deepStrictEqual(deserialized, messageNumber); 160 | }); 161 | 162 | it('serializes NaN to proto3 JSON', () => { 163 | const serialized = toProto3JSON(messageNaN); 164 | assert.deepStrictEqual(serialized, jsonNaN); 165 | }); 166 | 167 | it('deserializes NaN from proto3 JSON', () => { 168 | const deserialized = fromProto3JSON(MessageWithValue, jsonNaN); 169 | // Attempting to serialize NaN or Infinity results in error. 170 | // "NaN" would parse as string_value, not number_value. 171 | // https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Value 172 | assert.deepStrictEqual( 173 | deserialized, 174 | MessageWithValue.fromObject({ 175 | valueField: { 176 | stringValue: 'NaN', 177 | }, 178 | }), 179 | ); 180 | }); 181 | 182 | it('serializes Infinity to proto3 JSON', () => { 183 | const serialized = toProto3JSON(messageInfinity); 184 | assert.deepStrictEqual(serialized, jsonInfinity); 185 | }); 186 | 187 | it('deserializes Infinity from proto3 JSON', () => { 188 | const deserialized = fromProto3JSON(MessageWithValue, jsonInfinity); 189 | // Attempting to serialize NaN or Infinity results in error. 190 | // "Infinity" would parse as string_value, not number_value. 191 | // https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Value 192 | assert.deepStrictEqual( 193 | deserialized, 194 | MessageWithValue.fromObject({ 195 | valueField: { 196 | stringValue: 'Infinity', 197 | }, 198 | }), 199 | ); 200 | }); 201 | 202 | it('serializes negative Infinity to proto3 JSON', () => { 203 | const serialized = toProto3JSON(messageNegInfinity); 204 | assert.deepStrictEqual(serialized, jsonNegInfinity); 205 | }); 206 | 207 | it('deserializes Infinity from proto3 JSON', () => { 208 | const deserialized = fromProto3JSON(MessageWithValue, jsonNegInfinity); 209 | // Attempting to serialize NaN, Infinity, negative Infinity results in error. 210 | // "-Infinity" would parse as string_value, not number_value. 211 | // https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Value 212 | assert.deepStrictEqual( 213 | deserialized, 214 | MessageWithValue.fromObject({ 215 | valueField: { 216 | stringValue: '-Infinity', 217 | }, 218 | }), 219 | ); 220 | }); 221 | 222 | it('serializes StringValue to proto3 JSON', () => { 223 | const serialized = toProto3JSON(messageString); 224 | assert.deepStrictEqual(serialized, jsonString); 225 | }); 226 | 227 | it('deserializes StringValue from proto3 JSON', () => { 228 | const deserialized = fromProto3JSON(MessageWithValue, jsonString); 229 | assert.deepStrictEqual(deserialized, messageString); 230 | }); 231 | 232 | it('serializes BoolValue to proto3 JSON', () => { 233 | const serialized = toProto3JSON(messageBool); 234 | assert.deepStrictEqual(serialized, jsonBool); 235 | }); 236 | 237 | it('deserializes BoolValue from proto3 JSON', () => { 238 | const deserialized = fromProto3JSON(MessageWithValue, jsonBool); 239 | assert.deepStrictEqual(deserialized, messageBool); 240 | }); 241 | 242 | it('serializes StructValue to proto3 JSON', () => { 243 | const serialized = toProto3JSON(messageStruct); 244 | assert.deepStrictEqual(serialized, jsonStruct); 245 | }); 246 | 247 | it('deserializes StructValue from proto3 JSON', () => { 248 | const deserialized = fromProto3JSON(MessageWithValue, jsonStruct); 249 | assert.deepStrictEqual(deserialized, messageStruct); 250 | }); 251 | 252 | it('serializes ListValue to proto3 JSON', () => { 253 | const serialized = toProto3JSON(messageList); 254 | assert.deepStrictEqual(serialized, jsonList); 255 | }); 256 | 257 | it('deserializes ListValue from proto3 JSON', () => { 258 | const deserialized = fromProto3JSON(MessageWithValue, jsonList); 259 | assert.deepStrictEqual(deserialized, messageList); 260 | }); 261 | } 262 | 263 | function testGoogleProtobufStruct(root: protobuf.Root) { 264 | const MessageWithStruct = root.lookupType('test.MessageWithStruct'); 265 | const message = MessageWithStruct.fromObject({ 266 | structField: { 267 | fields: { 268 | stringField: { 269 | stringValue: 'test', 270 | }, 271 | numberField: { 272 | numberValue: 42, 273 | }, 274 | }, 275 | }, 276 | }); 277 | const json = { 278 | structField: { 279 | stringField: 'test', 280 | numberField: 42, 281 | }, 282 | }; 283 | 284 | const emptyStructMessage = MessageWithStruct.fromObject({ 285 | structField: {}, 286 | }); 287 | const emptyStructJson = { 288 | structField: {}, 289 | }; 290 | 291 | it('serializes google.protobuf.Struct to proto3 JSON', () => { 292 | const serialized = toProto3JSON(message); 293 | assert.deepStrictEqual(serialized, json); 294 | }); 295 | 296 | it('deserializes google.protobuf.Struct from proto3 JSON', () => { 297 | const deserialized = fromProto3JSON(MessageWithStruct, json); 298 | assert.deepStrictEqual(deserialized, message); 299 | }); 300 | 301 | it('serializes empty google.protobuf.Struct to proto3 JSON', () => { 302 | const serialized = toProto3JSON(emptyStructMessage); 303 | assert.deepStrictEqual(serialized, emptyStructJson); 304 | }); 305 | 306 | it('deserializes empty google.protobuf.Struct from proto3 JSON', () => { 307 | const deserialized = fromProto3JSON(MessageWithStruct, emptyStructJson); 308 | assert.deepStrictEqual(deserialized, emptyStructMessage); 309 | }); 310 | } 311 | 312 | function testGoogleProtobufListValue(root: protobuf.Root) { 313 | const MessageWithListValue = root.lookupType('test.MessageWithListValue'); 314 | const message = MessageWithListValue.fromObject({ 315 | listValueField: { 316 | values: [ 317 | { 318 | structValue: { 319 | fields: { 320 | stringField: { 321 | stringValue: 'test', 322 | }, 323 | }, 324 | }, 325 | }, 326 | { 327 | structValue: { 328 | fields: { 329 | numberField: { 330 | numberValue: 42, 331 | }, 332 | }, 333 | }, 334 | }, 335 | ], 336 | }, 337 | }); 338 | const json: JSONObject = { 339 | listValueField: [{stringField: 'test'}, {numberField: 42}], 340 | }; 341 | 342 | const emptyListMessage = MessageWithListValue.fromObject({ 343 | listValueField: {}, 344 | }); 345 | const emptyListJson = { 346 | listValueField: [], 347 | }; 348 | 349 | it('serializes google.protobuf.ListValue to proto3 JSON', () => { 350 | const serialized = toProto3JSON(message); 351 | assert.deepStrictEqual(serialized, json); 352 | }); 353 | 354 | it('deserializes google.protobuf.ListValue from proto3 JSON', () => { 355 | const deserialized = fromProto3JSON(MessageWithListValue, json); 356 | assert.deepStrictEqual(deserialized, message); 357 | }); 358 | 359 | it('serializes empty google.protobuf.ListValue to proto3 JSON', () => { 360 | const serialized = toProto3JSON(emptyListMessage); 361 | assert.deepStrictEqual(serialized, emptyListJson); 362 | }); 363 | 364 | it('deserializes empty google.protobuf.ListValue from proto3 JSON', () => { 365 | const deserialized = fromProto3JSON(MessageWithListValue, emptyListJson); 366 | assert.deepStrictEqual(deserialized, emptyListMessage); 367 | }); 368 | } 369 | 370 | function testGoogleProtobufNullValue(root: protobuf.Root) { 371 | const MessageWithNullValue = root.lookupType('test.MessageWithNullValue'); 372 | const message = MessageWithNullValue.fromObject({ 373 | nullValueField: 'NULL_VALUE', 374 | }); 375 | const json = { 376 | nullValueField: null, 377 | }; 378 | 379 | it('serializes google.protobuf.NullValue to proto3 JSON', () => { 380 | const serialized = toProto3JSON(message); 381 | assert.deepStrictEqual(serialized, json); 382 | }); 383 | 384 | it('deserializes google.protobuf.NullValue from proto3 JSON', () => { 385 | const deserialized = fromProto3JSON(MessageWithNullValue, json); 386 | assert.deepStrictEqual(deserialized, message); 387 | }); 388 | } 389 | 390 | testTwoTypesOfLoad('google.protobuf.Value', [ 391 | testGoogleProtobufValue, 392 | testGoogleProtobufStruct, 393 | testGoogleProtobufListValue, 394 | testGoogleProtobufNullValue, 395 | ]); 396 | -------------------------------------------------------------------------------- /test-fixtures/proto/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "nested": { 3 | "test": { 4 | "nested": { 5 | "PrimitiveTypes": { 6 | "fields": { 7 | "integerField": { 8 | "type": "int32", 9 | "id": 1 10 | }, 11 | "unsignedIntegerField": { 12 | "type": "uint32", 13 | "id": 2 14 | }, 15 | "fixedIntegerField": { 16 | "type": "fixed32", 17 | "id": 3 18 | }, 19 | "stringField": { 20 | "type": "string", 21 | "id": 4 22 | }, 23 | "boolField": { 24 | "type": "bool", 25 | "id": 5 26 | }, 27 | "int64Field": { 28 | "type": "int64", 29 | "id": 6 30 | }, 31 | "uint64Field": { 32 | "type": "uint64", 33 | "id": 7 34 | }, 35 | "doubleField": { 36 | "type": "double", 37 | "id": 8 38 | } 39 | } 40 | }, 41 | "MessageWithNestedMessage": { 42 | "fields": { 43 | "innerMessage": { 44 | "type": "AnyContent", 45 | "id": 1 46 | } 47 | } 48 | }, 49 | "MessageWithAny": { 50 | "fields": { 51 | "anyField": { 52 | "type": "google.protobuf.Any", 53 | "id": 3 54 | } 55 | } 56 | }, 57 | "AnyContent": { 58 | "fields": { 59 | "stringField": { 60 | "type": "string", 61 | "id": 1 62 | } 63 | } 64 | }, 65 | "MessageWithBytesField": { 66 | "fields": { 67 | "bytesField": { 68 | "type": "bytes", 69 | "id": 3 70 | } 71 | } 72 | }, 73 | "MessageWithMap": { 74 | "fields": { 75 | "mapField": { 76 | "keyType": "string", 77 | "type": "MapValue", 78 | "id": 3 79 | }, 80 | "stringMapField": { 81 | "keyType": "string", 82 | "type": "string", 83 | "id": 4 84 | }, 85 | "longMapField": { 86 | "keyType": "string", 87 | "type": "int64", 88 | "id": 5 89 | }, 90 | "enumMapField": { 91 | "keyType": "string", 92 | "type": "Enum", 93 | "id": 6 94 | } 95 | } 96 | }, 97 | "MapValue": { 98 | "fields": { 99 | "stringField": { 100 | "type": "string", 101 | "id": 1 102 | } 103 | } 104 | }, 105 | "MessageWithRepeated": { 106 | "fields": { 107 | "repeatedString": { 108 | "rule": "repeated", 109 | "type": "string", 110 | "id": 3 111 | }, 112 | "repeatedMessage": { 113 | "rule": "repeated", 114 | "type": "RepeatedValue", 115 | "id": 4 116 | }, 117 | "oneMoreRepeatedString": { 118 | "rule": "repeated", 119 | "type": "string", 120 | "id": 5 121 | }, 122 | "repeatedLong": { 123 | "rule": "repeated", 124 | "type": "int64", 125 | "id": 6 126 | }, 127 | "repeatedEnum": { 128 | "rule": "repeated", 129 | "type": "Enum", 130 | "id": 7 131 | } 132 | } 133 | }, 134 | "RepeatedValue": { 135 | "fields": { 136 | "stringField": { 137 | "type": "string", 138 | "id": 1 139 | } 140 | } 141 | }, 142 | "Enum": { 143 | "values": { 144 | "UNKNOWN": 0, 145 | "KNOWN": 1 146 | } 147 | }, 148 | "MessageWithEnum": { 149 | "fields": { 150 | "enumField": { 151 | "type": "Enum", 152 | "id": 3 153 | } 154 | } 155 | }, 156 | "MessageWithOneof": { 157 | "oneofs": { 158 | "_oneofField": { 159 | "oneof": [ 160 | "stringField", 161 | "integerField", 162 | "messageField" 163 | ] 164 | } 165 | }, 166 | "fields": { 167 | "stringField": { 168 | "type": "string", 169 | "id": 1 170 | }, 171 | "integerField": { 172 | "type": "int32", 173 | "id": 2 174 | }, 175 | "messageField": { 176 | "type": "AnyContent", 177 | "id": 3 178 | } 179 | } 180 | }, 181 | "MessageWithValue": { 182 | "fields": { 183 | "valueField": { 184 | "type": "google.protobuf.Value", 185 | "id": 1 186 | } 187 | } 188 | }, 189 | "MessageWithStruct": { 190 | "fields": { 191 | "structField": { 192 | "type": "google.protobuf.Struct", 193 | "id": 1 194 | } 195 | } 196 | }, 197 | "MessageWithListValue": { 198 | "fields": { 199 | "listValueField": { 200 | "type": "google.protobuf.ListValue", 201 | "id": 1 202 | } 203 | } 204 | }, 205 | "MessageWithNullValue": { 206 | "fields": { 207 | "nullValueField": { 208 | "type": "google.protobuf.NullValue", 209 | "id": 1 210 | } 211 | } 212 | }, 213 | "MessageWithDuration": { 214 | "fields": { 215 | "durationField": { 216 | "type": "google.protobuf.Duration", 217 | "id": 1 218 | } 219 | } 220 | }, 221 | "MessageWithTimestamp": { 222 | "fields": { 223 | "timestampField": { 224 | "type": "google.protobuf.Timestamp", 225 | "id": 1 226 | } 227 | } 228 | }, 229 | "MessageWithEmpty": { 230 | "fields": { 231 | "emptyField": { 232 | "type": "google.protobuf.Empty", 233 | "id": 1 234 | } 235 | } 236 | }, 237 | "MessageWithWrappers": { 238 | "fields": { 239 | "doubleValueField": { 240 | "type": "google.protobuf.DoubleValue", 241 | "id": 1 242 | }, 243 | "floatValueField": { 244 | "type": "google.protobuf.FloatValue", 245 | "id": 2 246 | }, 247 | "int64ValueField": { 248 | "type": "google.protobuf.Int64Value", 249 | "id": 3 250 | }, 251 | "uint64ValueField": { 252 | "type": "google.protobuf.UInt64Value", 253 | "id": 4 254 | }, 255 | "int32ValueField": { 256 | "type": "google.protobuf.Int32Value", 257 | "id": 5 258 | }, 259 | "uint32ValueField": { 260 | "type": "google.protobuf.UInt32Value", 261 | "id": 6 262 | }, 263 | "boolValueField": { 264 | "type": "google.protobuf.BoolValue", 265 | "id": 7 266 | }, 267 | "stringValueField": { 268 | "type": "google.protobuf.StringValue", 269 | "id": 8 270 | }, 271 | "bytesValueField": { 272 | "type": "google.protobuf.BytesValue", 273 | "id": 9 274 | }, 275 | "nanValueField": { 276 | "type": "google.protobuf.DoubleValue", 277 | "id": 10 278 | }, 279 | "infinityValueField": { 280 | "type": "google.protobuf.DoubleValue", 281 | "id": 11 282 | }, 283 | "negativeInfinityValueField": { 284 | "type": "google.protobuf.DoubleValue", 285 | "id": 12 286 | } 287 | } 288 | }, 289 | "MessageWithFieldMask": { 290 | "fields": { 291 | "fieldMaskField": { 292 | "type": "google.protobuf.FieldMask", 293 | "id": 1 294 | } 295 | } 296 | } 297 | } 298 | }, 299 | "google": { 300 | "nested": { 301 | "protobuf": { 302 | "nested": { 303 | "Any": { 304 | "fields": { 305 | "type_url": { 306 | "type": "string", 307 | "id": 1 308 | }, 309 | "value": { 310 | "type": "bytes", 311 | "id": 2 312 | } 313 | } 314 | }, 315 | "Duration": { 316 | "fields": { 317 | "seconds": { 318 | "type": "int64", 319 | "id": 1 320 | }, 321 | "nanos": { 322 | "type": "int32", 323 | "id": 2 324 | } 325 | } 326 | }, 327 | "Empty": { 328 | "fields": {} 329 | }, 330 | "FieldMask": { 331 | "fields": { 332 | "paths": { 333 | "rule": "repeated", 334 | "type": "string", 335 | "id": 1 336 | } 337 | } 338 | }, 339 | "Struct": { 340 | "fields": { 341 | "fields": { 342 | "keyType": "string", 343 | "type": "Value", 344 | "id": 1 345 | } 346 | } 347 | }, 348 | "Value": { 349 | "oneofs": { 350 | "kind": { 351 | "oneof": [ 352 | "nullValue", 353 | "numberValue", 354 | "stringValue", 355 | "boolValue", 356 | "structValue", 357 | "listValue" 358 | ] 359 | } 360 | }, 361 | "fields": { 362 | "nullValue": { 363 | "type": "NullValue", 364 | "id": 1 365 | }, 366 | "numberValue": { 367 | "type": "double", 368 | "id": 2 369 | }, 370 | "stringValue": { 371 | "type": "string", 372 | "id": 3 373 | }, 374 | "boolValue": { 375 | "type": "bool", 376 | "id": 4 377 | }, 378 | "structValue": { 379 | "type": "Struct", 380 | "id": 5 381 | }, 382 | "listValue": { 383 | "type": "ListValue", 384 | "id": 6 385 | } 386 | } 387 | }, 388 | "NullValue": { 389 | "values": { 390 | "NULL_VALUE": 0 391 | } 392 | }, 393 | "ListValue": { 394 | "fields": { 395 | "values": { 396 | "rule": "repeated", 397 | "type": "Value", 398 | "id": 1 399 | } 400 | } 401 | }, 402 | "Timestamp": { 403 | "fields": { 404 | "seconds": { 405 | "type": "int64", 406 | "id": 1 407 | }, 408 | "nanos": { 409 | "type": "int32", 410 | "id": 2 411 | } 412 | } 413 | }, 414 | "DoubleValue": { 415 | "fields": { 416 | "value": { 417 | "type": "double", 418 | "id": 1 419 | } 420 | } 421 | }, 422 | "FloatValue": { 423 | "fields": { 424 | "value": { 425 | "type": "float", 426 | "id": 1 427 | } 428 | } 429 | }, 430 | "Int64Value": { 431 | "fields": { 432 | "value": { 433 | "type": "int64", 434 | "id": 1 435 | } 436 | } 437 | }, 438 | "UInt64Value": { 439 | "fields": { 440 | "value": { 441 | "type": "uint64", 442 | "id": 1 443 | } 444 | } 445 | }, 446 | "Int32Value": { 447 | "fields": { 448 | "value": { 449 | "type": "int32", 450 | "id": 1 451 | } 452 | } 453 | }, 454 | "UInt32Value": { 455 | "fields": { 456 | "value": { 457 | "type": "uint32", 458 | "id": 1 459 | } 460 | } 461 | }, 462 | "BoolValue": { 463 | "fields": { 464 | "value": { 465 | "type": "bool", 466 | "id": 1 467 | } 468 | } 469 | }, 470 | "StringValue": { 471 | "fields": { 472 | "value": { 473 | "type": "string", 474 | "id": 1 475 | } 476 | } 477 | }, 478 | "BytesValue": { 479 | "fields": { 480 | "value": { 481 | "type": "bytes", 482 | "id": 1 483 | } 484 | } 485 | } 486 | } 487 | } 488 | } 489 | } 490 | } 491 | } --------------------------------------------------------------------------------