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 home grown JSON serialization #663

Merged
merged 1 commit into from
Mar 18, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this should have stayd as toJson to support circularity.

return [logRecordJson];
};

Expand Down
74 changes: 34 additions & 40 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,32 +10103,6 @@
}
});

// ../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]);
}
seenObjects.delete(value);
return newValue;
}
return internalDecircular(object);
}
var init_decircular = __esm({
"../node_modules/decircular/index.js"() {
}
});

// ../howdju-common/lib/general.ts
function mapValuesDeep(obj, fn2, options = { mapArrays: true }, key) {
const { mapArrays } = options;
Expand Down Expand Up @@ -10204,6 +10178,30 @@
return void 0;
};
}
function toJson(val) {
const seen = /* @__PURE__ */ new WeakSet();
return JSON.stringify(val, handleCircularReferences(seen));
}
function handleCircularReferences(seen) {
return function(_key, value) {
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 = Array.isArray(value) ? [] : {};
for (const [key, val] of Object.entries(value)) {
newValue[key] = handleCircularReferences(seen)(key, val);
}
seen.delete(value);
return newValue;
};
}
function toSlug(text) {
if (!text) {
return text;
Expand Down Expand Up @@ -10241,14 +10239,13 @@
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_commonErrors();
mapKeysDeep = (obj, fn2, parentKey = void 0) => {
if ((0, import_lodash2.isArray)(obj)) {
Expand Down Expand Up @@ -10413,9 +10410,6 @@
},
{}
);
toJson = function toJson2(val, replacer) {
return JSON.stringify(decircular(val), replacer);
};
fromJson = function fromJson2(json) {
return JSON.parse(json);
};
Expand Down Expand Up @@ -27744,8 +27738,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 +27750,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 +27763,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
Loading