Skip to content

Commit

Permalink
Fix home grown JSON serialization
Browse files Browse the repository at this point in the history
Signed-off-by: Carl Gieringer <78054+carlgieringer@users.noreply.github.com>
  • Loading branch information
carlgieringer committed Mar 18, 2024
1 parent 95a8b7b commit 8be44f5
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 62 deletions.
1 change: 0 additions & 1 deletion babel-node-modules-opt-in.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Patterns matching node_modules dependencies that Babel and Jest should transform
approx-string-match
decircular
@grrr/cookie-consent
@grrr/utils
is-absolute-url
Expand Down
6 changes: 5 additions & 1 deletion howdju-common/lib/general.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,15 @@ describe("toJson", () => {
test("handles circular references", () => {
const obj: any = { a: 1, b: "2" };
obj.c = { d: obj };
expect(toJson(obj)).toBe('{"a":1,"b":"2","c":{"d":"[Circular *]"}}');
expect(toJson(obj)).toBe('{"a":1,"b":"2","c":{"d":"[Circular]"}}');
});
test("does not consider all identical fields circular", () => {
const child = { d: 3 };
const obj: any = { a: child, b: child };
expect(toJson(obj)).toBe('{"a":{"d":3},"b":{"d":3}}');
});
test("serializes moments as ISO strings", () => {
const obj: any = { a: moment.utc("2022-11-08T21:44:00") };
expect(toJson(obj)).toBe('{"a":"2022-11-08T21:44:00.000Z"}');
});
});
39 changes: 32 additions & 7 deletions howdju-common/lib/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
} from "lodash";
import moment, { Moment, unitOfTime, Duration, TemplateFunction } from "moment";
import isAbsoluteUrlLib from "is-absolute-url";
import decircular from "decircular";

import { newProgrammingError } from "./commonErrors";
import { CamelCasedPropertiesDeep, MergeDeep } from "type-fest";
Expand Down Expand Up @@ -455,12 +454,38 @@ export const keysTo = (obj: { [k: string]: any }, val: any) =>
{} as { [k: string]: typeof val }
);

export const toJson = function toJson(
val: any,
replacer?: (key: string, value: any) => any
) {
return JSON.stringify(decircular(val), replacer);
};
export function toJson(val: any) {
const seen = new WeakSet();
return JSON.stringify(val, handleCircularReferences(seen));
}

function handleCircularReferences(seen: WeakSet<any>) {
return function (_key: string, value: any) {
if (value === null || typeof value !== "object") {
return value;
}

if (seen.has(value)) {
return "[Circular]";
}

if (value.toJSON) {
return value.toJSON();
}

seen.add(value);

const newValue: Record<string, any> = Array.isArray(value) ? [] : {};

for (const [key, val] of Object.entries(value)) {
newValue[key] = handleCircularReferences(seen)(key, val);
}

seen.delete(value);

return newValue;
};
}

