diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html
index 513e8f18e..a3f80c7e8 100644
--- a/docs/generated/changelog.html
+++ b/docs/generated/changelog.html
@@ -12,6 +12,7 @@
Agent-JS Changelog
Version x.x.x
+ - fix: Principal JSON is compatible with @dfinity/utils jsonReviver helper
- chore: npm audit
- feat: Principal class serializes to JSON
-
diff --git a/package-lock.json b/package-lock.json
index 7f05ce978..9d092c595 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -771,6 +771,17 @@
"resolved": "packages/principal",
"link": true
},
+ "node_modules/@dfinity/utils": {
+ "version": "0.0.22",
+ "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-0.0.22.tgz",
+ "integrity": "sha512-NmMyNik0Lz5QFil+9BSfKOsCaUIWqwMsjxkyknfBOh12dAltDxc9xvyZmYh7Ug/0Bpw6qQEZMcCjnXce61MARQ==",
+ "dev": true,
+ "peerDependencies": {
+ "@dfinity/agent": "^0.19.2",
+ "@dfinity/candid": "^0.19.2",
+ "@dfinity/principal": "^0.19.2"
+ }
+ },
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"dev": true,
@@ -17736,6 +17747,7 @@
"simple-cbor": "^0.4.1"
},
"devDependencies": {
+ "@dfinity/utils": "^0.0.22",
"@peculiar/webcrypto": "^1.4.3",
"@trust/webcrypto": "^0.9.2",
"@types/jest": "^28.1.4",
diff --git a/packages/agent/package.json b/packages/agent/package.json
index 11ac5346d..4d47b48a5 100644
--- a/packages/agent/package.json
+++ b/packages/agent/package.json
@@ -60,6 +60,7 @@
"simple-cbor": "^0.4.1"
},
"devDependencies": {
+ "@dfinity/utils": "^0.0.22",
"@peculiar/webcrypto": "^1.4.3",
"@trust/webcrypto": "^0.9.2",
"@types/jest": "^28.1.4",
diff --git a/packages/agent/src/canisterStatus/__snapshots__/index.test.ts.snap b/packages/agent/src/canisterStatus/__snapshots__/index.test.ts.snap
index d502985f8..c91341cce 100644
--- a/packages/agent/src/canisterStatus/__snapshots__/index.test.ts.snap
+++ b/packages/agent/src/canisterStatus/__snapshots__/index.test.ts.snap
@@ -2,7 +2,9 @@
exports[`Canister Status utility should query canister controllers 1`] = `
Array [
- "rwlgt-iiaaa-aaaaa-aaaaa-cai",
+ Object {
+ "__principal__": "rwlgt-iiaaa-aaaaa-aaaaa-cai",
+ },
]
`;
@@ -22,7 +24,9 @@ exports[`Canister Status utility should support multiple requests 1`] = `2022-05
exports[`Canister Status utility should support multiple requests 2`] = `
Array [
- "rwlgt-iiaaa-aaaaa-aaaaa-cai",
+ Object {
+ "__principal__": "rwlgt-iiaaa-aaaaa-aaaaa-cai",
+ },
]
`;
diff --git a/packages/principal/src/index.test.ts b/packages/principal/src/index.test.ts
index 92a9d510d..18b230ef1 100644
--- a/packages/principal/src/index.test.ts
+++ b/packages/principal/src/index.test.ts
@@ -1,4 +1,5 @@
import { Principal } from '.';
+import { jsonReviver } from '@dfinity/utils';
describe('Principal', () => {
it('encodes properly', () => {
@@ -50,6 +51,20 @@ describe('Principal', () => {
it('serializes to JSON', () => {
const principal = Principal.fromText('ryjl3-tyaaa-aaaaa-aaaba-cai');
- expect(JSON.stringify(principal)).toBe('"ryjl3-tyaaa-aaaaa-aaaba-cai"');
+
+ const json = principal.toJSON();
+ expect(json).toEqual({ __principal__: 'ryjl3-tyaaa-aaaaa-aaaba-cai' });
+
+ const stringified = JSON.stringify(principal);
+ expect(stringified).toEqual('{"__principal__":"ryjl3-tyaaa-aaaaa-aaaba-cai"}');
+
+ expect(JSON.parse(stringified, jsonReviver)).toEqual(principal);
+ });
+
+ it('serializes from JSON string', () => {
+ const principal = Principal.fromText('ryjl3-tyaaa-aaaaa-aaaba-cai');
+ const json = JSON.stringify(principal);
+ json;
+ expect(Principal.from(json)).toEqual(principal);
});
});
diff --git a/packages/principal/src/index.ts b/packages/principal/src/index.ts
index 92c19045f..fab88354e 100644
--- a/packages/principal/src/index.ts
+++ b/packages/principal/src/index.ts
@@ -2,6 +2,7 @@ import { decode, encode } from './utils/base32';
import { getCrc32 } from './utils/getCrc';
import { sha224 } from './utils/sha224';
+export const JSON_KEY_PRINCIPAL = '__principal__';
const SELF_AUTHENTICATING_SUFFIX = 2;
const ANONYMOUS_SUFFIX = 4;
@@ -13,6 +14,10 @@ const fromHexString = (hexString: string) =>
const toHexString = (bytes: Uint8Array) =>
bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
+export type JsonnablePrincipal = {
+ [JSON_KEY_PRINCIPAL]: string;
+};
+
export class Principal {
public static anonymous(): Principal {
return new this(new Uint8Array([ANONYMOUS_SUFFIX]));
@@ -50,15 +55,24 @@ export class Principal {
}
public static fromText(text: string): Principal {
- const canisterIdNoDash = text.toLowerCase().replace(/-/g, '');
+ let maybePrincipal = text;
+ // If formatted as JSON string, parse it first
+ if (text.includes(JSON_KEY_PRINCIPAL)) {
+ const obj = JSON.parse(text);
+ if (JSON_KEY_PRINCIPAL in obj) {
+ maybePrincipal = obj[JSON_KEY_PRINCIPAL];
+ }
+ }
+
+ const canisterIdNoDash = maybePrincipal.toLowerCase().replace(/-/g, '');
let arr = decode(canisterIdNoDash);
arr = arr.slice(4, arr.length);
const principal = new this(arr);
- if (principal.toText() !== text) {
+ if (principal.toText() !== maybePrincipal) {
throw new Error(
- `Principal "${principal.toText()}" does not have a valid checksum (original value "${text}" may not be a valid Principal ID).`,
+ `Principal "${principal.toText()}" does not have a valid checksum (original value "${maybePrincipal}" may not be a valid Principal ID).`,
);
}
@@ -109,10 +123,10 @@ export class Principal {
/**
* Serializes to JSON
- * @returns {string} string
+ * @returns {JsonnablePrincipal} a JSON object with a single key, {@link JSON_KEY_PRINCIPAL}, whose value is the principal as a string
*/
- public toJSON(): string {
- return this.toText();
+ public toJSON(): JsonnablePrincipal {
+ return { [JSON_KEY_PRINCIPAL]: this.toText() };
}
/**