json.js 10.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

var $JSON = global.JSON;

function Revive(holder, name, reviver) {
  var val = holder[name];
  if (IS_OBJECT(val)) {
    if (IS_ARRAY(val)) {
      var length = val.length;
      for (var i = 0; i < length; i++) {
        var newElement = Revive(val, $String(i), reviver);
        val[i] = newElement;
      }
    } else {
      for (var p in val) {
41
        if (%_CallFunction(val, p, ObjectHasOwnProperty)) {
42 43 44 45 46 47 48 49 50 51
          var newElement = Revive(val, p, reviver);
          if (IS_UNDEFINED(newElement)) {
            delete val[p];
          } else {
            val[p] = newElement;
          }
        }
      }
    }
  }
52
  return %_CallFunction(holder, name, val, reviver);
53 54 55
}

function JSONParse(text, reviver) {
56
  var unfiltered = %ParseJson(TO_STRING_INLINE(text));
57
  if (IS_SPEC_FUNCTION(reviver)) {
58 59 60 61 62 63 64
    return Revive({'': unfiltered}, '', reviver);
  } else {
    return unfiltered;
  }
}

function SerializeArray(value, replacer, stack, indent, gap) {
65
  if (!%PushIfAbsent(stack, value)) {
66
    throw MakeTypeError('circular_structure', $Array());
67
  }
68 69
  var stepback = indent;
  indent += gap;
70
  var partial = new InternalArray();
71 72 73
  var len = value.length;
  for (var i = 0; i < len; i++) {
    var strP = JSONSerialize($String(i), value, replacer, stack,
74 75
                             indent, gap);
    if (IS_UNDEFINED(strP)) {
76
      strP = "null";
77
    }
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
    partial.push(strP);
  }
  var final;
  if (gap == "") {
    final = "[" + partial.join(",") + "]";
  } else if (partial.length > 0) {
    var separator = ",\n" + indent;
    final = "[\n" + indent + partial.join(separator) + "\n" +
        stepback + "]";
  } else {
    final = "[]";
  }
  stack.pop();
  return final;
}

function SerializeObject(value, replacer, stack, indent, gap) {
95
  if (!%PushIfAbsent(stack, value)) {
96
    throw MakeTypeError('circular_structure', $Array());
97
  }
98 99
  var stepback = indent;
  indent += gap;
100
  var partial = new InternalArray();
101 102 103
  if (IS_ARRAY(replacer)) {
    var length = replacer.length;
    for (var i = 0; i < length; i++) {
104
      if (%_CallFunction(replacer, i, ObjectHasOwnProperty)) {
105 106 107
        var p = replacer[i];
        var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
        if (!IS_UNDEFINED(strP)) {
108
          var member = %QuoteJSONString(p) + ":";
109 110 111 112 113 114 115 116
          if (gap != "") member += " ";
          member += strP;
          partial.push(member);
        }
      }
    }
  } else {
    for (var p in value) {
117
      if (%_CallFunction(value, p, ObjectHasOwnProperty)) {
118 119
        var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
        if (!IS_UNDEFINED(strP)) {
120
          var member = %QuoteJSONString(p) + ":";
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
          if (gap != "") member += " ";
          member += strP;
          partial.push(member);
        }
      }
    }
  }
  var final;
  if (gap == "") {
    final = "{" + partial.join(",") + "}";
  } else if (partial.length > 0) {
    var separator = ",\n" + indent;
    final = "{\n" + indent + partial.join(separator) + "\n" +
        stepback + "}";
  } else {
    final = "{}";
  }
  stack.pop();
  return final;
}

