1// 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
28// This file relies on the fact that the following declarations have been made
29// in runtime.js:
30// var $Array = global.Array;
31// var $String = global.String;
32
33var $JSON = global.JSON;
34
35// -------------------------------------------------------------------
36
37function Revive(holder, name, reviver) {
38  var val = holder[name];
39  if (IS_OBJECT(val)) {
40    if (IS_ARRAY(val)) {
41      var length = val.length;
42      for (var i = 0; i < length; i++) {
43        var newElement = Revive(val, $String(i), reviver);
44        val[i] = newElement;
45      }
46    } else {
47      for (var p in val) {
48        if (%_CallFunction(val, p, ObjectHasOwnProperty)) {
49          var newElement = Revive(val, p, reviver);
50          if (IS_UNDEFINED(newElement)) {
51            delete val[p];
52          } else {
53            val[p] = newElement;
54          }
55        }
56      }
57    }
58  }
59  return %_CallFunction(holder, name, val, reviver);
60}
61
62function JSONParse(text, reviver) {
63  var unfiltered = %ParseJson(TO_STRING_INLINE(text));
64  if (IS_SPEC_FUNCTION(reviver)) {
65    return Revive({'': unfiltered}, '', reviver);
66  } else {
67    return unfiltered;
68  }
69}
70
71function SerializeArray(value, replacer, stack, indent, gap) {
72  if (!%PushIfAbsent(stack, value)) {
73    throw MakeTypeError('circular_structure', $Array());
74  }
75  var stepback = indent;
76  indent += gap;
77  var partial = new InternalArray();
78  var len = value.length;
79  for (var i = 0; i < len; i++) {
80    var strP = JSONSerialize($String(i), value, replacer, stack,
81                             indent, gap);
82    if (IS_UNDEFINED(strP)) {
83      strP = "null";
84    }
85    partial.push(strP);
86  }
87  var final;
88  if (gap == "") {
89    final = "[" + partial.join(",") + "]";
90  } else if (partial.length > 0) {
91    var separator = ",\n" + indent;
92    final = "[\n" + indent + partial.join(separator) + "\n" +
93        stepback + "]";
94  } else {
95    final = "[]";
96  }
97  stack.pop();
98  return final;
99}
100
101function SerializeObject(value, replacer, stack, indent, gap) {
102  if (!%PushIfAbsent(stack, value)) {
103    throw MakeTypeError('circular_structure', $Array());
104  }
105  var stepback = indent;
106  indent += gap;
107  var partial = new InternalArray();
108  if (IS_ARRAY(replacer)) {
109    var length = replacer.length;
110    for (var i = 0; i < length; i++) {
111      if (%_CallFunction(replacer, i, ObjectHasOwnProperty)) {
112        var p = replacer[i];
113        var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
114        if (!IS_UNDEFINED(strP)) {
115          var member = %QuoteJSONString(p) + ":";
116          if (gap != "") member += " ";
117          member += strP;
118          partial.push(member);
119        }
120      }
121    }
122  } else {
123    for (var p in value) {
124      if (%_CallFunction(value, p, ObjectHasOwnProperty)) {
125        var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
126        if (!IS_UNDEFINED(strP)) {
127          var member = %QuoteJSONString(p) + ":";
128          if (gap != "") member += " ";
129          member += strP;
130          partial.push(member);
131        }
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
149function JSONSerialize(key, holder, replacer, stack, indent, gap) {
150  var value = holder[key];
151  if (IS_SPEC_OBJECT(value)) {
152    var toJSON = value.toJSON;
153    if (IS_SPEC_FUNCTION(toJSON)) {
154      value = %_CallFunction(value, key, toJSON);
155    }
156  }
157  if (IS_SPEC_FUNCTION(replacer)) {
158    value = %_CallFunction(holder, key, value, replacer);
159  }
160  if (IS_STRING(value)) {
161    return %QuoteJSONString(value);
162  } else if (IS_NUMBER(value)) {
163    return JSON_NUMBER_TO_STRING(value);
164  } else if (IS_BOOLEAN(value)) {
165    return value ? "true" : "false";
166  } else if (IS_NULL(value)) {
167    return "null";
168  } else if (IS_SPEC_OBJECT(value) && !(typeof value == "function")) {
169    // Non-callable object. If it's a primitive wrapper, it must be unwrapped.
170    if (IS_ARRAY(value)) {
171      return SerializeArray(value, replacer, stack, indent, gap);
172    } else if (IS_NUMBER_WRAPPER(value)) {
173      value = ToNumber(value);
174      return JSON_NUMBER_TO_STRING(value);
175    } else if (IS_STRING_WRAPPER(value)) {
176      return %QuoteJSONString(ToString(value));
177    } else if (IS_BOOLEAN_WRAPPER(value)) {
178      return %_ValueOf(value) ? "true" : "false";
179    } else {
180      return SerializeObject(value, replacer, stack, indent, gap);
181    }
182  }
183  // Undefined or a callable object.
184  return UNDEFINED;
185}
186
187
188function JSONStringify(value, replacer, space) {
189  if (%_ArgumentsLength() == 1) {
190    return %BasicJSONStringify(value);
191  }
192  if (IS_OBJECT(space)) {
193    // Unwrap 'space' if it is wrapped
194    if (IS_NUMBER_WRAPPER(space)) {
195      space = ToNumber(space);
196    } else if (IS_STRING_WRAPPER(space)) {
197      space = ToString(space);
198    }
199  }
200  var gap;
201  if (IS_NUMBER(space)) {
202    space = MathMax(0, MathMin(ToInteger(space), 10));
203    gap = %_SubString("          ", 0, space);
204  } else if (IS_STRING(space)) {
205    if (space.length > 10) {
206      gap = %_SubString(space, 0, 10);
207    } else {
208      gap = space;
209    }
210  } else {
211    gap = "";
212  }
213  return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap);
214}
215
216
217// -------------------------------------------------------------------
218
219function SetUpJSON() {
220  %CheckIsBootstrapping();
221
222  // Set up non-enumerable properties of the JSON object.
223  InstallFunctions($JSON, DONT_ENUM, $Array(
224    "parse", JSONParse,
225    "stringify", JSONStringify
226  ));
227}
228
229SetUpJSON();
230
231
232// -------------------------------------------------------------------
233// JSON Builtins
234
235function JSONSerializeAdapter(key, object) {
236  var holder = {};
237  holder[key] = object;
238  // No need to pass the actual holder since there is no replacer function.
239  return JSONSerialize(key, holder, UNDEFINED, new InternalArray(), "", "");
240}
241