Home | History | Annotate | Download | only in src
      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 var $JSON = global.JSON;
     29 
     30 function 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) {
     41         if (%_CallFunction(val, p, ObjectHasOwnProperty)) {
     42           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   }
     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     return Revive({'': unfiltered}, '', reviver);
     59   } else {
     60     return unfiltered;
     61   }
     62 }
     63 
     64 function SerializeArray(value, replacer, stack, indent, gap) {
     65   if (!%PushIfAbsent(stack, value)) {
     66     throw MakeTypeError('circular_structure', $Array());
     67   }
     68   var stepback = indent;
     69   indent += gap;
     70   var partial = new InternalArray();
     71   var len = value.length;
     72   for (var i = 0; i < len; i++) {
     73     var strP = JSONSerialize($String(i), value, replacer, stack,
     74                              indent, gap);
     75     if (IS_UNDEFINED(strP)) {
     76       strP = "null";
     77     }
     78     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 
     94 function SerializeObject(value, replacer, stack, indent, gap) {
     95   if (!%PushIfAbsent(stack, value)) {
     96     throw MakeTypeError('circular_structure', $Array());
     97   }
     98   var stepback = indent;
     99   indent += gap;
    100   var partial = new InternalArray();
    101   if (IS_ARRAY(replacer)) {
    102     var length = replacer.length;
    103     for (var i = 0; i < length; i++) {
    104       if (%_CallFunction(replacer, i, ObjectHasOwnProperty)) {
    105         var p = replacer[i];
    106         var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
    107         if (!IS_UNDEFINED(strP)) {
    108           var member = %QuoteJSONString(p) + ":";
    109           if (gap != "") member += " ";
    110           member += strP;
    111           partial.push(member);
    112         }
    113       }
    114     }
    115   } else {
    116     for (var p in value) {
    117       if (%_CallFunction(value, p, ObjectHasOwnProperty)) {
    118         var strP = JSONSerialize(p, value, replacer, stack, indent, gap);
    119         if (!IS_UNDEFINED(strP)) {
    120           var member = %QuoteJSONString(p) + ":";
    121           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 
    142 function JSONSerialize(key, holder, replacer, stack, indent, gap) {
    143   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   if (IS_STRING(value)) {
    154     return %QuoteJSONString(value);
    155   } else if (IS_NUMBER(value)) {
    156     return JSON_NUMBER_TO_STRING(value);
    157   } 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);
    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       return %_ValueOf(value) ? "true" : "false";
    172     } else {
    173       return SerializeObject(value, replacer, stack, indent, gap);
    174     }
    175   }
    176   // Undefined or a callable object.
    177   return void 0;
    178 }
    179 
    180 
    181 function BasicSerializeArray(value, stack, builder) {
    182   var len = value.length;
    183   if (len == 0) {
    184     builder.push("[]");
    185     return;
    186   }
    187   if (!%PushIfAbsent(stack, value)) {
    188     throw MakeTypeError('circular_structure', $Array());
    189   }
    190   builder.push("[");
    191   var val = value[0];
    192   if (IS_STRING(val)) {
    193     // First entry is a string. Remaining entries are likely to be strings too.
    194     var array_string = %QuoteJSONStringArray(value);
    195     if (!IS_UNDEFINED(array_string)) {
    196       // array_string also includes bracket characters so we are done.
    197       builder[builder.length - 1] = array_string;
    198       stack.pop();
    199       return;
    200     } 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;
    209           BasicJSONSerialize(i, val, stack, builder);
    210           if (before == builder.length) builder[before - 1] = ",null";
    211         }
    212       }
    213     }
    214   } else if (IS_NUMBER(val)) {
    215     // First entry is a number. Remaining entries are likely to be numbers too.
    216     builder.push(JSON_NUMBER_TO_STRING(val));
    217     for (var i = 1; i < len; i++) {
    218       builder.push(",");
    219       val = value[i];
    220       if (IS_NUMBER(val)) {
    221         builder.push(JSON_NUMBER_TO_STRING(val));
    222       } else {
    223         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     for (var i = 1; i < len; i++) {
    233       builder.push(",");
    234       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       if (!first) {
    253         builder.push(%QuoteJSONStringComma(p));
    254       } else {
    255         builder.push(%QuoteJSONString(p));
    256       }
    257       builder.push(":");
    258       var before = builder.length;
    259       BasicJSONSerialize(p, value[p], stack, builder);
    260       if (before == builder.length) {
    261         builder.pop();
    262         builder.pop();
    263       } 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       value = %_CallFunction(value, ToString(key), toJSON);
    278     }
    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     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.
    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       builder.push(%_ValueOf(value) ? "true" : "false");
    298     } else if (IS_ARRAY(value)) {
    299       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     var builder = new InternalArray();
    310     BasicJSONSerialize('', value, new InternalArray(), builder);
    311     if (builder.length == 0) return;
    312     var result = %_FastAsciiArrayJoin(builder, "");
    313     if (!IS_UNDEFINED(result)) return result;
    314     return %StringBuilderConcat(builder, builder.length, "");
    315   }
    316   if (IS_OBJECT(space)) {
    317     // Unwrap 'space' if it is wrapped
    318     if (IS_NUMBER_WRAPPER(space)) {
    319       space = ToNumber(space);
    320     } else if (IS_STRING_WRAPPER(space)) {
    321       space = ToString(space);
    322     }
    323   }
    324   var gap;
    325   if (IS_NUMBER(space)) {
    326     space = MathMax(0, MathMin(ToInteger(space), 10));
    327     gap = SubString("          ", 0, space);
    328   } else if (IS_STRING(space)) {
    329     if (space.length > 10) {
    330       gap = SubString(space, 0, 10);
    331     } else {
    332       gap = space;
    333     }
    334   } else {
    335     gap = "";
    336   }
    337   return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap);
    338 }
    339 
    340 function SetUpJSON() {
    341   %CheckIsBootstrapping();
    342   InstallFunctions($JSON, DONT_ENUM, $Array(
    343     "parse", JSONParse,
    344     "stringify", JSONStringify
    345   ));
    346 }
    347 
    348 SetUpJSON();
    349