function JSONSerialize(key, holder, replacer, stack, indent, gap) {
  var value = holder[key];
144
  if (IS_SPEC_OBJECT(value)) {
145
    var toJSON = value.toJSON;
146
    if (IS_SPEC_FUNCTION(toJSON)) {
147
      value = %_CallFunction(value, key, toJSON);
148
    }
149
  }
150
  if (IS_SPEC_FUNCTION(replacer)) {
151
    value = %_CallFunction(holder, key, value, replacer);
152
  }
153 154 155
  if (IS_STRING(value)) {
    return %QuoteJSONString(value);
  } else if (IS_NUMBER(value)) {
156
    return JSON_NUMBER_TO_STRING(value);
157 158 159 160 161 162 163 164 165 166
  } else if (IS_BOOLEAN(value)) {
    return value ? "true" : "false";
  } else if (IS_NULL(value)) {
    return "null";
  } else if (IS_SPEC_OBJECT(value) && !(typeof value == "function")) {
    // Non-callable object. If it's a primitive wrapper, it must be unwrapped.
    if (IS_ARRAY(value)) {
      return SerializeArray(value, replacer, stack, indent, gap);
    } else if (IS_NUMBER_WRAPPER(value)) {
      value = ToNumber(value);
167
      return JSON_NUMBER_TO_STRING(value);
168
    } else if (IS_STRING_WRAPPER(value)) {
169
      return %QuoteJSONString(ToString(value));
170
    } else if (IS_BOOLEAN_WRAPPER(value)) {
171 172 173
      return %_ValueOf(value) ? "true" : "false";
    } else {
      return SerializeObject(value, replacer, stack, indent, gap);
174 175
    }
  }
176 177
  // Undefined or a callable object.
  return void 0;
178 179
}

180 181

function BasicSerializeArray(value, stack, builder) {
182 183 184 185 186
  var len = value.length;
  if (len == 0) {
    builder.push("[]");
    return;
  }
187
  if (!%PushIfAbsent(stack, value)) {
188
    throw MakeTypeError('circular_structure', $Array());
189
  }
190
  builder.push("[");
191 192 193
  var val = value[0];
  if (IS_STRING(val)) {
    // First entry is a string. Remaining entries are likely to be strings too.
194 195
    var array_string = %QuoteJSONStringArray(value);
    if (!IS_UNDEFINED(array_string)) {
196 197 198 199
      // array_string also includes bracket characters so we are done.
      builder[builder.length - 1] = array_string;
      stack.pop();
      return;
200 201 202 203 204 205 206 207 208
    } else {
      builder.push(%QuoteJSONString(val));
      for (var i = 1; i < len; i++) {
        val = value[i];
        if (IS_STRING(val)) {
          builder.push(%QuoteJSONStringComma(val));
        } else {
          builder.push(",");
          var before = builder.length;
209
          BasicJSONSerialize(i, val, stack, builder);
210 211
          if (before == builder.length) builder[before - 1] = ",null";
        }
212 213 214 215
      }
    }
  } else if (IS_NUMBER(val)) {
    // First entry is a number. Remaining entries are likely to be numbers too.
216
    builder.push(JSON_NUMBER_TO_STRING(val));
217 218 219 220
    for (var i = 1; i < len; i++) {
      builder.push(",");
      val = value[i];
      if (IS_NUMBER(val)) {
221
        builder.push(JSON_NUMBER_TO_STRING(val));
222 223
      } else {
        var before = builder.length;
224
        BasicJSONSerialize(i, val, stack, builder);
225
        if (before == builder.length) builder[before - 1] = ",null";
226 227 228
      }
    }
  } else {
229
    var before = builder.length;
230
    BasicJSONSerialize(0, val, stack, builder);
231
    if (before == builder.length) builder.push("null");
232 233 234
    for (var i = 1; i < len; i++) {
      builder.push(",");
      before = builder.length;
235
      BasicJSONSerialize(i, value[i], stack, builder);
236
      if (before == builder.length) builder[before - 1] = ",null";
237
    }
238 239
  }
  stack.pop();
240
  builder.push("]");
241 242
}

243 244

