UNPKG

4.66 kBJavaScriptView Raw
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 *
8 * @format
9 */
10'use strict';
11/**
12 * This function works similar to JSON.stringify except that for the case there
13 * are multiple common subtrees, it generates a string for a IIFE that re-uses
14 * the same objects for the duplicate subtrees.
15 */
16
17function dedupeJSONStringify(jsonValue) {
18 // Clone the object to convert references to the same object instance into
19 // copies. This is needed for the WeakMap/Map to recognize them as duplicates.
20 jsonValue = JSON.parse(JSON.stringify(jsonValue));
21 var metadataForHash = new Map();
22 var metadataForVal = new WeakMap();
23 var varDefs = [];
24 collectMetadata(jsonValue);
25 collectDuplicates(jsonValue);
26 var code = printJSCode(false, '', jsonValue);
27 return varDefs.length === 0 ? code : "(function(){\nvar ".concat(varDefs.join(',\n'), ";\nreturn ").concat(code, ";\n})()"); // Collect common metadata for each object in the value tree, ensuring that
28 // equivalent values have the *same reference* to the same metadata. Note that
29 // the hashes generated are not exactly JSON, but still identify equivalent
30 // values. Runs in linear time due to hashing in a bottom-up recursion.
31
32 function collectMetadata(value) {
33 if (value == null || typeof value !== 'object') {
34 return JSON.stringify(value);
35 }
36
37 var hash;
38
39 if (Array.isArray(value)) {
40 hash = '[';
41
42 for (var i = 0; i < value.length; i++) {
43 hash += collectMetadata(value[i]) + ',';
44 }
45 } else {
46 hash = '{';
47
48 for (var k in value) {
49 if (value.hasOwnProperty(k) && value[k] !== undefined) {
50 hash += k + ':' + collectMetadata(value[k]) + ',';
51 }
52 }
53 }
54
55 var metadata = metadataForHash.get(hash);
56
57 if (!metadata) {
58 metadata = {
59 value: value,
60 hash: hash,
61 isDuplicate: false
62 };
63 metadataForHash.set(hash, metadata);
64 }
65
66 metadataForVal.set(value, metadata);
67 return hash;
68 } // Using top-down recursion, linearly scan the JSON tree to determine which
69 // values should be deduplicated.
70
71
72 function collectDuplicates(value) {
73 if (value == null || typeof value !== 'object') {
74 return;
75 }
76
77 var metadata = metadataForVal.get(value); // Only consider duplicates with hashes longer than 2 (excludes [] and {}).
78
79 if (metadata && metadata.value !== value && metadata.hash.length > 2) {
80 metadata.isDuplicate = true;
81 return;
82 }
83
84 if (Array.isArray(value)) {
85 for (var i = 0; i < value.length; i++) {
86 collectDuplicates(value[i]);
87 }
88 } else {
89 for (var k in value) {
90 if (value.hasOwnProperty(k) && value[k] !== undefined) {
91 collectDuplicates(value[k]);
92 }
93 }
94 }
95 } // Stringify JS, replacing duplicates with variable references.
96
97
98 function printJSCode(isDupedVar, depth, value) {
99 if (value == null || typeof value !== 'object') {
100 return JSON.stringify(value);
101 } // Only use variable references at depth beyond the top level.
102
103
104 if (depth !== '') {
105 var metadata = metadataForVal.get(value);
106
107 if (metadata && metadata.isDuplicate) {
108 if (!metadata.varName) {
109 var refCode = printJSCode(true, '', value);
110 metadata.varName = 'v' + varDefs.length;
111 varDefs.push(metadata.varName + ' = ' + refCode);
112 }
113
114 return '(' + metadata.varName + '/*: any*/)';
115 }
116 }
117
118 var str;
119 var isEmpty = true;
120 var depth2 = depth + ' ';
121
122 if (Array.isArray(value)) {
123 // Empty arrays can only have one inferred flow type and then conflict if
124 // used in different places, this is unsound if we would write to them but
125 // this whole module is based on the idea of a read only JSON tree.
126 if (isDupedVar && value.length === 0) {
127 return '([]/*: any*/)';
128 }
129
130 str = '[';
131
132 for (var i = 0; i < value.length; i++) {
133 str += (isEmpty ? '\n' : ',\n') + depth2 + printJSCode(isDupedVar, depth2, value[i]);
134 isEmpty = false;
135 }
136
137 str += isEmpty ? ']' : "\n".concat(depth, "]");
138 } else {
139 str = '{';
140
141 for (var k in value) {
142 if (value.hasOwnProperty(k) && value[k] !== undefined) {
143 str += (isEmpty ? '\n' : ',\n') + depth2 + JSON.stringify(k) + ': ' + printJSCode(isDupedVar, depth2, value[k]);
144 isEmpty = false;
145 }
146 }
147
148 str += isEmpty ? '}' : "\n".concat(depth, "}");
149 }
150
151 return str;
152 }
153}
154
155module.exports = dedupeJSONStringify;
\No newline at end of file