Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix:resolve data precision loss in JSON messages #1507

Merged
merged 4 commits into from Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/deploy_web.yaml
Expand Up @@ -13,12 +13,12 @@ jobs:
deploy_website:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: use node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'

- uses: actions/checkout@v4
- name: set env
run: |
cd web
Expand All @@ -43,12 +43,12 @@ jobs:
if: github.event_name != 'pull_request'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: use node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'

- uses: actions/checkout@v4
- name: set env
run: |
cd web
Expand Down
2 changes: 2 additions & 0 deletions cli/package.json
Expand Up @@ -29,6 +29,7 @@
"concat-stream": "^2.0.0",
"core-js": "^3.26.0",
"js-yaml": "^4.1.0",
"json-bigint": "^1.0.0",
"mqtt": "^4.3.7",
"protobufjs": "^7.2.3",
"pump": "^3.0.0",
Expand All @@ -40,6 +41,7 @@
"@faker-js/faker": "^7.6.0",
"@types/concat-stream": "^2.0.0",
"@types/js-yaml": "^4.0.5",
"@types/json-bigint": "^1.0.4",
"@types/node": "^17.0.43",
"@types/pump": "^1.1.1",
"@types/readable-stream": "^2.3.13",
Expand Down
5 changes: 3 additions & 2 deletions cli/src/utils/convertPayload.ts
@@ -1,11 +1,12 @@
import chalk from 'chalk'
import { jsonParse, jsonStringify } from './jsonUtils'

