|  | // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  | // Disable background compilation so that the Issue 24908 can be reproduced. | 
|  | // VMOptions=--no-background-compilation | 
|  |  | 
|  | library json_test; | 
|  |  | 
|  | import "package:expect/expect.dart"; | 
|  | import "dart:convert"; | 
|  |  | 
|  | void testJson(jsonText, expected) { | 
|  | compare(expected, actual, path) { | 
|  | if (expected is List) { | 
|  | Expect.isTrue(actual is List); | 
|  | Expect.equals(expected.length, actual.length, "$path: List length"); | 
|  | for (int i = 0; i < expected.length; i++) { | 
|  | compare(expected[i], actual[i], "$path[$i] in $jsonText"); | 
|  | } | 
|  | } else if (expected is Map) { | 
|  | Expect.isTrue(actual is Map); | 
|  | Expect.equals(expected.length, actual.length, | 
|  | "$path: Map size in $jsonText"); | 
|  | expected.forEach((key, value) { | 
|  | Expect.isTrue(actual.containsKey(key)); | 
|  | compare(value, actual[key], "$path[$key] in $jsonText"); | 
|  | }); | 
|  | } else if (expected is num) { | 
|  | Expect.equals(expected is int, actual is int, | 
|  | "$path: not same number type in $jsonText"); | 
|  | Expect.isTrue(expected.compareTo(actual) == 0, | 
|  | "$path: Expected: $expected, was: $actual in $jsonText"); | 
|  | } else { | 
|  | // String, bool, null. | 
|  | Expect.equals(expected, actual, "$path in $jsonText"); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (var reviver in [null, (k, v) => v]) { | 
|  | for (var split in [0, 1, 2, 3, 4, 5]) { | 
|  | var name = (reviver == null) ? "" : "reviver:"; | 
|  | var sink = new ChunkedConversionSink.withCallback((values) { | 
|  | var value = values[0]; | 
|  | compare(expected, value, "$name$value"); | 
|  | }); | 
|  | var decoderSink = json.decoder.startChunkedConversion(sink); | 
|  | switch (split) { | 
|  | case 0: | 
|  | // Split after first char. | 
|  | decoderSink.add(jsonText.substring(0, 1)); | 
|  | decoderSink.add(jsonText.substring(1)); | 
|  | decoderSink.close(); | 
|  | break; | 
|  | case 1: | 
|  | // Split before last char. | 
|  | int length = jsonText.length; | 
|  | decoderSink.add(jsonText.substring(0, length - 1)); | 
|  | decoderSink.add(jsonText.substring(length - 1)); | 
|  | decoderSink.close(); | 
|  | break; | 
|  | case 2: | 
|  | // Split in middle. | 
|  | int half = jsonText.length ~/ 2; | 
|  | decoderSink.add(jsonText.substring(0, half)); | 
|  | decoderSink.add(jsonText.substring(half)); | 
|  | decoderSink.close(); | 
|  | break; | 
|  | case 3: | 
|  | // Split in three chunks. | 
|  | int length = jsonText.length; | 
|  | int third = length ~/ 3; | 
|  | decoderSink.add(jsonText.substring(0, third)); | 
|  | decoderSink.add(jsonText.substring(third, 2 * third)); | 
|  | decoderSink.add(jsonText.substring(2 * third)); | 
|  | decoderSink.close(); | 
|  | break; | 
|  | case 4: | 
|  | // Use .decode | 
|  | sink.add([json.decode(jsonText)]); | 
|  | break; | 
|  | case 5: | 
|  | // Use jsonDecode | 
|  | sink.add([jsonDecode(jsonText)]); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | String escape(String s) { | 
|  | var sb = new StringBuffer(); | 
|  | for (int i = 0; i < s.length; i++) { | 
|  | int code = s.codeUnitAt(i); | 
|  | if (code == '\\'.codeUnitAt(0)) | 
|  | sb.write(r'\\'); | 
|  | else if (code == '\"'.codeUnitAt(0)) | 
|  | sb.write(r'\"'); | 
|  | else if (code >= 32 && code < 127) | 
|  | sb.writeCharCode(code); | 
|  | else { | 
|  | String hex = '000${code.toRadixString(16)}'; | 
|  | sb.write(r'\u' '${hex.substring(hex.length - 4)}'); | 
|  | } | 
|  | } | 
|  | return '$sb'; | 
|  | } | 
|  |  | 
|  | void testThrows(jsonText) { | 
|  | var message = "json = '${escape(jsonText)}'"; | 
|  | Expect.throwsFormatException(() => json.decode(jsonText), | 
|  | "json.decode, $message"); | 
|  | Expect.throwsFormatException(() => jsonDecode(jsonText), | 
|  | "jsonDecode, $message"); | 
|  | Expect.throwsFormatException(() => json.decoder.convert(jsonText), | 
|  | "json.decoder.convert, $message"); | 
|  | Expect.throwsFormatException(() => | 
|  | utf8.decoder.fuse(json.decoder).convert(utf8.encode(jsonText)), | 
|  | "utf8.decoder.fuse(json.decoder) o utf.encode, $message"); | 
|  | } | 
|  |  | 
|  | testNumbers() { | 
|  | // Positive tests for number formats. | 
|  | var integerList = ["0", "9", "9999"]; | 
|  | var signList = ["", "-"]; | 
|  | var fractionList = ["", ".0", ".1", ".99999"]; | 
|  | var exponentList = [""]; | 
|  | for (var exphead in ["e", "E", "e-", "E-", "e+", "E+"]) { | 
|  | for (var expval in ["0", "1", "200"]) { | 
|  | exponentList.add("$exphead$expval"); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (var integer in integerList) { | 
|  | for (var sign in signList) { | 
|  | for (var fraction in fractionList) { | 
|  | for (var exp in exponentList) { | 
|  | for (var ws in ["", " ", "\t"]) { | 
|  | var literal = "$ws$sign$integer$fraction$exp$ws"; | 
|  | var expectedValue = num.parse(literal); | 
|  | testJson(literal, expectedValue); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Regression test. | 
|  | // Detect and handle overflow on integer literals by making them doubles | 
|  | // (work like `num.parse`). | 
|  | testJson("9223372036854774784", 9223372036854774784); | 
|  | testJson("-9223372036854775808", -9223372036854775808); | 
|  | testJson("9223372036854775808", 9223372036854775808.0); | 
|  | testJson("-9223372036854775809", -9223372036854775809.0); | 
|  | testJson("9223372036854775808.0", 9223372036854775808.0); | 
|  | testJson("9223372036854775810", 9223372036854775810.0); | 
|  | testJson("18446744073709551616.0", 18446744073709551616.0); | 
|  | testJson("1e309", double.infinity); | 
|  | testJson("-1e309", double.negativeInfinity); | 
|  | testJson("1e-325", 0.0); | 
|  | testJson("-1e-325", -0.0); | 
|  | // No overflow on exponent. | 
|  | testJson("1e18446744073709551616", double.infinity); | 
|  | testJson("-1e18446744073709551616", double.negativeInfinity); | 
|  | testJson("1e-18446744073709551616", 0.0); | 
|  | testJson("-1e-18446744073709551616", -0.0); | 
|  |  | 
|  | // (Wrapping numbers in list because the chunked parsing handles top-level | 
|  | // numbers by buffering and then parsing using platform parser). | 
|  | testJson("[9223372036854774784]", [9223372036854774784]); | 
|  | testJson("[-9223372036854775808]", [-9223372036854775808]); | 
|  | testJson("[9223372036854775808]", [9223372036854775808.0]); | 
|  | testJson("[-9223372036854775809]", [-9223372036854775809.0]); | 
|  | testJson("[9223372036854775808.0]", [9223372036854775808.0]); | 
|  | testJson("[9223372036854775810]", [9223372036854775810.0]); | 
|  | testJson("[18446744073709551616.0]", [18446744073709551616.0]); | 
|  | testJson("[1e309]", [double.infinity]); | 
|  | testJson("[-1e309]", [double.negativeInfinity]); | 
|  | testJson("[1e-325]", [0.0]); | 
|  | testJson("[-1e-325]", [-0.0]); | 
|  | // No overflow on exponent. | 
|  | testJson("[1e18446744073709551616]", [double.infinity]); | 
|  | testJson("[-1e18446744073709551616]", [double.negativeInfinity]); | 
|  | testJson("[1e-18446744073709551616]", [0.0]); | 
|  | testJson("[-1e-18446744073709551616]", [-0.0]); | 
|  |  | 
|  | // Negative tests (syntax error). | 
|  | // testError thoroughly tests the given parts with a lot of valid | 
|  | // values for the other parts. | 
|  | testError({signs, integers, fractions, exponents}) { | 
|  | def(value, defaultValue) { | 
|  | if (value == null) return defaultValue; | 
|  | if (value is List) return value; | 
|  | return [value]; | 
|  | } | 
|  |  | 
|  | signs = def(signs, signList); | 
|  | integers = def(integers, integerList); | 
|  | fractions = def(fractions, fractionList); | 
|  | exponents = def(exponents, exponentList); | 
|  | for (var integer in integers) { | 
|  | for (var sign in signs) { | 
|  | for (var fraction in fractions) { | 
|  | for (var exponent in exponents) { | 
|  | var literal = "$sign$integer$fraction$exponent"; | 
|  | testThrows(literal); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Doubles overflow to Infinity. | 
|  | testJson("1e+400", double.infinity); | 
|  | // (Integers do not, but we don't have those on dart2js). | 
|  |  | 
|  | // Integer part cannot be omitted: | 
|  | testError(integers: ""); | 
|  |  | 
|  | // Test for "Initial zero only allowed for zero integer part" moved to | 
|  | // json_strict_test.dart because IE's jsonDecode accepts additional initial | 
|  | // zeros. | 
|  |  | 
|  | // Only minus allowed as sign. | 
|  | testError(signs: "+"); | 
|  | // Requires digits after decimal point. | 
|  | testError(fractions: "."); | 
|  | // Requires exponent digts, and only digits. | 
|  | testError(exponents: ["e", "e+", "e-", "e.0"]); | 
|  |  | 
|  | // No whitespace inside numbers. | 
|  | // Additional case "- 2.2e+2" in json_strict_test.dart. | 
|  | testThrows("-2 .2e+2"); | 
|  | testThrows("-2. 2e+2"); | 
|  | testThrows("-2.2 e+2"); | 
|  | testThrows("-2.2e +2"); | 
|  | testThrows("-2.2e+ 2"); | 
|  | testThrows("01"); | 
|  | testThrows("0."); | 
|  | testThrows(".0"); | 
|  | testThrows("0.e1"); | 
|  |  | 
|  | testThrows("[2.,2]"); | 
|  | testThrows("{2.:2}"); | 
|  |  | 
|  | testThrows("NaN"); | 
|  | testThrows("Infinity"); | 
|  | testThrows("-Infinity"); | 
|  | Expect.throws(() => json.encode(double.nan)); | 
|  | Expect.throws(() => json.encode(double.infinity)); | 
|  | Expect.throws(() => json.encode(double.negativeInfinity)); | 
|  | Expect.throws(() => jsonEncode(double.nan)); | 
|  | Expect.throws(() => jsonEncode(double.infinity)); | 
|  | Expect.throws(() => jsonEncode(double.negativeInfinity)); | 
|  | } | 
|  |  | 
|  | testStrings() { | 
|  | // String parser accepts and understands escapes. | 
|  | var input = r'"\u0000\uffff\n\r\f\t\b\/\\\"' '\x20\ufffd\uffff"'; | 
|  | var expected = "\u0000\uffff\n\r\f\t\b\/\\\"\x20\ufffd\uffff"; | 
|  | testJson(input, expected); | 
|  | // Empty string. | 
|  | testJson(r'""', ""); | 
|  | // Escape first. | 
|  | var escapes = { | 
|  | "f": "\f", | 
|  | "b": "\b", | 
|  | "n": "\n", | 
|  | "r": "\r", | 
|  | "t": "\t", | 
|  | r"\": r"\", | 
|  | '"': '"', | 
|  | "/": "/", | 
|  | }; | 
|  | escapes.forEach((esc, lit) { | 
|  | testJson('"\\$esc........"', "$lit........"); | 
|  | // Escape last. | 
|  | testJson('"........\\$esc"', "........$lit"); | 
|  | // Escape middle. | 
|  | testJson('"....\\$esc...."', "....$lit...."); | 
|  | }); | 
|  |  | 
|  | // Does not accept single quotes. | 
|  | testThrows(r"''"); | 
|  | // Throws on unterminated strings. | 
|  | testThrows(r'"......\"'); | 
|  | // Throws on unterminated escapes. | 
|  | testThrows(r'"\'); // ' is not escaped. | 
|  | testThrows(r'"\a"'); | 
|  | testThrows(r'"\u"'); | 
|  | testThrows(r'"\u1"'); | 
|  | testThrows(r'"\u12"'); | 
|  | testThrows(r'"\u123"'); | 
|  | testThrows(r'"\ux"'); | 
|  | testThrows(r'"\u1x"'); | 
|  | testThrows(r'"\u12x"'); | 
|  | testThrows(r'"\u123x"'); | 
|  | // Throws on bad escapes. | 
|  | testThrows(r'"\a"'); | 
|  | testThrows(r'"\x00"'); | 
|  | testThrows(r'"\c2"'); | 
|  | testThrows(r'"\000"'); | 
|  | testThrows(r'"\u{0}"'); | 
|  | testThrows(r'"\%"'); | 
|  | testThrows('"\\\x00"'); // Not raw string! | 
|  | // Throws on control characters. | 
|  | for (int i = 0; i < 32; i++) { | 
|  | var string = new String.fromCharCodes([0x22, i, 0x22]); // '"\x00"' etc. | 
|  | testThrows(string); | 
|  | } | 
|  | } | 
|  |  | 
|  | testObjects() { | 
|  | testJson(r'{}', {}); | 
|  | testJson(r'{"x":42}', {"x": 42}); | 
|  | testJson(r'{"x":{"x":{"x":42}}}', { | 
|  | "x": { | 
|  | "x": {"x": 42} | 
|  | } | 
|  | }); | 
|  | testJson(r'{"x":10,"x":42}', {"x": 42}); | 
|  | testJson(r'{"":42}', {"": 42}); | 
|  |  | 
|  | // Keys must be strings. | 
|  | testThrows(r'{x:10}'); | 
|  | testThrows(r'{true:10}'); | 
|  | testThrows(r'{false:10}'); | 
|  | testThrows(r'{null:10}'); | 
|  | testThrows(r'{42:10}'); | 
|  | testThrows(r'{42e1:10}'); | 
|  | testThrows(r'{-42:10}'); | 
|  | testThrows(r'{["text"]:10}'); | 
|  | testThrows(r'{:10}'); | 
|  | } | 
|  |  | 
|  | testArrays() { | 
|  | testJson(r'[]', []); | 
|  | testJson(r'[1.1e1,"string",true,false,null,{}]', | 
|  | [1.1e1, "string", true, false, null, {}]); | 
|  | testJson(r'[[[[[[]]]],[[[]]],[[]]]]', [ | 
|  | [ | 
|  | [ | 
|  | [ | 
|  | [[]] | 
|  | ] | 
|  | ], | 
|  | [ | 
|  | [[]] | 
|  | ], | 
|  | [[]] | 
|  | ] | 
|  | ]); | 
|  | testJson(r'[{},[{}],{"x":[]}]', [ | 
|  | {}, | 
|  | [{}], | 
|  | {"x": []} | 
|  | ]); | 
|  |  | 
|  | testThrows(r'[1,,2]'); | 
|  | testThrows(r'[1,2,]'); | 
|  | testThrows(r'[,2]'); | 
|  | } | 
|  |  | 
|  | testWords() { | 
|  | testJson(r'true', true); | 
|  | testJson(r'false', false); | 
|  | testJson(r'null', null); | 
|  | testJson(r'[true]', [true]); | 
|  | testJson(r'{"true":true}', {"true": true}); | 
|  |  | 
|  | testThrows(r'truefalse'); | 
|  | testThrows(r'trues'); | 
|  | testThrows(r'nulll'); | 
|  | testThrows(r'full'); | 
|  | testThrows(r'nul'); | 
|  | testThrows(r'tru'); | 
|  | testThrows(r'fals'); | 
|  | testThrows(r'\null'); | 
|  | testThrows(r't\rue'); | 
|  | testThrows(r't\rue'); | 
|  | } | 
|  |  | 
|  | testWhitespace() { | 
|  | // Valid white-space characters. | 
|  | var v = '\t\r\n\ '; | 
|  | // Invalid white-space and non-recognized characters. | 
|  | var invalids = ['\x00', '\f', '\x08', '\\', '\xa0', '\u2028', '\u2029']; | 
|  |  | 
|  | // Valid whitespace accepted "everywhere". | 
|  | testJson('$v[${v}-2.2e2$v,$v{$v"key"$v:${v}true$v}$v,$v"ab"$v]$v', [ | 
|  | -2.2e2, | 
|  | {"key": true}, | 
|  | "ab" | 
|  | ]); | 
|  |  | 
|  | // IE9 accepts invalid characters at the end, so some of these tests have been | 
|  | // moved to json_strict_test.dart. | 
|  | for (var i in invalids) { | 
|  | testThrows('${i}"s"'); | 
|  | testThrows('42${i}'); | 
|  | testThrows('$i[]'); | 
|  | testThrows('[$i]'); | 
|  | testThrows('[$i"s"]'); | 
|  | testThrows('["s"$i]'); | 
|  | testThrows('$i{"k":"v"}'); | 
|  | testThrows('{$i"k":"v"}'); | 
|  | testThrows('{"k"$i:"v"}'); | 
|  | testThrows('{"k":$i"v"}'); | 
|  | testThrows('{"k":"v"$i}'); | 
|  | } | 
|  | } | 
|  |  | 
|  | main() { | 
|  | testNumbers(); | 
|  | testStrings(); | 
|  | testWords(); | 
|  | testObjects(); | 
|  | testArrays(); | 
|  | testWhitespace(); | 
|  | } |