1// 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}) 281