const convertJSON = (value: Buffer | string, action: 'encode' | 'decode') => {
try {
if (action === 'decode') {
return JSON.stringify(JSON.parse(value.toString()), null, 2)
return jsonStringify(jsonParse(value.toString()), null, 2)
} else {
return Buffer.from(JSON.stringify(JSON.parse(value.toString())))
return Buffer.from(jsonStringify(jsonParse(value.toString())))
}
} catch (err) {
return chalk.red(err)
Expand Down
24 changes: 24 additions & 0 deletions cli/src/utils/jsonUtils.ts
@@ -0,0 +1,24 @@
const JSONBigNumber = require('json-bigint')
const JSONBigInt = require('json-bigint')({ useNativeBigInt: true })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be changed to ES6 import?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just a matter of code style. I will merge the PR first.


export const jsonParse: typeof JSON.parse = (...args: any[]) => {
try {
// When JSON only contains integers, use the native bigint type.
return JSONBigInt.parse(...args)
} catch (_error) {
// When JSON contains floating-point numbers, use the bignumber library.
// The numbers in the parsed JSON Object will be represented as BigNumber Objects.
return JSONBigNumber.parse(...args)
}
}

export const jsonStringify: typeof JSON.stringify = (...args: any[]) => {
try {
// When JSON only contains integers, use the native bigint type.
return JSONBigInt.stringify(...args)
} catch (_error) {
// When JSON contains floating-point numbers, use the bignumber library.
// Integers will be represented in scientific notation.
return JSONBigNumber.stringify(...args)
}
}
17 changes: 17 additions & 0 deletions cli/yarn.lock
Expand Up @@ -77,6 +77,11 @@
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138"
integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==

"@types/json-bigint@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@types/json-bigint/-/json-bigint-1.0.4.tgz#250d29e593375499d8ba6efaab22d094c3199ef3"
integrity sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==

"@types/node@*", "@types/node@^17.0.43":
version "17.0.43"
resolved "https://registry.npmjs.org/@types/node/-/node-17.0.43.tgz"
Expand Down Expand Up @@ -170,6 +175,11 @@ base64-js@^1.3.1:
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==

bignumber.js@^9.0.0:
version "9.1.2"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==

bl@^4.0.2:
version "4.1.0"
resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz"
Expand Down Expand Up @@ -449,6 +459,13 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"

json-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
dependencies:
bignumber.js "^9.0.0"

json-parse-better-errors@^1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz"
Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -45,6 +45,7 @@
"element-ui": "^2.15.5",
"fs-extra": "^8.1.0",
"js-yaml": "^4.1.0",
"json-bigint": "^1.0.0",
"json2csv": "^5.0.3",
"log4js": "^6.4.0",
"moment": "^2.29.4",
Expand Down Expand Up @@ -78,6 +79,7 @@
"@types/electron-devtools-installer": "^2.2.0",
"@types/fs-extra": "^8.0.0",
"@types/js-yaml": "^4.0.5",
"@types/json-bigint": "^1.0.4",
"@types/json2csv": "^5.0.3",
"@types/lodash": "^4.14.142",
"@types/mocha": "^5.2.7",
Expand Down
6 changes: 4 additions & 2 deletions src/utils/convertPayload.ts
@@ -1,3 +1,5 @@
import { jsonParse, jsonStringify } from './jsonUtils'

interface CodeType {
encode: (str: string) => string
decode: (str: string) => string
Expand Down Expand Up @@ -32,8 +34,8 @@ const convertHex = (value: string, codeType: 'encode' | 'decode'): string => {
const convertJSON = (value: string): Promise<string> =>
new Promise((resolve, reject) => {
try {
let $json = JSON.parse(value)
$json = JSON.stringify($json, null, 2)
let $json = jsonParse(value)
$json = jsonStringify($json, null, 2)
return resolve($json)
} catch (error) {
return reject(error)
Expand Down
24 changes: 24 additions & 0 deletions src/utils/jsonUtils.ts
@@ -0,0 +1,24 @@
const JSONBigNumber = require('json-bigint')
const JSONBigInt = require('json-bigint')({ useNativeBigInt: true })

export const jsonParse: typeof JSON.parse = (...args: any[]) => {
try {
// When JSON only contains integers, use the native bigint type.
return JSONBigInt.parse(...args)
} catch (_error) {
// When JSON contains floating-point numbers, use the bignumber library.
// The numbers in the parsed JSON Object will be represented as BigNumber Objects.
return JSONBigNumber.parse(...args)
}
}

export const jsonStringify: typeof JSON.stringify = (...args: any[]) => {
try {
// When JSON only contains integers, use the native bigint type.
return JSONBigInt.stringify(...args)
} catch (_error) {
// When JSON contains floating-point numbers, use the bignumber library.
// Integers will be represented in scientific notation.
return JSONBigNumber.stringify(...args)
}
}
6 changes: 4 additions & 2 deletions src/utils/validFormatJson.ts
@@ -1,8 +1,10 @@
import { jsonParse, jsonStringify } from './jsonUtils'

export default (jsonStrValue: string) => {
try {
const jsonValue = JSON.parse(jsonStrValue)
const jsonValue = jsonParse(jsonStrValue)
if (jsonValue) {
return JSON.stringify(jsonValue, null, 2)
return jsonStringify(jsonValue, null, 2)
}
return undefined
} catch (error) {
Expand Down
8 changes: 4 additions & 4 deletions src/views/connections/ConnectionsDetail.vue
Expand Up @@ -308,7 +308,7 @@ import useServices from '@/database/useServices'
import { getMessageId, getSubscriptionId } from '@/utils/idGenerator'
import getContextmenuPosition from '@/utils/getContextmenuPosition'
import { deserializeBufferToProtobuf, printObjectAsString, serializeProtobufToBuffer } from '@/utils/protobuf'

import { jsonParse, jsonStringify } from '@/utils/jsonUtils'
type CommandType =
| 'searchContent'
| 'clearHistory'
Expand Down Expand Up @@ -696,7 +696,7 @@ export default class ConnectionsDetail extends Vue {
this.$message.success(this.$tc('common.deleteSuccess'))
this.$emit('reload')
this.$log.info(
`Delete message success, Name: ${this.record.name} ClientID: ${this.record.clientId}, Payload: ${JSON.stringify(
`Delete message success, Name: ${this.record.name} ClientID: ${this.record.clientId}, Payload: ${jsonStringify(
res.payload,
)}`,
)
Expand Down Expand Up @@ -1227,7 +1227,7 @@ export default class ConnectionsDetail extends Vue {
this.$log.info(`Message Arrived with topic: ${topic}`)
let receivedLog = `${this.record.name} message arrived: message added "${
message.id
}" and added to topic: "${topic}", payload: ${JSON.stringify(
}" and added to topic: "${topic}", payload: ${jsonStringify(
message.payload,
)} MQTT.js onMessageArrived trigger`
if (this.record.mqttVersion === '5.0') {
Expand Down Expand Up @@ -1559,7 +1559,7 @@ export default class ConnectionsDetail extends Vue {
* Logs details of a successfully published message.
*/
private logSuccessfulPublish(publishMessage: MessageModel) {
const logPayload = JSON.stringify(publishMessage.payload)
const logPayload = jsonStringify(publishMessage.payload)
let pubLog = `${this.record.name} successfully published message ${logPayload} to topic "${publishMessage.topic}"`
if (this.record.mqttVersion === '5.0') {
const logProperties = JSON.stringify(publishMessage.properties)
Expand Down
2 changes: 2 additions & 0 deletions web/package.json
Expand Up @@ -18,6 +18,7 @@
"core-js": "~2.6.11",
"element-ui": "^2.13.0",
"fs-extra": "^8.1.0",
"json-bigint": "^1.0.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"moment": "^2.29.4",
Expand All @@ -41,6 +42,7 @@
"@fullhuman/vue-cli-plugin-purgecss": "~2.2.0",
"@types/chai": "^4.1.0",
"@types/fs-extra": "^8.0.0",
"@types/json-bigint": "^1.0.4",
"@types/lodash": "^4.14.142",
"@types/lowdb": "^1.0.9",
"@types/mocha": "^5.2.7",
Expand Down
6 changes: 4 additions & 2 deletions web/src/utils/convertPayload.ts
@@ -1,3 +1,5 @@
import { jsonParse, jsonStringify } from './jsonUtils'

interface CodeType {
encode: (str: string) => string
decode: (str: string) => string
Expand Down Expand Up @@ -30,8 +32,8 @@ const convertHex = (value: string, codeType: 'encode' | 'decode'): string => {
const convertJSON = (value: string): Promise<string> => {
return new Promise((resolve, reject) => {
try {
let $json = JSON.parse(value)
$json = JSON.stringify($json, null, 2)
let $json = jsonParse(value)
$json = jsonStringify($json, null, 2)
return resolve($json)
} catch (error) {
return reject(error)
Expand Down
24 changes: 24 additions & 0 deletions web/src/utils/jsonUtils.ts
@@ -0,0 +1,24 @@
const JSONBigNumber = require('json-bigint')
const JSONBigInt = require('json-bigint')({ useNativeBigInt: true })

export const jsonParse: typeof JSON.parse = (...args: any[]) => {
try {
// When JSON only contains integers, use the native bigint type.
return JSONBigInt.parse(...args)
} catch (_error) {
// When JSON contains floating-point numbers, use the bignumber library.
// The numbers in the parsed JSON Object will be represented as BigNumber Objects.
return JSONBigNumber.parse(...args)
}
}

export const jsonStringify: typeof JSON.stringify = (...args: any[]) => {
try {
// When JSON only contains integers, use the native bigint type.
return JSONBigInt.stringify(...args)
} catch (_error) {
// When JSON contains floating-point numbers, use the bignumber library.
// Integers will be represented in scientific notation.
return JSONBigNumber.stringify(...args)
}
}
5 changes: 3 additions & 2 deletions web/src/utils/validFormatJson.ts
@@ -1,11 +1,12 @@
import { Message } from 'element-ui'
import { TranslateResult } from 'vue-i18n'
import { jsonParse, jsonStringify } from './jsonUtils'

export default (jsonStrValue: string, warnMessage?: TranslateResult) => {
try {
const jsonValue = JSON.parse(jsonStrValue)
const jsonValue = jsonParse(jsonStrValue)
if (jsonValue) {
return JSON.stringify(jsonValue, null, 2)
return jsonStringify(jsonValue, null, 2)
}
return undefined
} catch (error) {
Expand Down
5 changes: 3 additions & 2 deletions web/src/views/connections/ConnectionsDetail.vue
Expand Up @@ -214,6 +214,7 @@ import connectionMessageService from '@/utils/api/connectionMessageService.ts'
import { hasMessagePayloadID, hasMessageHeaderID } from '@/utils/historyRecordUtils'
import historyMessageHeaderService from '@/utils/api/historyMessageHeaderService'
import historyMessagePayloadService from '@/utils/api/historyMessagePayloadService'
import { jsonParse, jsonStringify } from '@/utils/jsonUtils'

type MessageType = 'all' | 'received' | 'publish'
type CommandType = 'searchByTopic' | 'clearHistory' | 'disconnect' | 'deleteConnect'
Expand Down Expand Up @@ -910,7 +911,7 @@ export default class ConnectionsDetail extends Vue {
private convertPayloadByType(value: Buffer | string, type: PayloadType, way: 'publish' | 'receive'): Buffer | string {
const validJSONType = (jsonValue: string, warnMessage: TranslateResult) => {
try {
return JSON.parse(jsonValue)
return jsonParse(jsonValue)
} catch (error) {
this.$message.warning(`${warnMessage} ${error.toString()}`)
return false
Expand All @@ -934,7 +935,7 @@ export default class ConnectionsDetail extends Vue {
if (receiveType === 'JSON') {
const jsonValue = validJSONType(receiveValue.toString(), this.$t('connections.receivedMsg'))
if (jsonValue) {
return JSON.stringify(jsonValue, null, 2)
return jsonStringify(jsonValue, null, 2)
}
}
return receiveValue.toString()
Expand Down
17 changes: 17 additions & 0 deletions web/yarn.lock
Expand Up @@ -1008,6 +1008,11 @@
"@types/minimatch" "*"
"@types/node" "*"

"@types/json-bigint@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@types/json-bigint/-/json-bigint-1.0.4.tgz#250d29e593375499d8ba6efaab22d094c3199ef3"
integrity sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==

"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
Expand Down Expand Up @@ -2045,6 +2050,11 @@ big.js@^5.2.2:
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==

bignumber.js@^9.0.0:
version "9.1.2"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==

binary-extensions@^1.0.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
Expand Down Expand Up @@ -5792,6 +5802,13 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==

json-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
dependencies:
bignumber.js "^9.0.0"

json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
Expand Down