export const fromJson = function fromJson(json: string) {
return JSON.parse(json);
Expand Down
1 change: 0 additions & 1 deletion howdju-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"ajv": "^8.1.0",
"ajv-formats": "^2.0.2",
"approx-string-match": "^2.0.0",
"decircular": "^1.0.0",
"dom-anchor-text-position": "^5.0.0",
"dom-anchor-text-quote": "^4.0.2",
"is-absolute-url": "^4.0.1",
Expand Down
1 change: 0 additions & 1 deletion howdju-mobile-app/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { merge } from "lodash";
import baseConfig from "../jest.config.base";
const transformOptInPatterns = [
"approx-string-match",
"decircular",
"is-absolute-url",
"(@|jest-)?react-native",
"normalize-url",
Expand Down
4 changes: 2 additions & 2 deletions howdju-service-common/lib/logging/AwsLogger.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const join = require("lodash/join");
const map = require("lodash/map");
const mapValues = require("lodash/mapValues");

const { utcTimestamp, toJson } = require("howdju-common");
const { utcTimestamp } = require("howdju-common");

const { processArgs } = require("./processArgs");

Expand Down Expand Up @@ -91,7 +91,7 @@ const makeJsonLogArguments = function (logLevel, logLevelNumber, ...args) {
logRecord["data"] = data;
}

const logRecordJson = toJson(logRecord, jsonStringifyReplacer);
const logRecordJson = JSON.stringify(logRecord, jsonStringifyReplacer);
return [logRecordJson];
};

Expand Down
83 changes: 42 additions & 41 deletions howdju-text-fragments/dist/rangeToFragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -1349,10 +1349,10 @@
}();
var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout, ctxNow = Date2 && Date2.now !== root.Date.now && Date2.now, ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout;
var nativeCeil = Math2.ceil, nativeFloor = Math2.floor, nativeGetSymbols = Object2.getOwnPropertySymbols, nativeIsBuffer = Buffer2 ? Buffer2.isBuffer : undefined2, nativeIsFinite = context.isFinite, nativeJoin = arrayProto.join, nativeKeys = overArg(Object2.keys, Object2), nativeMax = Math2.max, nativeMin = Math2.min, nativeNow = Date2.now, nativeParseInt = context.parseInt, nativeRandom = Math2.random, nativeReverse = arrayProto.reverse;
var DataView = getNative(context, "DataView"), Map2 = getNative(context, "Map"), Promise2 = getNative(context, "Promise"), Set2 = getNative(context, "Set"), WeakMap2 = getNative(context, "WeakMap"), nativeCreate = getNative(Object2, "create");
var metaMap = WeakMap2 && new WeakMap2();
var DataView = getNative(context, "DataView"), Map2 = getNative(context, "Map"), Promise2 = getNative(context, "Promise"), Set2 = getNative(context, "Set"), WeakMap = getNative(context, "WeakMap"), nativeCreate = getNative(Object2, "create");
var metaMap = WeakMap && new WeakMap();
var realNames = {};
var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map2), promiseCtorString = toSource(Promise2), setCtorString = toSource(Set2), weakMapCtorString = toSource(WeakMap2);
var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map2), promiseCtorString = toSource(Promise2), setCtorString = toSource(Set2), weakMapCtorString = toSource(WeakMap);
var symbolProto = Symbol2 ? Symbol2.prototype : undefined2, symbolValueOf = symbolProto ? symbolProto.valueOf : undefined2, symbolToString = symbolProto ? symbolProto.toString : undefined2;
function lodash(value) {
if (isObjectLike(value) && !isArray3(value) && !(value instanceof LazyWrapper)) {
Expand Down Expand Up @@ -3406,7 +3406,7 @@
return result2;
};
var getTag = baseGetTag;
if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map2 && getTag(new Map2()) != mapTag || Promise2 && getTag(Promise2.resolve()) != promiseTag || Set2 && getTag(new Set2()) != setTag || WeakMap2 && getTag(new WeakMap2()) != weakMapTag) {
if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map2 && getTag(new Map2()) != mapTag || Promise2 && getTag(Promise2.resolve()) != promiseTag || Set2 && getTag(new Set2()) != setTag || WeakMap && getTag(new WeakMap()) != weakMapTag) {
getTag = function(value) {
var result2 = baseGetTag(value), Ctor = result2 == objectTag ? value.constructor : undefined2, ctorString = Ctor ? toSource(Ctor) : "";
if (ctorString) {
Expand Down Expand Up @@ -10103,29 +10103,30 @@
}
});

