encodeFunctionJson function
Encodes finite plain JSON only; never invokes a user object's toJson hook.
The bytes are exactly those the TypeScript encodeFunctionJson
(JSON.stringify) writes for the same value, so the emulator and the
gateway re-encode a result identically (functions/02 ยง8): numbers follow
functionJsonNumber, and an object's array-index keys (canonical integers
up to 2^32 - 2) come first in ascending order, then its other keys in
insertion order, as ECMAScript orders own properties. Strings keep
jsonEncode's escaping, which JSON.stringify shares.
Implementation
Uint8List encodeFunctionJson(
Object? value, {
int maxBytes = 2097152,
int maxDepth = 12,
}) {
if (maxDepth < 1 || maxDepth > 64) {
throw ArgumentError.value(maxDepth, 'maxDepth');
}
var nodes = 0;
final ancestors = <Object>{};
void visit(Object? value, int depth) {
if (++nodes > 100000 || depth > maxDepth) {
throw const FormatException('JSON nesting or node limit exceeded.');
}
if (value == null || value is bool) return;
if (value is String && _wellFormed(value)) return;
if (value is num &&
value.isFinite &&
_decimalIdentity(value.toString()) ==
_decimalIdentity(value.toDouble().toString())) {
return;
}
if (value is Map || value is List) {
if (!ancestors.add(value)) {
throw const FormatException('JSON must not contain cycles.');
}
if (value is List) {
for (final item in value) {
visit(item, depth + 1);
}
} else {
for (final entry in (value as Map).entries) {
if (entry.key is! String || !_wellFormed(entry.key as String)) {
throw const FormatException('Invalid JSON key.');
}
visit(entry.value, depth + 1);
}
}
ancestors.remove(value);
return;
}
throw const FormatException('Value is not finite scalar JSON.');
}
visit(value, 0);
final text = StringBuffer();
_writeFunctionJson(text, value);
final bytes = Uint8List.fromList(utf8.encode(text.toString()));
parseFunctionJson(bytes, maxBytes: maxBytes, maxDepth: maxDepth);
return bytes;
}