1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
14 * The Original Code is SpiderMonkey JSON.
16 * The Initial Developer of the Original Code is
17 * Mozilla Corporation.
18 * Portions created by the Initial Developer are Copyright (C) 1998-1999
19 * the Initial Developer. All Rights Reserved.
22 * Robert Sayre <sayrer@gmail.com>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either of the GNU General Public License Version 2 or later (the "GPL"),
26 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
58 JSClass js_JSONClass
= {
60 JSCLASS_HAS_CACHED_PROTO(JSProto_JSON
),
61 JS_PropertyStub
, JS_PropertyStub
, JS_PropertyStub
, JS_PropertyStub
,
62 JS_EnumerateStub
, JS_ResolveStub
, JS_ConvertStub
, JS_FinalizeStub
,
63 JSCLASS_NO_OPTIONAL_MEMBERS
67 js_json_parse(JSContext
*cx
, uintN argc
, jsval
*vp
)
72 // Must throw an Error if there isn't a first arg
73 if (!JS_ConvertArguments(cx
, argc
, argv
, "S", &s
))
77 JSONParser
*jp
= js_BeginJSONParse(cx
, vp
);
78 JSBool ok
= jp
!= NULL
;
81 ok
= js_ConsumeJSONText(cx
, jp
, JS_GetStringChars(s
), JS_GetStringLength(s
));
82 ok
&= js_FinishJSONParse(cx
, jp
);
86 JS_ReportError(cx
, "Error parsing JSON.");
91 struct StringifyClosure
93 StringifyClosure(JSContext
*aCx
, jsval
*str
) : cx(aCx
), s(str
)
102 WriteCallback(const jschar
*buf
, uint32 len
, void *data
)
104 StringifyClosure
*sc
= static_cast<StringifyClosure
*>(data
);
105 JSString
*s1
= JSVAL_TO_STRING(*sc
->s
);
106 JSString
*s2
= js_NewStringCopyN(sc
->cx
, buf
, len
);
110 s1
= js_ConcatStrings(sc
->cx
, s1
, s2
);
114 *sc
->s
= STRING_TO_JSVAL(s1
);
119 js_json_stringify(JSContext
*cx
, uintN argc
, jsval
*vp
)
122 jsval
*argv
= vp
+ 2;
124 // Must throw an Error if there isn't a first arg
125 if (!JS_ConvertArguments(cx
, argc
, argv
, "o", &obj
))
128 // Only use objects and arrays as the root for now
129 jsval v
= OBJECT_TO_JSVAL(obj
);
130 JSBool ok
= js_TryJSON(cx
, &v
);
132 if (!(ok
&& !JSVAL_IS_PRIMITIVE(v
) &&
133 (type
= JS_TypeOfValue(cx
, v
)) != JSTYPE_FUNCTION
&&
134 type
!= JSTYPE_XML
)) {
135 JS_ReportError(cx
, "Invalid argument.");
139 JSString
*s
= JS_NewStringCopyN(cx
, "", 0);
144 jsval sv
= STRING_TO_JSVAL(s
);
145 StringifyClosure
sc(cx
, &sv
);
146 JSAutoTempValueRooter
tvr(cx
, 1, sc
.s
);
147 ok
= js_Stringify(cx
, &v
, NULL
, &WriteCallback
, &sc
, 0);
155 js_TryJSON(JSContext
*cx
, jsval
*vp
)
157 // Checks whether the return value implements toJSON()
160 if (!JSVAL_IS_PRIMITIVE(*vp
)) {
161 JSObject
*obj
= JSVAL_TO_OBJECT(*vp
);
162 ok
= js_TryMethod(cx
, obj
, cx
->runtime
->atomState
.toJSONAtom
, 0, NULL
, vp
);
169 static const jschar quote
= jschar('"');
170 static const jschar backslash
= jschar('\\');
171 static const jschar unicodeEscape
[] = {'\\', 'u', '0', '0'};
174 write_string(JSContext
*cx
, JSONWriteCallback callback
, void *data
, const jschar
*buf
, uint32 len
)
176 if (!callback("e
, 1, data
))
181 for (i
= 0; i
< len
; ++i
) {
182 if (buf
[i
] == quote
|| buf
[i
] == backslash
) {
183 if (!callback(&buf
[mark
], i
- mark
, data
) || !callback(&backslash
, 1, data
) ||
184 !callback(&buf
[i
], 1, data
)) {
188 } else if (buf
[i
] <= 31 || buf
[i
] == 127) {
189 if (!callback(&buf
[mark
], i
- mark
, data
) || !callback(unicodeEscape
, 4, data
))
192 unsigned int len
= JS_snprintf(ubuf
, sizeof(ubuf
), "%.2x", buf
[i
]);
194 // TODO: don't allocate a JSString just to inflate (js_InflateStringToBuffer on static?)
195 JSString
*us
= JS_NewStringCopyN(cx
, ubuf
, len
);
196 if (!callback(JS_GetStringChars(us
), len
, data
))
202 if (mark
< len
&& !callback(&buf
[mark
], len
- mark
, data
))
205 if (!callback("e
, 1, data
))
212 js_Stringify(JSContext
*cx
, jsval
*vp
, JSObject
*replacer
,
213 JSONWriteCallback callback
, void *data
, uint32 depth
)
215 if (depth
> JSON_MAX_DEPTH
)
216 return JS_FALSE
; /* encoding error */
219 JSObject
*obj
= JSVAL_TO_OBJECT(*vp
);
220 JSBool isArray
= JS_IsArrayObject(cx
, obj
);
221 jschar output
= jschar(isArray
? '[' : '{');
222 if (!callback(&output
, 1, data
))
225 JSObject
*iterObj
= NULL
;
230 if (!js_GetLengthProperty(cx
, obj
, &length
))
233 if (!js_ValueToIterator(cx
, JSITER_ENUMERATE
, vp
))
235 iterObj
= JSVAL_TO_OBJECT(*vp
);
238 jsval outputValue
= JSVAL_VOID
;
239 JSAutoTempValueRooter
tvr(cx
, 1, &outputValue
);
242 JSBool memberWritten
= JS_FALSE
;
244 outputValue
= JSVAL_VOID
;
247 if ((jsuint
)i
>= length
)
249 ok
= JS_GetElement(cx
, obj
, i
++, &outputValue
);
251 ok
= js_CallIteratorNext(cx
, iterObj
, &key
);
254 if (key
== JSVAL_HOLE
)
258 if (JSVAL_IS_STRING(key
)) {
259 ks
= JSVAL_TO_STRING(key
);
261 ks
= js_ValueToString(cx
, key
);
268 ok
= JS_GetUCProperty(cx
, obj
, JS_GetStringChars(ks
),
269 JS_GetStringLength(ks
), &outputValue
);
275 // if this is an array, holes are transmitted as null
276 if (isArray
&& outputValue
== JSVAL_VOID
) {
277 outputValue
= JSVAL_NULL
;
278 } else if (JSVAL_IS_OBJECT(outputValue
)) {
279 ok
= js_TryJSON(cx
, &outputValue
);
284 // elide undefined values
285 if (outputValue
== JSVAL_VOID
)
288 // output a comma unless this is the first member to write
290 output
= jschar(',');
291 ok
= callback(&output
, 1, data
);
295 memberWritten
= JS_TRUE
;
297 JSType type
= JS_TypeOfValue(cx
, outputValue
);
299 // Can't encode these types, so drop them
300 if (type
== JSTYPE_FUNCTION
|| type
== JSTYPE_XML
)
303 // Be careful below, this string is weakly rooted.
306 // If this isn't an array, we need to output a key
308 s
= js_ValueToString(cx
, key
);
314 ok
= write_string(cx
, callback
, data
, JS_GetStringChars(s
), JS_GetStringLength(s
));
318 output
= jschar(':');
319 ok
= callback(&output
, 1, data
);
324 if (!JSVAL_IS_PRIMITIVE(outputValue
)) {
326 ok
= js_Stringify(cx
, &outputValue
, replacer
, callback
, data
, depth
+ 1);
328 JSString
*outputString
;
329 s
= js_ValueToString(cx
, outputValue
);
335 if (type
== JSTYPE_STRING
) {
336 ok
= write_string(cx
, callback
, data
, JS_GetStringChars(s
), JS_GetStringLength(s
));
343 if (type
== JSTYPE_NUMBER
) {
344 if (JSVAL_IS_DOUBLE(outputValue
)) {
345 jsdouble d
= *JSVAL_TO_DOUBLE(outputValue
);
346 if (!JSDOUBLE_IS_FINITE(d
))
347 outputString
= JS_NewStringCopyN(cx
, "null", 4);
353 } else if (type
== JSTYPE_BOOLEAN
) {
355 } else if (JSVAL_IS_NULL(outputValue
)) {
356 outputString
= JS_NewStringCopyN(cx
, "null", 4);
358 ok
= JS_FALSE
; // encoding error
362 ok
= callback(JS_GetStringChars(outputString
), JS_GetStringLength(outputString
), data
);
367 // Always close the iterator, but make sure not to stomp on OK
368 ok
&= js_CloseIterator(cx
, *vp
);
369 // encoding error or propagate? FIXME: Bug 408838.
373 JS_ReportError(cx
, "Error during JSON encoding.");
377 output
= jschar(isArray
? ']' : '}');
378 ok
= callback(&output
, 1, data
);
383 // helper to determine whether a character could be part of a number
384 static JSBool
IsNumChar(jschar c
)
386 return ((c
<= '9' && c
>= '0') || c
== '.' || c
== '-' || c
== '+' || c
== 'e' || c
== 'E');
390 js_BeginJSONParse(JSContext
*cx
, jsval
*rootVal
)
395 JSObject
*arr
= js_NewArrayObject(cx
, 0, NULL
);
399 JSONParser
*jp
= (JSONParser
*) JS_malloc(cx
, sizeof(JSONParser
));
404 jp
->objectStack
= arr
;
405 if (!js_AddRoot(cx
, &jp
->objectStack
, "JSON parse stack"))
410 jp
->statep
= jp
->stateStack
;
411 *jp
->statep
= JSON_PARSE_STATE_INIT
;
412 jp
->rootVal
= rootVal
;
413 jp
->objectKey
= NULL
;
414 jp
->buffer
= (JSStringBuffer
*) JS_malloc(cx
, sizeof(JSStringBuffer
));
417 js_InitStringBuffer(jp
->buffer
);
421 JS_free(cx
, jp
->buffer
);
427 js_FinishJSONParse(JSContext
*cx
, JSONParser
*jp
)
433 js_FinishStringBuffer(jp
->buffer
);
435 JS_free(cx
, jp
->buffer
);
436 if (!js_RemoveRoot(cx
->runtime
, &jp
->objectStack
))
438 JSBool ok
= *jp
->statep
== JSON_PARSE_STATE_FINISHED
;
445 PushState(JSONParser
*jp
, JSONParserState state
)
447 if (*jp
->statep
== JSON_PARSE_STATE_FINISHED
)
448 return JS_FALSE
; // extra input
451 if ((uint32
)(jp
->statep
- jp
->stateStack
) >= JS_ARRAY_LENGTH(jp
->stateStack
))
452 return JS_FALSE
; // too deep
460 PopState(JSONParser
*jp
)
463 if (jp
->statep
< jp
->stateStack
) {
464 jp
->statep
= jp
->stateStack
;
468 if (*jp
->statep
== JSON_PARSE_STATE_INIT
)
469 *jp
->statep
= JSON_PARSE_STATE_FINISHED
;
475 PushValue(JSContext
*cx
, JSONParser
*jp
, JSObject
*parent
, jsval value
)
477 JSAutoTempValueRooter
tvr(cx
, 1, &value
);
480 if (OBJ_IS_ARRAY(cx
, parent
)) {
482 ok
= js_GetLengthProperty(cx
, parent
, &len
);
484 ok
= JS_SetElement(cx
, parent
, len
, &value
);
486 ok
= JS_DefineUCProperty(cx
, parent
, JS_GetStringChars(jp
->objectKey
),
487 JS_GetStringLength(jp
->objectKey
), value
,
488 NULL
, NULL
, JSPROP_ENUMERATE
);
495 PushObject(JSContext
*cx
, JSONParser
*jp
, JSObject
*obj
)
498 if (!js_GetLengthProperty(cx
, jp
->objectStack
, &len
))
500 if (len
>= JSON_MAX_DEPTH
)
501 return JS_FALSE
; // decoding error
503 jsval v
= OBJECT_TO_JSVAL(obj
);
505 // Check if this is the root object
508 if (!JS_SetElement(cx
, jp
->objectStack
, 0, jp
->rootVal
))
514 if (!JS_GetElement(cx
, jp
->objectStack
, len
- 1, &p
))
516 JS_ASSERT(JSVAL_IS_OBJECT(p
));
517 JSObject
*parent
= JSVAL_TO_OBJECT(p
);
518 if (!PushValue(cx
, jp
, parent
, OBJECT_TO_JSVAL(obj
)))
521 if (!JS_SetElement(cx
, jp
->objectStack
, len
, &v
))
528 GetTopOfObjectStack(JSContext
*cx
, JSONParser
*jp
)
531 if (!js_GetLengthProperty(cx
, jp
->objectStack
, &length
))
535 if (!JS_GetElement(cx
, jp
->objectStack
, length
- 1, &o
))
538 JS_ASSERT(!JSVAL_IS_PRIMITIVE(o
));
539 return JSVAL_TO_OBJECT(o
);
543 OpenObject(JSContext
*cx
, JSONParser
*jp
)
545 JSObject
*obj
= js_NewObject(cx
, &js_ObjectClass
, NULL
, NULL
, 0);
549 return PushObject(cx
, jp
, obj
);
553 OpenArray(JSContext
*cx
, JSONParser
*jp
)
555 // Add an array to an existing array or object
556 JSObject
*arr
= js_NewArrayObject(cx
, 0, NULL
);
560 return PushObject(cx
, jp
, arr
);
564 CloseObject(JSContext
*cx
, JSONParser
*jp
)
567 if (!js_GetLengthProperty(cx
, jp
->objectStack
, &len
))
569 if (!js_SetLengthProperty(cx
, jp
->objectStack
, len
- 1))
576 CloseArray(JSContext
*cx
, JSONParser
*jp
)
578 return CloseObject(cx
, jp
);
582 HandleNumber(JSContext
*cx
, JSONParser
*jp
, const jschar
*buf
, uint32 len
)
586 if (!js_strtod(cx
, buf
, buf
+ len
, &ep
, &val
) || ep
!= buf
+ len
)
591 JSObject
*obj
= GetTopOfObjectStack(cx
, jp
);
592 if (obj
&& JS_NewNumberValue(cx
, val
, &numVal
))
593 ok
= PushValue(cx
, jp
, obj
, numVal
);
595 ok
= JS_FALSE
; // decode error
601 HandleString(JSContext
*cx
, JSONParser
*jp
, const jschar
*buf
, uint32 len
)
603 JSObject
*obj
= GetTopOfObjectStack(cx
, jp
);
604 JSString
*str
= js_NewStringCopyN(cx
, buf
, len
);
608 return PushValue(cx
, jp
, obj
, STRING_TO_JSVAL(str
));
612 HandleKeyword(JSContext
*cx
, JSONParser
*jp
, const jschar
*buf
, uint32 len
)
615 JSTokenType tt
= js_CheckKeyword(buf
, len
);
616 if (tt
!= TOK_PRIMARY
)
620 keyword
= JSVAL_NULL
;
621 else if (buf
[0] == 't')
622 keyword
= JSVAL_TRUE
;
623 else if (buf
[0] == 'f')
624 keyword
= JSVAL_FALSE
;
628 JSObject
*obj
= GetTopOfObjectStack(cx
, jp
);
632 return PushValue(cx
, jp
, obj
, keyword
);
636 HandleData(JSContext
*cx
, JSONParser
*jp
, JSONDataType type
, const jschar
*buf
, uint32 len
)
638 JSBool ok
= JS_FALSE
;
641 case JSON_DATA_STRING
:
642 ok
= HandleString(cx
, jp
, buf
, len
);
645 case JSON_DATA_KEYSTRING
:
646 jp
->objectKey
= js_NewStringCopyN(cx
, buf
, len
);
650 case JSON_DATA_NUMBER
:
651 ok
= HandleNumber(cx
, jp
, buf
, len
);
654 case JSON_DATA_KEYWORD
:
655 ok
= HandleKeyword(cx
, jp
, buf
, len
);
659 JS_NOT_REACHED("Should have a JSON data type");
662 js_FinishStringBuffer(jp
->buffer
);
663 js_InitStringBuffer(jp
->buffer
);
669 js_ConsumeJSONText(JSContext
*cx
, JSONParser
*jp
, const jschar
*data
, uint32 len
)
673 if (*jp
->statep
== JSON_PARSE_STATE_INIT
) {
674 PushState(jp
, JSON_PARSE_STATE_OBJECT_VALUE
);
677 for (i
= 0; i
< len
; i
++) {
679 switch (*jp
->statep
) {
680 case JSON_PARSE_STATE_VALUE
:
685 if (*jp
->statep
!= JSON_PARSE_STATE_ARRAY
)
686 return JS_FALSE
; // unexpected char
687 if (!CloseArray(cx
, jp
) || !PopState(jp
))
693 // we should only find these in OBJECT_KEY state
694 return JS_FALSE
; // unexpected failure
698 *jp
->statep
= JSON_PARSE_STATE_STRING
;
703 *jp
->statep
= JSON_PARSE_STATE_NUMBER
;
704 js_AppendChar(jp
->buffer
, c
);
709 *jp
->statep
= JSON_PARSE_STATE_KEYWORD
;
710 js_AppendChar(jp
->buffer
, c
);
714 // fall through in case the value is an object or array
715 case JSON_PARSE_STATE_OBJECT_VALUE
:
717 *jp
->statep
= JSON_PARSE_STATE_OBJECT
;
718 if (!OpenObject(cx
, jp
) || !PushState(jp
, JSON_PARSE_STATE_OBJECT_PAIR
))
720 } else if (c
== '[') {
721 *jp
->statep
= JSON_PARSE_STATE_ARRAY
;
722 if (!OpenArray(cx
, jp
) || !PushState(jp
, JSON_PARSE_STATE_VALUE
))
724 } else if (!JS_ISXMLSPACE(c
)) {
725 return JS_FALSE
; // unexpected
729 case JSON_PARSE_STATE_OBJECT
:
731 if (!CloseObject(cx
, jp
) || !PopState(jp
))
733 } else if (c
== ',') {
734 if (!PushState(jp
, JSON_PARSE_STATE_OBJECT_PAIR
))
736 } else if (c
== ']' || !JS_ISXMLSPACE(c
)) {
737 return JS_FALSE
; // unexpected
741 case JSON_PARSE_STATE_ARRAY
:
743 if (!CloseArray(cx
, jp
) || !PopState(jp
))
745 } else if (c
== ',') {
746 if (!PushState(jp
, JSON_PARSE_STATE_VALUE
))
748 } else if (!JS_ISXMLSPACE(c
)) {
749 return JS_FALSE
; // unexpected
753 case JSON_PARSE_STATE_OBJECT_PAIR
:
755 // we want to be waiting for a : when the string has been read
756 *jp
->statep
= JSON_PARSE_STATE_OBJECT_IN_PAIR
;
757 if (!PushState(jp
, JSON_PARSE_STATE_STRING
))
759 } else if (c
== '}') {
760 // pop off the object pair state and the object state
761 if (!CloseObject(cx
, jp
) || !PopState(jp
) || !PopState(jp
))
763 } else if (c
== ']' || !JS_ISXMLSPACE(c
)) {
764 return JS_FALSE
; // unexpected
768 case JSON_PARSE_STATE_OBJECT_IN_PAIR
:
770 *jp
->statep
= JSON_PARSE_STATE_VALUE
;
771 } else if (!JS_ISXMLSPACE(c
)) {
772 return JS_FALSE
; // unexpected
776 case JSON_PARSE_STATE_STRING
:
781 if (*jp
->statep
== JSON_PARSE_STATE_OBJECT_IN_PAIR
) {
782 jdt
= JSON_DATA_KEYSTRING
;
784 jdt
= JSON_DATA_STRING
;
786 if (!HandleData(cx
, jp
, jdt
, jp
->buffer
->base
, STRING_BUFFER_OFFSET(jp
->buffer
)))
788 } else if (c
== '\\') {
789 *jp
->statep
= JSON_PARSE_STATE_STRING_ESCAPE
;
791 js_AppendChar(jp
->buffer
, c
);
795 case JSON_PARSE_STATE_STRING_ESCAPE
:
801 case 'b' : c
= '\b'; break;
802 case 'f' : c
= '\f'; break;
803 case 'n' : c
= '\n'; break;
804 case 'r' : c
= '\r'; break;
805 case 't' : c
= '\t'; break;
810 *jp
->statep
= JSON_PARSE_STATE_STRING_HEX
;
813 return JS_FALSE
; // unexpected
817 js_AppendChar(jp
->buffer
, c
);
818 *jp
->statep
= JSON_PARSE_STATE_STRING
;
821 case JSON_PARSE_STATE_STRING_HEX
:
822 if (('0' <= c
) && (c
<= '9'))
823 jp
->hexChar
= (jp
->hexChar
<< 4) | (c
- '0');
824 else if (('a' <= c
) && (c
<= 'f'))
825 jp
->hexChar
= (jp
->hexChar
<< 4) | (c
- 'a' + 0x0a);
826 else if (('A' <= c
) && (c
<= 'F'))
827 jp
->hexChar
= (jp
->hexChar
<< 4) | (c
- 'A' + 0x0a);
829 return JS_FALSE
; // unexpected
831 if (++(jp
->numHex
) == 4) {
832 js_AppendChar(jp
->buffer
, jp
->hexChar
);
835 *jp
->statep
= JSON_PARSE_STATE_STRING
;
839 case JSON_PARSE_STATE_KEYWORD
:
841 js_AppendChar(jp
->buffer
, c
);
843 // this character isn't part of the keyword, process it again
848 if (!HandleData(cx
, jp
, JSON_DATA_KEYWORD
, jp
->buffer
->base
, STRING_BUFFER_OFFSET(jp
->buffer
)))
853 case JSON_PARSE_STATE_NUMBER
:
855 js_AppendChar(jp
->buffer
, c
);
857 // this character isn't part of the number, process it again
861 if (!HandleData(cx
, jp
, JSON_DATA_NUMBER
, jp
->buffer
->base
, STRING_BUFFER_OFFSET(jp
->buffer
)))
866 case JSON_PARSE_STATE_FINISHED
:
867 if (!JS_ISXMLSPACE(c
))
868 return JS_FALSE
; // extra input
873 JS_NOT_REACHED("Invalid JSON parser state");
882 json_toSource(JSContext
*cx
, uintN argc
, jsval
*vp
)
884 *vp
= ATOM_KEY(CLASS_ATOM(cx
, JSON
));
889 static JSFunctionSpec json_static_methods
[] = {
891 JS_FN(js_toSource_str
, json_toSource
, 0, 0),
893 JS_FN("parse", js_json_parse
, 0, 0),
894 JS_FN("stringify", js_json_stringify
, 0, 0),
899 js_InitJSONClass(JSContext
*cx
, JSObject
*obj
)
903 JSON
= JS_NewObject(cx
, &js_JSONClass
, NULL
, obj
);
906 if (!JS_DefineProperty(cx
, obj
, js_JSON_str
, OBJECT_TO_JSVAL(JSON
),
907 JS_PropertyStub
, JS_PropertyStub
, 0))
910 if (!JS_DefineFunctions(cx
, JSON
, json_static_methods
))