blob: 6c984a157df36803f31ff87021e2c36e96207aa0 [file] [log] [blame]
ager@chromium.org3a37e9b2009-04-27 09:26:21 +00001// Copyright 2009 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6// * Redistributions of source code must retain the above copyright
7// notice, this list of conditions and the following disclaimer.
8// * Redistributions in binary form must reproduce the above
9// copyright notice, this list of conditions and the following
10// disclaimer in the documentation and/or other materials provided
11// with the distribution.
12// * Neither the name of Google Inc. nor the names of its
13// contributors may be used to endorse or promote products derived
14// from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28var $JSON = global.JSON;
29
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000030function Revive(holder, name, reviver) {
31 var val = holder[name];
32 if (IS_OBJECT(val)) {
33 if (IS_ARRAY(val)) {
34 var length = val.length;
35 for (var i = 0; i < length; i++) {
36 var newElement = Revive(val, $String(i), reviver);
37 val[i] = newElement;
38 }
39 } else {
40 for (var p in val) {
whesse@chromium.org7a392b32011-01-31 11:30:36 +000041 if (%_CallFunction(val, p, ObjectHasOwnProperty)) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000042 var newElement = Revive(val, p, reviver);
43 if (IS_UNDEFINED(newElement)) {
44 delete val[p];
45 } else {
46 val[p] = newElement;
47 }
48 }
49 }
50 }
51 }
karlklose@chromium.org8f806e82011-03-07 14:06:08 +000052 return %_CallFunction(holder, name, val, reviver);
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000053}
54
55function JSONParse(text, reviver) {
fschneider@chromium.org9e3e0b62011-01-03 10:16:46 +000056 var unfiltered = %ParseJson(TO_STRING_INLINE(text));
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000057 if (IS_FUNCTION(reviver)) {
58 return Revive({'': unfiltered}, '', reviver);
59 } else {
60 return unfiltered;
61 }
62}
63
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000064function SerializeArray(value, replacer, stack, indent, gap) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +000065 if (!%PushIfAbsent(stack, value)) {
karlklose@chromium.org8f806e82011-03-07 14:06:08 +000066 throw MakeTypeError('circular_structure', $Array());
ager@chromium.org5c838252010-02-19 08:53:10 +000067 }
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000068 var stepback = indent;
69 indent += gap;
karlklose@chromium.org8f806e82011-03-07 14:06:08 +000070 var partial = new InternalArray();
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000071 var len = value.length;
72 for (var i = 0; i < len; i++) {
73 var strP = JSONSerialize($String(i), value, replacer, stack,
ager@chromium.org5c838252010-02-19 08:53:10 +000074 indent, gap);
75 if (IS_UNDEFINED(strP)) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000076 strP = "null";
ager@chromium.org5c838252010-02-19 08:53:10 +000077 }
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000078 partial.push(strP);
79 }
80 var final;
81 if (gap == "") {
82 final = "[" + partial.join(",") + "]";
83 } else if (partial.length > 0) {
84 var separator = ",\n" + indent;
85 final = "[\n" + indent + partial.join(separator) + "\n" +
86 stepback + "]";
87 } else {
88 final = "[]";
89 }
90 stack.pop();
91 return final;
92}
93
94function SerializeObject(value, replacer, stack, indent, gap) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +000095 if (!%PushIfAbsent(stack, value)) {
karlklose@chromium.org8f806e82011-03-07 14:06:08 +000096 throw MakeTypeError('circular_structure', $Array());
ager@chromium.org5c838252010-02-19 08:53:10 +000097 }
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000098 var stepback = indent;
99 indent += gap;
karlklose@chromium.org8f806e82011-03-07 14:06:08 +0000100 var partial = new InternalArray();
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000101 if (IS_ARRAY(replacer)) {
102 var length = replacer.length;
103 for (var i = 0; i < length; i++) {
whesse@chromium.org7a392b32011-01-31 11:30:36 +0000104 if (%_CallFunction(replacer, i, ObjectHasOwnProperty)) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000105 var p = replacer[i];
106 var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
107 if (!IS_UNDEFINED(strP)) {
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000108 var member = %QuoteJSONString(p) + ":";
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000109 if (gap != "") member += " ";
110 member += strP;
111 partial.push(member);
112 }
113 }
114 }
115 } else {
116 for (var p in value) {
whesse@chromium.org7a392b32011-01-31 11:30:36 +0000117 if (%_CallFunction(value, p, ObjectHasOwnProperty)) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000118 var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
119 if (!IS_UNDEFINED(strP)) {
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000120 var member = %QuoteJSONString(p) + ":";
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000121 if (gap != "") member += " ";
122 member += strP;
123 partial.push(member);
124 }
125 }
126 }
127 }
128 var final;
129 if (gap == "") {
130 final = "{" + partial.join(",") + "}";
131 } else if (partial.length > 0) {
132 var separator = ",\n" + indent;
133 final = "{\n" + indent + partial.join(separator) + "\n" +
134 stepback + "}";
135 } else {
136 final = "{}";
137 }
138 stack.pop();
139 return final;
140}
141
142function JSONSerialize(key, holder, replacer, stack, indent, gap) {
143 var value = holder[key];
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000144 if (IS_SPEC_OBJECT(value)) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000145 var toJSON = value.toJSON;
ager@chromium.org5c838252010-02-19 08:53:10 +0000146 if (IS_FUNCTION(toJSON)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000147 value = %_CallFunction(value, key, toJSON);
ager@chromium.org5c838252010-02-19 08:53:10 +0000148 }
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000149 }
ager@chromium.org5c838252010-02-19 08:53:10 +0000150 if (IS_FUNCTION(replacer)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000151 value = %_CallFunction(holder, key, value, replacer);
ager@chromium.org5c838252010-02-19 08:53:10 +0000152 }
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000153 if (IS_STRING(value)) {
154 return %QuoteJSONString(value);
155 } else if (IS_NUMBER(value)) {
ricow@chromium.orgd2be9012011-06-01 06:00:58 +0000156 return JSON_NUMBER_TO_STRING(value);
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000157 } else if (IS_BOOLEAN(value)) {
158 return value ? "true" : "false";
159 } else if (IS_NULL(value)) {
160 return "null";
161 } else if (IS_SPEC_OBJECT(value) && !(typeof value == "function")) {
162 // Non-callable object. If it's a primitive wrapper, it must be unwrapped.
163 if (IS_ARRAY(value)) {
164 return SerializeArray(value, replacer, stack, indent, gap);
165 } else if (IS_NUMBER_WRAPPER(value)) {
166 value = ToNumber(value);
ricow@chromium.orgd2be9012011-06-01 06:00:58 +0000167 return JSON_NUMBER_TO_STRING(value);
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000168 } else if (IS_STRING_WRAPPER(value)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000169 return %QuoteJSONString(ToString(value));
ager@chromium.org5c838252010-02-19 08:53:10 +0000170 } else if (IS_BOOLEAN_WRAPPER(value)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000171 return %_ValueOf(value) ? "true" : "false";
172 } else {
173 return SerializeObject(value, replacer, stack, indent, gap);
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000174 }
175 }
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000176 // Undefined or a callable object.
177 return void 0;
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000178}
179
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000180
181function BasicSerializeArray(value, stack, builder) {
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000182 var len = value.length;
183 if (len == 0) {
184 builder.push("[]");
185 return;
186 }
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000187 if (!%PushIfAbsent(stack, value)) {
karlklose@chromium.org8f806e82011-03-07 14:06:08 +0000188 throw MakeTypeError('circular_structure', $Array());
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000189 }
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000190 builder.push("[");
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000191 var val = value[0];
192 if (IS_STRING(val)) {
193 // First entry is a string. Remaining entries are likely to be strings too.
ricow@chromium.orgc54d3652011-05-30 09:20:16 +0000194 var array_string = %QuoteJSONStringArray(value);
195 if (!IS_UNDEFINED(array_string)) {
ricow@chromium.org96696a72011-05-30 11:17:41 +0000196 // array_string also includes bracket characters so we are done.
197 builder[builder.length - 1] = array_string;
198 stack.pop();
199 return;
ricow@chromium.orgc54d3652011-05-30 09:20:16 +0000200 } else {
201 builder.push(%QuoteJSONString(val));
202 for (var i = 1; i < len; i++) {
203 val = value[i];
204 if (IS_STRING(val)) {
205 builder.push(%QuoteJSONStringComma(val));
206 } else {
207 builder.push(",");
208 var before = builder.length;
ricow@chromium.orgd2be9012011-06-01 06:00:58 +0000209 BasicJSONSerialize(i, val, stack, builder);
ricow@chromium.orgc54d3652011-05-30 09:20:16 +0000210 if (before == builder.length) builder[before - 1] = ",null";
211 }
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000212 }
213 }
214 } else if (IS_NUMBER(val)) {
215 // First entry is a number. Remaining entries are likely to be numbers too.
ricow@chromium.orgd2be9012011-06-01 06:00:58 +0000216 builder.push(JSON_NUMBER_TO_STRING(val));
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000217 for (var i = 1; i < len; i++) {
218 builder.push(",");
219 val = value[i];
220 if (IS_NUMBER(val)) {
ricow@chromium.orgd2be9012011-06-01 06:00:58 +0000221 builder.push(JSON_NUMBER_TO_STRING(val));
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000222 } else {
223 var before = builder.length;
ricow@chromium.orgd2be9012011-06-01 06:00:58 +0000224 BasicJSONSerialize(i, val, stack, builder);
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000225 if (before == builder.length) builder[before - 1] = ",null";
226 }
227 }
228 } else {
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000229 var before = builder.length;
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000230 BasicJSONSerialize(0, val, stack, builder);
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000231 if (before == builder.length) builder.push("null");
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000232 for (var i = 1; i < len; i++) {
233 builder.push(",");
234 before = builder.length;
ricow@chromium.orgd2be9012011-06-01 06:00:58 +0000235 BasicJSONSerialize(i, value[i], stack, builder);
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000236 if (before == builder.length) builder[before - 1] = ",null";
237 }
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000238 }
239 stack.pop();
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000240 builder.push("]");
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000241}
242
243
244function BasicSerializeObject(value, stack, builder) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000245 if (!%PushIfAbsent(stack, value)) {
karlklose@chromium.org8f806e82011-03-07 14:06:08 +0000246 throw MakeTypeError('circular_structure', $Array());
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000247 }
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000248 builder.push("{");
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000249 var first = true;
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000250 for (var p in value) {
251 if (%HasLocalProperty(value, p)) {
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000252 if (!first) {
253 builder.push(%QuoteJSONStringComma(p));
254 } else {
255 builder.push(%QuoteJSONString(p));
256 }
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000257 builder.push(":");
258 var before = builder.length;
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000259 BasicJSONSerialize(p, value[p], stack, builder);
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000260 if (before == builder.length) {
261 builder.pop();
262 builder.pop();
263 } else {
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000264 first = false;
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000265 }
266 }
267 }
268 stack.pop();
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000269 builder.push("}");
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000270}
271
272
sgjesse@chromium.orgc6c57182011-01-17 12:24:25 +0000273function BasicJSONSerialize(key, value, stack, builder) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000274 if (IS_SPEC_OBJECT(value)) {
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000275 var toJSON = value.toJSON;
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000276 if (IS_FUNCTION(toJSON)) {
277 value = %_CallFunction(value, ToString(key), toJSON);
278 }
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000279 }
280 if (IS_STRING(value)) {
ager@chromium.org04921a82011-06-27 13:21:41 +0000281 builder.push(value !== "" ? %QuoteJSONString(value) : '""');
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000282 } else if (IS_NUMBER(value)) {
ricow@chromium.orgd2be9012011-06-01 06:00:58 +0000283 builder.push(JSON_NUMBER_TO_STRING(value));
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000284 } else if (IS_BOOLEAN(value)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000285 builder.push(value ? "true" : "false");
286 } else if (IS_NULL(value)) {
287 builder.push("null");
288 } else if (IS_SPEC_OBJECT(value) && !(typeof value == "function")) {
289 // Value is a non-callable object.
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000290 // Unwrap value if necessary
291 if (IS_NUMBER_WRAPPER(value)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000292 value = ToNumber(value);
ricow@chromium.orgd2be9012011-06-01 06:00:58 +0000293 builder.push(JSON_NUMBER_TO_STRING(value));
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000294 } else if (IS_STRING_WRAPPER(value)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000295 builder.push(%QuoteJSONString(ToString(value)));
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000296 } else if (IS_BOOLEAN_WRAPPER(value)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000297 builder.push(%_ValueOf(value) ? "true" : "false");
298 } else if (IS_ARRAY(value)) {
299 BasicSerializeArray(value, stack, builder);
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000300 } else {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000301 BasicSerializeObject(value, stack, builder);
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000302 }
303 }
304}
305
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000306
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000307function JSONStringify(value, replacer, space) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000308 if (%_ArgumentsLength() == 1) {
karlklose@chromium.org8f806e82011-03-07 14:06:08 +0000309 var builder = new InternalArray();
310 BasicJSONSerialize('', value, new InternalArray(), builder);
kasperl@chromium.orga5551262010-12-07 12:49:48 +0000311 if (builder.length == 0) return;
312 var result = %_FastAsciiArrayJoin(builder, "");
313 if (!IS_UNDEFINED(result)) return result;
314 return %StringBuilderConcat(builder, builder.length, "");
315 }
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000316 if (IS_OBJECT(space)) {
317 // Unwrap 'space' if it is wrapped
318 if (IS_NUMBER_WRAPPER(space)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000319 space = ToNumber(space);
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000320 } else if (IS_STRING_WRAPPER(space)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000321 space = ToString(space);
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000322 }
323 }
324 var gap;
325 if (IS_NUMBER(space)) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000326 space = MathMax(0, MathMin(ToInteger(space), 10));
327 gap = SubString(" ", 0, space);
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000328 } else if (IS_STRING(space)) {
ager@chromium.org5c838252010-02-19 08:53:10 +0000329 if (space.length > 10) {
ager@chromium.org5f0c45f2010-12-17 08:51:21 +0000330 gap = SubString(space, 0, 10);
ager@chromium.org5c838252010-02-19 08:53:10 +0000331 } else {
332 gap = space;
333 }
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000334 } else {
335 gap = "";
336 }
karlklose@chromium.org8f806e82011-03-07 14:06:08 +0000337 return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap);
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000338}
339
340function SetupJSON() {
341 InstallFunctions($JSON, DONT_ENUM, $Array(
342 "parse", JSONParse,
343 "stringify", JSONStringify
344 ));
345}
346
347SetupJSON();