blob: b8836eaddd919fb9387c962391f4075e7df8e302 [file] [log] [blame]
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001// Copyright 2009 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5(function(global, utils) {
6
7"use strict";
8
9%CheckIsBootstrapping();
10
11// -------------------------------------------------------------------
12// Imports
13
14var GlobalDate = global.Date;
15var GlobalJSON = global.JSON;
16var GlobalSet = global.Set;
17var InternalArray = utils.InternalArray;
18var MakeTypeError;
19var MaxSimple;
20var MinSimple;
21var ObjectHasOwnProperty;
22var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
23
24utils.Import(function(from) {
25 MakeTypeError = from.MakeTypeError;
26 MaxSimple = from.MaxSimple;
27 MinSimple = from.MinSimple;
28 ObjectHasOwnProperty = from.ObjectHasOwnProperty;
29});
30
31// -------------------------------------------------------------------
32
33function CreateDataProperty(o, p, v) {
34 var desc = {value: v, enumerable: true, writable: true, configurable: true};
35 return %reflect_define_property(o, p, desc);
36}
37
38
39function InternalizeJSONProperty(holder, name, reviver) {
40 var val = holder[name];
41 if (IS_RECEIVER(val)) {
42 if (%is_arraylike(val)) {
43 var length = TO_LENGTH(val.length);
44 for (var i = 0; i < length; i++) {
45 var newElement =
46 InternalizeJSONProperty(val, %_NumberToString(i), reviver);
47 if (IS_UNDEFINED(newElement)) {
48 %reflect_delete_property(val, i);
49 } else {
50 CreateDataProperty(val, i, newElement);
51 }
52 }
53 } else {
54 for (var p of %object_keys(val)) {
55 var newElement = InternalizeJSONProperty(val, p, reviver);
56 if (IS_UNDEFINED(newElement)) {
57 %reflect_delete_property(val, p);
58 } else {
59 CreateDataProperty(val, p, newElement);
60 }
61 }
62 }
63 }
64 return %_Call(reviver, holder, name, val);
65}
66
67
68function JSONParse(text, reviver) {
69 var unfiltered = %ParseJson(text);
70 if (IS_CALLABLE(reviver)) {
71 return InternalizeJSONProperty({'': unfiltered}, '', reviver);
72 } else {
73 return unfiltered;
74 }
75}
76
77
78function SerializeArray(value, replacer, stack, indent, gap) {
79 if (!%PushIfAbsent(stack, value)) throw MakeTypeError(kCircularStructure);
80 var stepback = indent;
81 indent += gap;
82 var partial = new InternalArray();
83 var len = TO_LENGTH(value.length);
84 for (var i = 0; i < len; i++) {
85 var strP = JSONSerialize(%_NumberToString(i), value, replacer, stack,
86 indent, gap);
87 if (IS_UNDEFINED(strP)) {
88 strP = "null";
89 }
90 partial.push(strP);
91 }
92 var final;
93 if (gap == "") {
94 final = "[" + partial.join(",") + "]";
95 } else if (partial.length > 0) {
96 var separator = ",\n" + indent;
97 final = "[\n" + indent + partial.join(separator) + "\n" +
98 stepback + "]";
99 } else {
100 final = "[]";
101 }
102 stack.pop();
103 return final;
104}
105
106
107function SerializeObject(value, replacer, stack, indent, gap) {
108 if (!%PushIfAbsent(stack, value)) throw MakeTypeError(kCircularStructure);
109 var stepback = indent;
110 indent += gap;
111 var partial = new InternalArray();
112 if (IS_ARRAY(replacer)) {
113 var length = replacer.length;
114 for (var i = 0; i < length; i++) {
115 var p = replacer[i];
116 var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
117 if (!IS_UNDEFINED(strP)) {
118 var member = %QuoteJSONString(p) + ":";
119 if (gap != "") member += " ";
120 member += strP;
121 partial.push(member);
122 }
123 }
124 } else {
125 for (var p of %object_keys(value)) {
126 var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
127 if (!IS_UNDEFINED(strP)) {
128 var member = %QuoteJSONString(p) + ":";
129 if (gap != "") member += " ";
130 member += strP;
131 partial.push(member);
132 }
133 }
134 }
135 var final;
136 if (gap == "") {
137 final = "{" + partial.join(",") + "}";
138 } else if (partial.length > 0) {
139 var separator = ",\n" + indent;
140 final = "{\n" + indent + partial.join(separator) + "\n" +
141 stepback + "}";
142 } else {
143 final = "{}";
144 }
145 stack.pop();
146 return final;
147}
148
149
150function JSONSerialize(key, holder, replacer, stack, indent, gap) {
151 var value = holder[key];
152 if (IS_RECEIVER(value)) {
153 var toJSON = value.toJSON;
154 if (IS_CALLABLE(toJSON)) {
155 value = %_Call(toJSON, value, key);
156 }
157 }
158 if (IS_CALLABLE(replacer)) {
159 value = %_Call(replacer, holder, key, value);
160 }
161 if (IS_STRING(value)) {
162 return %QuoteJSONString(value);
163 } else if (IS_NUMBER(value)) {
164 return JSON_NUMBER_TO_STRING(value);
165 } else if (IS_BOOLEAN(value)) {
166 return value ? "true" : "false";
167 } else if (IS_NULL(value)) {
168 return "null";
169 } else if (IS_RECEIVER(value) && !IS_CALLABLE(value)) {
170 // Non-callable object. If it's a primitive wrapper, it must be unwrapped.
171 if (%is_arraylike(value)) {
172 return SerializeArray(value, replacer, stack, indent, gap);
173 } else if (IS_NUMBER_WRAPPER(value)) {
174 value = TO_NUMBER(value);
175 return JSON_NUMBER_TO_STRING(value);
176 } else if (IS_STRING_WRAPPER(value)) {
177 return %QuoteJSONString(TO_STRING(value));
178 } else if (IS_BOOLEAN_WRAPPER(value)) {
179 return %_ValueOf(value) ? "true" : "false";
180 } else {
181 return SerializeObject(value, replacer, stack, indent, gap);
182 }
183 }
184 // Undefined or a callable object.
185 return UNDEFINED;
186}
187
188
189function JSONStringify(value, replacer, space) {
190 if (%_ArgumentsLength() == 1 && !IS_PROXY(value)) {
191 return %BasicJSONStringify(value);
192 }
193 if (!IS_CALLABLE(replacer) && %is_arraylike(replacer)) {
194 var property_list = new InternalArray();
195 var seen_properties = new GlobalSet();
196 var length = TO_LENGTH(replacer.length);
197 for (var i = 0; i < length; i++) {
198 var v = replacer[i];
199 var item;
200 if (IS_STRING(v)) {
201 item = v;
202 } else if (IS_NUMBER(v)) {
203 item = %_NumberToString(v);
204 } else if (IS_STRING_WRAPPER(v) || IS_NUMBER_WRAPPER(v)) {
205 item = TO_STRING(v);
206 } else {
207 continue;
208 }
209 if (!seen_properties.has(item)) {
210 property_list.push(item);
211 seen_properties.add(item);
212 }
213 }
214 replacer = property_list;
215 }
216 if (IS_OBJECT(space)) {
217 // Unwrap 'space' if it is wrapped
218 if (IS_NUMBER_WRAPPER(space)) {
219 space = TO_NUMBER(space);
220 } else if (IS_STRING_WRAPPER(space)) {
221 space = TO_STRING(space);
222 }
223 }
224 var gap;
225 if (IS_NUMBER(space)) {
226 space = MaxSimple(0, MinSimple(TO_INTEGER(space), 10));
227 gap = %_SubString(" ", 0, space);
228 } else if (IS_STRING(space)) {
229 if (space.length > 10) {
230 gap = %_SubString(space, 0, 10);
231 } else {
232 gap = space;
233 }
234 } else {
235 gap = "";
236 }
237 return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap);
238}
239
240// -------------------------------------------------------------------
241
242%AddNamedProperty(GlobalJSON, toStringTagSymbol, "JSON", READ_ONLY | DONT_ENUM);
243
244// Set up non-enumerable properties of the JSON object.
245utils.InstallFunctions(GlobalJSON, DONT_ENUM, [
246 "parse", JSONParse,
247 "stringify", JSONStringify
248]);
249
250// -------------------------------------------------------------------
251// Date.toJSON
252
253// 20.3.4.37 Date.prototype.toJSON ( key )
254function DateToJSON(key) {
255 var o = TO_OBJECT(this);
256 var tv = TO_PRIMITIVE_NUMBER(o);
257 if (IS_NUMBER(tv) && !NUMBER_IS_FINITE(tv)) {
258 return null;
259 }
260 return o.toISOString();
261}
262
263// Set up non-enumerable functions of the Date prototype object.
264utils.InstallFunctions(GlobalDate.prototype, DONT_ENUM, [
265 "toJSON", DateToJSON
266]);
267
268// -------------------------------------------------------------------
269// JSON Builtins
270
271function JsonSerializeAdapter(key, object) {
272 var holder = {};
273 holder[key] = object;
274 // No need to pass the actual holder since there is no replacer function.
275 return JSONSerialize(key, holder, UNDEFINED, new InternalArray(), "", "");
276}
277
278%InstallToContext(["json_serialize_adapter", JsonSerializeAdapter]);
279
280})