json_canonicalizer

A dependency-free RFC 8785 JSON Canonicalization Scheme implementation for Dart.

Install

dart pub add json_canonicalizer

Usage

Both accept an already-parsed JSON value.

  • canonicalize(value) returns canonical JSON as a String.
  • canonicalizeUtf8(value) returns the same JSON as UTF-8 bytes for hashing or signing.

Both also take allowInvalidUnicode. Pass true to escape a lone UTF-16 surrogate as \uXXXX instead of throwing. RFC 8785 requires rejecting a lone surrogate, but Dart strings can contain one from truncated or malformed input.

Examples

import 'package:json_canonicalizer/json_canonicalizer.dart';

// Sorts object keys by UTF-16 order and removes extra spaces.
canonicalize({'b': 1, '\u{e000}': 'private use', '😳': 'flushed', 'a': 2});
// {"a":2,"b":1,"😳":"flushed","":"private use"}

// Formats numbers the same way as JavaScript JSON.
canonicalize(<Object?>[-0.0, 333333333.33333329, 1e30, 0.000001]);
// [0,333333333.3333333,1e+30,0.000001]

// Returns UTF-8 bytes for hashing or signing.
canonicalizeUtf8({'currency': '€'});
// [123, 34, 99, 117, 114, 114, 101, 110, 99, 121, 34, 58, 34, 226, 130, 172, 34, 125]

// A lone surrogate throws by default.
canonicalize('a\ud800b');
// Throws JsonCanonicalizationException.

// allowInvalidUnicode escapes it instead.
canonicalize('a\ud800b', allowInvalidUnicode: true);
// "a\ud800b"

Boundaries

Pass in a JSON value that has already been parsed. It can contain null, booleans, finite numbers, valid Unicode strings, lists, and maps with string keys. This package only canonicalizes the value; it does not parse, normalize, hash, sign, or call toJson.

Case Result
Arrays Preserve their original order.
Object keys Sort by UTF-16 code units.
Strings Escape control characters, quotes, and backslashes. Keep other Unicode unchanged.
-0.0 Writes 0.
NaN, infinity, lossy int, non-string key, cycle Throw JsonCanonicalizationException.
Lone surrogate Throws, unless you pass allowInvalidUnicode.
Nested lists and maps Work up to 100,000 levels deep.

Errors point to the invalid value with an RFC 6901 path:

try {
  canonicalize({
    'payload': [
      {'a/b~c': double.nan},
    ],
  });
} on JsonCanonicalizationException catch (error) {
  print(error.path);
  // /payload/0/a~1b~0c
}

The test suite covers RFC 8785 examples, Appendix B number vectors, the upstream JCS corpus, Unicode, escaping, invalid input, cycles, shared subtrees, allowInvalidUnicode, and 100,000-level lists and maps.

License

Unlicense. Upstream test vectors in test/ are Apache-2.0.

Libraries

json_canonicalizer
RFC 8785 JSON Canonicalization Scheme for parsed I-JSON values.