function BasicSerializeObject(value, stack, builder) {
245
  if (!%PushIfAbsent(stack, value)) {
246
    throw MakeTypeError('circular_structure', $Array());
247
  }
248
  builder.push("{");
249
  var first = true;
250
  for (var p in value) {
251
    if (%HasLocalProperty(value, p)) {
252 253 254 255 256
      if (!first) {
        builder.push(%QuoteJSONStringComma(p));
      } else {
        builder.push(%QuoteJSONString(p));
      }
257
      builder.push(":");
258
      var before = builder.length;
259
      BasicJSONSerialize(p, value[p], stack, builder);
260 261 262 263
      if (before == builder.length) {
        builder.pop();
        builder.pop();
      } else {
264
        first = false;
265
      }
266 267 268
    }
  }
  stack.pop();
269
  builder.push("}");
270 271
}

272

273
function BasicJSONSerialize(key, value, stack, builder) {
274
  if (IS_SPEC_OBJECT(value)) {
275
    var toJSON = value.toJSON;
276
    if (IS_SPEC_FUNCTION(toJSON)) {
277 278
      value = %_CallFunction(value, ToString(key), toJSON);
    }
279
  }
280
  if (IS_STRING(value)) {
281
    builder.push(value !== "" ? %QuoteJSONString(value) : '""');
282
  } else if (IS_NUMBER(value)) {
283
    builder.push(JSON_NUMBER_TO_STRING(value));
284
  } else if (IS_BOOLEAN(value)) {
285 286 287 288 289
    builder.push(value ? "true" : "false");
  } else if (IS_NULL(value)) {
    builder.push("null");
  } else if (IS_SPEC_OBJECT(value) && !(typeof value == "function")) {
    // Value is a non-callable object.
290
    // Unwrap value if necessary
291
    if (IS_NUMBER_WRAPPER(value)) {
292
      value = ToNumber(value);
293
      builder.push(JSON_NUMBER_TO_STRING(value));
294
    } else if (IS_STRING_WRAPPER(value)) {
295
      builder.push(%QuoteJSONString(ToString(value)));
296
    } else if (IS_BOOLEAN_WRAPPER(value)) {
297 298 299
      builder.push(%_ValueOf(value) ? "true" : "false");
    } else if (IS_ARRAY(value)) {
      BasicSerializeArray(value, stack, builder);
300
    } else {
301
      BasicSerializeObject(value, stack, builder);
302
    }
303 304 305
  }
}

306

307
function JSONStringify(value, replacer, space) {
308
  if (%_ArgumentsLength() == 1) {
309 310
    var builder = new InternalArray();
    BasicJSONSerialize('', value, new InternalArray(), builder);
311 312 313 314
    if (builder.length == 0) return;
    var result = %_FastAsciiArrayJoin(builder, "");
    if (!IS_UNDEFINED(result)) return result;
    return %StringBuilderConcat(builder, builder.length, "");
315
  }
316 317 318
  if (IS_OBJECT(space)) {
    // Unwrap 'space' if it is wrapped
    if (IS_NUMBER_WRAPPER(space)) {
319
      space = ToNumber(space);
320
    } else if (IS_STRING_WRAPPER(space)) {
321
      space = ToString(space);
322 323 324 325
    }
  }
  var gap;
  if (IS_NUMBER(space)) {
326 327
    space = MathMax(0, MathMin(ToInteger(space), 10));
    gap = SubString("          ", 0, space);
328
  } else if (IS_STRING(space)) {
329
    if (space.length > 10) {
330
      gap = SubString(space, 0, 10);
331 332 333
    } else {
      gap = space;
    }
334 335 336
  } else {
    gap = "";
  }
337
  return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap);
338 339
}

340 341
function SetUpJSON() {
  %CheckIsBootstrapping();
342 343 344 345 346 347
  InstallFunctions($JSON, DONT_ENUM, $Array(
    "parse", JSONParse,
    "stringify", JSONStringify
  ));
}

348
SetUpJSON();