// ../node_modules/decircular/index.js
function decircular(object) {
const seenObjects = /* @__PURE__ */ new WeakMap();
function internalDecircular(value, path = []) {
if (!(value !== null && typeof value === "object")) {
return value;
}
const existingPath = seenObjects.get(value);
if (existingPath) {
return `[Circular *${existingPath.join(".")}]`;
}
seenObjects.set(value, path);
const newValue = Array.isArray(value) ? [] : {};
for (const [key2, value2] of Object.entries(value)) {
newValue[key2] = internalDecircular(value2, [...path, key2]);
// ../node_modules/safe-stringify/index.js
function safeStringifyReplacer(seen) {
return function(key, value) {
if (value !== null && typeof value === "object") {
if (seen.has(value)) {
return "[Circular]";
}
seen.add(value);
const newValue = Array.isArray(value) ? [] : {};
for (const [key2, value2] of Object.entries(value)) {
newValue[key2] = safeStringifyReplacer(seen)(key2, value2);
}
seen.delete(value);
return newValue;
}
seenObjects.delete(value);
return newValue;
}
return internalDecircular(object);
return value;
};
}
function safeStringify(object, { indentation } = {}) {
const seen = /* @__PURE__ */ new WeakSet();
return JSON.stringify(object, safeStringifyReplacer(seen), indentation);
}
var init_decircular = __esm({
"../node_modules/decircular/index.js"() {
var init_safe_stringify = __esm({
"../node_modules/safe-stringify/index.js"() {
}
});

Expand Down Expand Up @@ -10204,6 +10205,9 @@
return void 0;
};
}
function toJson(val) {
return safeStringify(val);
}
function toSlug(text) {
if (!text) {
return text;
Expand Down Expand Up @@ -10241,14 +10245,14 @@
function isAbsoluteUrl2(val) {
return isAbsoluteUrl(val);
}
var import_lodash2, import_moment, mapKeysDeep, minDate, zeroDate, isTruthy, isFalsey, assert, isDefined, utcNow, momentAdd, momentSubtract, differenceDuration, formatDuration, timestampFormatString, utcTimestamp, arrayToObject, pushAll, insertAt, insertAllAt, removeAt, encodeQueryStringObject, encodeSorts, decodeSorts, toSingleLine, omitDeep, keysTo, toJson, fromJson, cleanWhitespace, normalizeText, toEntries;
var import_lodash2, import_moment, mapKeysDeep, minDate, zeroDate, isTruthy, isFalsey, assert, isDefined, utcNow, momentAdd, momentSubtract, differenceDuration, formatDuration, timestampFormatString, utcTimestamp, arrayToObject, pushAll, insertAt, insertAllAt, removeAt, encodeQueryStringObject, encodeSorts, decodeSorts, toSingleLine, omitDeep, keysTo, fromJson, cleanWhitespace, normalizeText, toEntries;
var init_general = __esm({
"../howdju-common/lib/general.ts"() {
"use strict";
import_lodash2 = __toESM(require_lodash());
import_moment = __toESM(require_moment());
init_is_absolute_url();
init_decircular();
init_safe_stringify();
init_commonErrors();
mapKeysDeep = (obj, fn2, parentKey = void 0) => {
if ((0, import_lodash2.isArray)(obj)) {
Expand Down Expand Up @@ -10413,9 +10417,6 @@
},
{}
);
toJson = function toJson2(val, replacer) {
return JSON.stringify(decircular(val), replacer);
};
fromJson = function fromJson2(json) {
return JSON.parse(json);
};
Expand Down Expand Up @@ -19797,12 +19798,12 @@
exports._ = _3;
var plus = new _Code("+");
function str2(strs, ...args) {
const expr = [safeStringify(strs[0])];
const expr = [safeStringify2(strs[0])];
let i3 = 0;
while (i3 < args.length) {
expr.push(plus);
addCodeArg(expr, args[i3]);
expr.push(plus, safeStringify(strs[++i3]));
expr.push(plus, safeStringify2(strs[++i3]));
}
optimize(expr);
return new _Code(expr);
Expand Down Expand Up @@ -19854,16 +19855,16 @@
}
exports.strConcat = strConcat;
function interpolate(x3) {
return typeof x3 == "number" || typeof x3 == "boolean" || x3 === null ? x3 : safeStringify(Array.isArray(x3) ? x3.join(",") : x3);
return typeof x3 == "number" || typeof x3 == "boolean" || x3 === null ? x3 : safeStringify2(Array.isArray(x3) ? x3.join(",") : x3);
}
function stringify(x3) {
return new _Code(safeStringify(x3));
return new _Code(safeStringify2(x3));
}
exports.stringify = stringify;
function safeStringify(x3) {
function safeStringify2(x3) {
return JSON.stringify(x3).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
}
exports.safeStringify = safeStringify;
exports.safeStringify = safeStringify2;
function getProperty(key) {
return typeof key == "string" && exports.IDENTIFIER.test(key) ? new _Code(`.${key}`) : _3`[${key}]`;
}
Expand Down Expand Up @@ -27744,8 +27745,8 @@
"../node_modules/lodash/_WeakMap.js"(exports, module) {
var getNative = require_getNative();
var root = require_root();
var WeakMap2 = getNative(root, "WeakMap");
module.exports = WeakMap2;
var WeakMap = getNative(root, "WeakMap");
module.exports = WeakMap;
}
});

Expand All @@ -27756,7 +27757,7 @@
var Map2 = require_Map();
var Promise2 = require_Promise();
var Set2 = require_Set();
var WeakMap2 = require_WeakMap();
var WeakMap = require_WeakMap();
var baseGetTag = require_baseGetTag();
var toSource = require_toSource();
var mapTag = "[object Map]";
Expand All @@ -27769,9 +27770,9 @@
var mapCtorString = toSource(Map2);
var promiseCtorString = toSource(Promise2);
var setCtorString = toSource(Set2);
var weakMapCtorString = toSource(WeakMap2);
var weakMapCtorString = toSource(WeakMap);
var getTag = baseGetTag;
if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map2 && getTag(new Map2()) != mapTag || Promise2 && getTag(Promise2.resolve()) != promiseTag || Set2 && getTag(new Set2()) != setTag || WeakMap2 && getTag(new WeakMap2()) != weakMapTag) {
if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map2 && getTag(new Map2()) != mapTag || Promise2 && getTag(Promise2.resolve()) != promiseTag || Set2 && getTag(new Set2()) != setTag || WeakMap && getTag(new WeakMap()) != weakMapTag) {
getTag = function(value) {
var result = baseGetTag(value), Ctor = result == objectTag ? value.constructor : void 0, ctorString = Ctor ? toSource(Ctor) : "";
if (ctorString) {
Expand Down
8 changes: 0 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16586,13 +16586,6 @@ __metadata:
languageName: node
linkType: hard

"decircular@npm:^1.0.0":
version: 1.0.0
resolution: "decircular@npm:1.0.0"
checksum: 8710dec50e2e643e38f12866537ff3aeac60c035e662474a9ec6641050d600b68bd18bc9e46c60d8b3d23a7f989815d646ff6aa818d446451143207b898fe4ae
languageName: node
linkType: hard

"decode-named-character-reference@npm:^1.0.0":
version: 1.0.2
resolution: "decode-named-character-reference@npm:1.0.2"
Expand Down Expand Up @@ -21797,7 +21790,6 @@ __metadata:
ajv: "npm:^8.1.0"
ajv-formats: "npm:^2.0.2"
approx-string-match: "npm:^2.0.0"
decircular: "npm:^1.0.0"
dom-anchor-text-position: "npm:^5.0.0"
dom-anchor-text-quote: "npm:^4.0.2"
esbuild: "npm:^0.18.17"
Expand Down

0 comments on commit 8be44f5

Please sign in to comment.