Java tutorial
/* Copyright (C) 2013-2014 Ian Teune <ian.teune@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.tinspx.util.json; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import static com.google.common.base.Preconditions.*; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.Files; import com.tinspx.util.collect.MapUtils; import com.tinspx.util.io.CAWriter; import com.tinspx.util.io.CharSequenceReader; import static com.tinspx.util.json.HandlerEvent.*; import com.tinspx.util.json.HandlerEvent.Validator; import com.tinspx.util.json.utils.JSONTest; import com.tinspx.util.json.utils.LoggingHandler; import com.tinspx.util.json.utils.RandomJSONGenerator; import com.tinspx.util.json.utils.TokenLoggingHandler; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.CharBuffer; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.boon.json.JsonSlurper; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import static org.junit.Assert.*; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; /** * Testing Notes: * Had issue where RandomJSONGenerator was using CharSequnces as Map keys. This * caused lookup issues as the equality functions expect them to be Strings. * For future reference, Field/Map keys MUST be Strings. * * @author Ian */ public class JSONParserTest { static final ParserOptions STRICT = ParserOptions.STRICT; static final ParserOptions LENIENT = ParserOptions.UNQUOTED; // static final ParserOptions LENIENT = ParserOptions.LENIENT.toBuilder().allowUnquotedStrings(false).build(); static final ParserOptions COMMAS = ParserOptions.builder().allowRedundantCommas(true).build(); static final ParserOptions COMMENTS = ParserOptions.builder().allowComments(true).build(); static final ParserOptions YAML = ParserOptions.builder().allowYAMLComments(true).build(); static final ParserOptions ALL_COMMENTS = ParserOptions.builder().allowComments(true).allowYAMLComments(true) .build(); static final ParserOptions UNQUOTED_FIELDS = ParserOptions.builder().allowUnquotedFieldNames(true).build(); static final ParserOptions UNQUOTED_STRINGS = ParserOptions.builder().allowUnquotedStrings(true).build(); static final ParserOptions SINGLE = ParserOptions.builder().allowSingleQuotes(true).build(); static final ParserOptions UNESCAPED_CONTROL = ParserOptions.builder().allowUnescapedControlChars(true).build(); static final ParserOptions CASE = ParserOptions.builder().allowCaseInsensitivePrimitives(true).build(); static final ParserOptions BACKSLASH = ParserOptions.builder().allowBackslashEscapingAnyChar(true).build(); static final ParserOptions SINGLE_COMMAS = SINGLE.or(COMMAS); RandomJSONGenerator gen; // Object json; public JSONParserTest() { gen = RandomJSONGenerator.builder().maxFieldLength(16).maxStringLength(32).maxObjectLength(8) .maxArrayLength(16).maxBigIntegerLength(48).build(); } @BeforeClass public static void setUpClass() { } @AfterClass public static void tearDownClass() { } @Before public void setUp() { } @After public void tearDown() { } static final String FILE = "C:\\Users\\Ian\\Documents\\test\\test.txt"; static final String RANDOM = "C:\\Users\\Ian\\Documents\\test\\random.txt"; static final String PARSEED = "C:\\Users\\Ian\\Documents\\test\\parsed.txt"; static final String LOG_HANDLER = "C:\\Users\\Ian\\Documents\\test\\log_handler.txt"; static final String LOG_JSON = "C:\\Users\\Ian\\Documents\\test\\log_json.txt"; static final String FORMATTED = "C:\\Users\\Ian\\Documents\\test\\formatted.txt"; static final String RAW = "C:\\Users\\Ian\\Documents\\test\\raw.txt"; @Test public void testRandomStandardParsing() throws IOException { CAWriter w = new CAWriter(1024 * 1024 * 16); for (int i = 0; i < 6; i++) { testRandomStandardParsing(w); } } @SuppressWarnings({ "CallToPrintStackTrace", "BroadCatchBlock", "TooBroadCatch" }) private void testRandomStandardParsing(CAWriter w) throws IOException { Object value = gen.buildRandomJSON(16, 12); w.reset(); ConfigurableWriter.INSTANCE.writeTo(w, value); Object parse = null; try { parse = JSONValue.parse(w); } catch (Exception ex) { ex.printStackTrace(); Assert.fail("parse exception normal"); } assertJsonEquals(value, parse, "normal"); w.reset(); IndentingWriter.indentingBuilder().build().writeTo(w, value); parse = null; try { parse = JSONValue.parse(new CharSequenceReader(w)); } catch (Exception ex) { ex.printStackTrace(); Assert.fail("parse exception indenting"); } assertJsonEquals(value, parse, "indenting"); w.reset(); JSONValue.writeTo(w, value); parse = null; try { parse = JSONValue.parse((Readable) CharBuffer.wrap(w.toCharArray())); } catch (Exception ex) { ex.printStackTrace(); Assert.fail("parse exception indenting"); } assertJsonEquals(value, parse, "indenting"); } static void writeFormatted(String file, Object value) throws IOException { Writer caw = new BufferedWriter(new FileWriter(file), 1024 * 16); // caw.write("start json\n"); IndentingWriter.indentingBuilder().build().writeTo(caw, value); // caw.write("\nend of json\n"); caw.flush(); caw.close(); } // @Test public void printRandom() throws IOException { RandomJSONGenerator g = RandomJSONGenerator.builder().maxFieldLength(64).maxStringLength(256) .maxObjectLength(16).maxArrayLength(32).bigNumbers(false).build(); Object value = g.buildRandomJSON(2, 4); Writer w = writer_utf(RAW); ConfigurableWriter.INSTANCE.writeTo(w, value); w.flush(); w.close(); w = writer_utf(FORMATTED); IndentingWriter.indentingBuilder().build().writeTo(w, value); w.flush(); w.close(); assertTrue(true); } static final String SAMPLE_FILE = "C:\\Users\\Ian\\Documents\\test\\json_sample.txt"; /** * tests boon, JSONSimple, and tin on the json sample file from json.org, * All parsers pass and the parsed objects are all equal to each other */ // @Test public void testSampleJSON() throws IOException { String sample = Files.toString(new File(SAMPLE_FILE), Charsets.UTF_8); Object simple = org.json.simple.JSONValue.parse(sample); Object tin = JSONValue.parse(sample); Object boon = new JsonSlurper().parseText(sample); assertJsonEquals(tin, simple, "tin - simple"); assertJsonEquals(tin, boon, "tin - boon"); assertJsonEquals(simple, boon, "simple - boon"); } /** * Tests JSONSimple, boon, and tin on a random JSON object * boon fails this test most of the time, SimpleJSON will fail if large * number are enabled */ // @Test @SuppressWarnings("UnusedAssignment") public void testOtherJSONParsers() throws IOException { Object value = gen.buildRandomJSON(14, 13); CAWriter w = new CAWriter(1024 * 1024); ConfigurableWriter.INSTANCE.writeTo(w, value); String input = w.toString(); w = null; Object tin = JSONValue.parse(input); assertJsonEquals(value, tin, "tin"); tin = null; Object simple = org.json.simple.JSONValue.parse(input); assertJsonEquals(value, simple, "simple"); simple = null; //boon fails, haha - so much for the fastes json parser in the world Object boon = new JsonSlurper().parseText(input); assertJsonEquals(value, boon, "boon"); } /** * tests deeply nsted lists on other parsers. only json-simple can handle * it - boon and jackson throw StackOverflow */ // @Test public void testNestedListOtherParsers() throws IOException { Object value = buildNestedList(1024 * 300); String json = JSONValue.toString(value); assertJsonEqualsNoRecurse(value, org.json.simple.JSONValue.parse(json), "simple"); assertJsonEqualsNoRecurse(value, new JsonSlurper().parseText(json), "boon"); //can't etst equality, but see if it can parse new ObjectMapper().readTree(json); } @Test public void testDeeplyNestedList() throws IOException { Object value = buildNestedList(1024 * 300); assertJsonEqualsNoRecurse(value, JSONValue.parse(JSONValue.toString(value))); } @Test public void testDeeplyNestedMap() throws IOException { Object value = buildNestedMap(1024 * 300, "k"); assertJsonEqualsNoRecurse(value, JSONValue.parse(JSONValue.toString(value))); } public static List<Object> buildNestedList(int depth) { final List<Object> head = Lists.newArrayListWithCapacity(1); List<Object> tail = head; for (; depth > 0; depth--) { List<Object> list = Lists.newArrayListWithCapacity(1); tail.add(list); tail = list; } return head; } public static Object buildNestedMap(int depth, String key) { checkNotNull(key); final Map<String, Object> head = MapUtils.newMutableSingletonMap(); Map<String, Object> tail = head; for (; depth > 0; depth--) { Map<String, Object> map = MapUtils.newMutableSingletonMap(); tail.put(key, map); tail = map; } return head; } static void assertJsonEqualsNoRecurse(Object a, Object b) { assertJsonEqualsNoRecurse(a, b, "json not equal"); } static void assertJsonEqualsNoRecurse(Object a, Object b, String msg) { assertTrue(msg, JSONTest.jsonEqualsNoRecurse(a, b)); assertTrue(msg, JSONTest.jsonEqualsNoRecurse(b, a)); } static void assertJsonEquals(Object a, Object b) { assertJsonEquals(a, b, "json not equal"); } static void assertJsonEquals(Object a, Object b, String msg) { JSONTest.jsonEqualsThrow(a, b); JSONTest.jsonEqualsThrow(b, a); assertTrue(msg, JSONTest.jsonEquals(a, b)); assertTrue(msg, JSONTest.jsonEquals(b, a)); assertTrue(msg, JSONTest.jsonEqualsNoRecurse(a, b)); assertTrue(msg, JSONTest.jsonEqualsNoRecurse(b, a)); } static Writer writer(String file) throws IOException { return new BufferedWriter(new FileWriter(file), 1024 * 32); } static Writer writer_utf(String file) throws IOException { return new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(file), 1024 * 64), Charsets.UTF_8); } // @Test public void logParse() throws IOException { Object value = gen.buildRandomJSON(16, 12); writeFormatted(LOG_JSON, value); CAWriter w = new CAWriter(1024 * 64); ConfigurableWriter.INSTANCE.writeTo(w, value); Writer out = writer(LOG_HANDLER); JSONParser.create(stream(w), new LoggingHandler(out), STRICT).parseFully(); out.flush(); out.close(); assertTrue(true); } // @Test public void testTokenLog() throws IOException { Object value = gen.buildRandomJSON(16, 12); // writeFormatted(LOG_JSON, value); CAWriter w = new CAWriter(1024 * 64); ConfigurableWriter.INSTANCE.writeTo(w, value); CAWriter tokens = new CAWriter(1024 * 64); JSONParser.create(stream(w), new TokenLoggingHandler(tokens), STRICT).parseFully(); // Writer out = writer(LOG_HANDLER); // out.append(tokens); // out.flush(); // out.close(); TokenLoggingHandler.verifyTokens(tokens); assertTrue(true); } // @Test public void testFrag1() throws IOException { String json = "], 'string', ]".replace('\'', '"'); Validator<JSONParser> handler = validator(SJ, SA, EA, SA); JSONParser.create(stream(json), handler).parseFully(); handler.finish(); } static void writeIndentingOut(Object value) throws IOException { Writer w = new OutputStreamWriter(System.out); IndentingWriter.create().writeTo(w, value); w.flush(); w.close(); System.out.println(); } // @Test public void printEvents() throws IOException { // String json = "], 'string']".replace('\'', '"'); // String json = ",\"89\":null}], 'string']".replace('\'', '"'); String json = "'key': 'value' ,\n'89':null}], 'string'], 'another string', 124.56, [{}]], [], 'test', {}," .replace('\'', '"'); Writer w = new OutputStreamWriter(System.out); JSONParser.create(stream(json), new LoggingHandler(w)).parseFragment(); w.flush(); System.out.println(); FragmentBuilder frag = new FragmentBuilder(); JSONParser.create(stream(json), frag).parseFragment(); System.out.println(frag.get()); writeIndentingOut(frag.get()); assertTrue(true); } static JSONStream stream(CharSequence seq) { return new CharSequenceStream(seq); } @Test public void basicTest1() throws IOException { String json = "[true, false, null, [], {}, {\"key\":\"value\"}, \"string\", 1]"; Validator<JSONParser> handler = validator(SJ, SA, p(true), p(false), p(null), SA, EA, SO, EO, SO, sf("key"), p("value"), EF, EO, p("string"), p(1), EA, EJ); JSONParser.create(stream(json), handler).parseFully(); handler.finish(); } @Test public void basicTest2() throws IOException { String json = " [true,\n\r \tfalse, null,[], {}, {\"key\":\"value\", \"array\" :\t[[], 8]}, \"string\", 1]\n\r"; Validator<JSONParser> handler = validator(SJ, SA, p(true), p(false), p(null), SA, EA, SO, EO, SO, sf("key"), p("value"), EF, sf("array"), SA, SA, EA, p(8), EA, EF, EO, p("string"), p(1), EA, EJ); JSONParser.create(stream(json), handler).parseFully(); handler.finish(); } @Test public void basicTest3() throws IOException { String json = " [true,\n\r \tfalse, null,[], {}, {\"ke\u3763y\":\"value\", \"array\uD834\uDD1E\" :\t[[], 8]}, \"string\", 1]\n\r"; Validator<JSONParser> handler = validator(SJ, SA, p(true), p(false), p(null), SA, EA, SO, EO, SO, sf("ke\u3763y"), p("value"), EF, sf("array\uD834\uDD1E"), SA, SA, EA, p(8), EA, EF, EO, p("string"), p(1), EA, EJ); JSONParser.create(stream(json), handler).parseFully(); handler.finish(); } @Test public void testParseLiteral() throws IOException { assertEquals("test string", JSONValue.parse("\"test string\"")); assertEquals(true, JSONValue.parse("true")); assertEquals(false, JSONValue.parse("false")); assertEquals(null, JSONValue.parse("null")); assertEquals(89, JSONValue.parse("89")); assertEquals(new BigDecimal("89.789"), JSONValue.parse("89.789")); } static void fpass(Object value, String json) throws IOException { fpass(value, json, STRICT); } static void fpass(Object value, String json, ParserOptions... options) throws IOException { assertFalse(value instanceof ParserOptions); assertTrue(json instanceof String); int count = 0; for (ParserOptions op : options) { count++; assertTrue(op instanceof ParserOptions); assertJsonEquals(value, doFullParseFragment(new CharSequenceStream(json), op)); assertJsonEquals(value, doFullParseFragment(new ReaderStream(new CharSequenceReader(json)), op)); } assertTrue(count > 0); } static void pass(Object value, String json, ParserOptions... options) throws IOException { assertFalse(value instanceof ParserOptions); assertTrue(json instanceof String); int count = 0; for (ParserOptions op : options) { count++; assertTrue(op instanceof ParserOptions); assertJsonEquals(value, doFullParse(new CharSequenceStream(json), op)); assertJsonEquals(value, doFullParse(new ReaderStream(new CharSequenceReader(json)), op)); } assertTrue(count > 0); } static Object doFullParse(JSONStream source, ParserOptions options) throws IOException { ObjectBuilder builder = new ObjectBuilder(); JSONParser parser = JSONParser.create(source, builder, options); parser.parseFully(); assertTrue(parser.isComplete()); assertTrue(builder.isComplete()); return builder.get(); } static Object doFullParseFragment(JSONStream source, ParserOptions options) throws IOException { FragmentBuilder builder = new FragmentBuilder(); JSONParser parser = JSONParser.create(source, builder, options); parser.parseFragment(); //can't assert complete because fragment tests are not always complete // assertTrue(parser.isComplete()); // assertTrue(builder.isComplete()); return builder.get(); } static void pass(String json) throws IOException { pass(json, STRICT); } static void pass(String json, ParserOptions... options) throws IOException { assertTrue(json instanceof String); int count = 0; for (ParserOptions op : options) { count++; assertTrue(op instanceof ParserOptions); doFullParse(new CharSequenceStream(json), op); doFullParse(new ReaderStream(new CharSequenceReader(json)), op); } assertTrue(count > 0); } static void fail(String json) throws IOException { fail(json, STRICT); } static void fail(String json, ParserOptions... options) throws IOException { assertTrue(json instanceof String); int count = 0; for (ParserOptions op : options) { count++; assertTrue(op instanceof ParserOptions); try { JSONValue.parse(json, op); Assert.fail(json + "\n (CharSequence) did not fail with options: " + op); } catch (JSONException ex) { } try { JSONValue.parse(new CharSequenceReader(json), op); Assert.fail(json + "\n (Readable) did not fail with options: " + op); } catch (JSONException ex) { } } assertTrue(count > 0); } static void ffail(String json) throws IOException { ffail(json, STRICT); } static void ffail(String json, ParserOptions... options) throws IOException { assertTrue(json instanceof String); int count = 0; for (ParserOptions op : options) { count++; assertTrue(op instanceof ParserOptions); try { JSONValue.parseFragment(json, op); Assert.fail(json + "\n (CharSequence) did not fail with options: " + op); } catch (JSONException ex) { } try { JSONValue.parseFragment(new CharSequenceReader(json), op); Assert.fail(json + "\n (Readable) did not fail with options: " + op); } catch (JSONException ex) { } } assertTrue(count > 0); } @Test public void testCommas() throws IOException { //array commas pass("[]"); pass("[true]"); pass("[false,null,100, \"test\"]"); //tests of primitives fail("[,]"); fail("[,,,,,]"); fail("[,true]"); fail("[,,,,,false]"); fail("[false,]"); fail("[null,,,,,]"); fail("[,8,\"\"]"); fail("[,,,,8,\"\"]"); fail("[8,,\"\"]"); fail("[100,,,,,\"test\"]"); fail("[,,,,,,null,,,,,\"test\",,,,,,]"); pass("[,]", COMMAS); pass("[,,,,,]", COMMAS); pass("[,true]", COMMAS); pass("[,,,,,false]", COMMAS); pass("[false,]", COMMAS); pass("[null,,,,,]", COMMAS); pass("[,8,\"\"]", COMMAS); pass("[,,,,8,\"\"]", COMMAS); pass("[8,,\"\"]", COMMAS); pass("[100,,,,,\"test\"]", COMMAS); pass("[,,,,,,null,,,,,\"test\",,,,,,]", COMMAS); //now with objects/arrays fail("[,[]]"); fail("[,{}]"); fail("[,,,,,[]]"); fail("[,,,,,{}]"); fail("[[],]"); fail("[{},]"); fail("[[],,,,,]"); fail("[{},,,,,]"); fail("[,[],{}]"); fail("[,,,,{},[]]"); fail("[[],,{}]"); fail("[[],,,,,{}]"); fail("[,,,,,,{},,,,,{},,,,,,]"); pass("[,[]]", COMMAS); pass("[,{}]", COMMAS); pass("[,,,,,[]]", COMMAS); pass("[,,,,,{}]", COMMAS); pass("[[],]", COMMAS); pass("[{},]", COMMAS); pass("[[],,,,,]", COMMAS); pass("[{},,,,,]", COMMAS); pass("[,[],{}]", COMMAS); pass("[,,,,{},[]]", COMMAS); pass("[[],,{}]", COMMAS); pass("[[],,,,,{}]", COMMAS); pass("[,,,,,,{},,,,,{},,,,,,]", COMMAS); //now nested fail("[,,,,[,null,100,[,,,]],,[,false,,{},,],,[false,[,]],,,[true,]]"); pass("[,,,,[,null,100,[,,,]],,[,false,,{},,],,[false,[,]],,,[true,]]", COMMAS); //object commas pass("{}"); pass("{'k' : false}".replace('\'', '"')); pass("{'k' : false, 'key' : 'test'}".replace('\'', '"')); fail("{,}".replace('\'', '"')); fail("{,,,,,}".replace('\'', '"')); fail("{, 'k':false}".replace('\'', '"')); fail("{, ,,,,,\n,'k':false}".replace('\'', '"')); fail("{'k':false,}".replace('\'', '"')); fail("{'k':false,,,\t, , }".replace('\'', '"')); fail("{'k':false,,'a':'b'}".replace('\'', '"')); fail("{'k':false,, \t,,,'a':null}".replace('\'', '"')); fail("{,'k':100,'a':null}".replace('\'', '"')); fail("{\n,,,\t,'k':100,'a':null}".replace('\'', '"')); fail("{'k':100,'a':true,}".replace('\'', '"')); fail("{'k':100,'a':true,,,\n\tr\\r, }".replace('\'', '"')); fail("{,,,,'k':100,,\n,,'a':true,,,\n\t\r, }".replace('\'', '"')); pass("{,}".replace('\'', '"'), COMMAS); pass("{,,,,,}".replace('\'', '"'), COMMAS); pass("{, 'k':false}".replace('\'', '"'), COMMAS); pass("{, ,,,,,\n,'k':false}".replace('\'', '"'), COMMAS); pass("{'k':false,}".replace('\'', '"'), COMMAS); pass("{'k':false,,,\t, , }".replace('\'', '"'), COMMAS); pass("{'k':false,,'a':'b'}".replace('\'', '"'), COMMAS); pass("{'k':false,, \t,,,'a':null}".replace('\'', '"'), COMMAS); pass("{,'k':100,'a':null}".replace('\'', '"'), COMMAS); pass("{\n,,,\t,'k':100,'a':null}".replace('\'', '"'), COMMAS); pass("{'k':100,'a':true,}".replace('\'', '"'), COMMAS); pass("{'k':100,'a':true,,,\n\t\r, }".replace('\'', '"'), COMMAS); pass("{,,,,'k':100,,\n,,'a':true,,,\n\t\r, }".replace('\'', '"'), COMMAS); //nested objects and arrays fail("{,,'k' : {, 'test' : [,,,,null,['valid;'],],,,,'key2': 544,,,},,\n\r,, 't':[[,,],\t[null,,],false,{'valid':9}], 'h': [,,false],,\n, }" .replace('\'', '"')); pass("{,,'k' : {, 'test' : [,,,,null,['valid;'],],,,,'key2': 544,,,},,\n\r,, 't':[[,,],\t[null,,],false,{'valid':9}], 'h': [,,false],,\n, }" .replace('\'', '"'), COMMAS); } @Test @SuppressWarnings("unchecked") public void testComments() throws IOException { assertEquals(ALL_COMMENTS, COMMENTS.or(YAML)); final ParserOptions CLENIENT = LENIENT.toBuilder().allowUnquotedStrings(false).build(); Object value = Arrays.asList(false, null, 100); //c style comments fail("[false //single line invalid[11[,false{json\"\u0000\t\n//\"\u0000\r,null/* mulitline comment \"\r\n\t \u0001 ** // **/ ,100]", STRICT, YAML); pass(value, "[false //single line invalid[11[,false{json\"\u0000\t\n//\"\u0000\r,null/* mulitline comment \"\r\n\t \u0001 ** // **/ ,100]", COMMENTS, CLENIENT); //YAML comments fail("[false #single line invalid[11[,false{json\"\u0000\t\n#\"\u0000\r,null ,100]", STRICT, COMMENTS); pass(value, "[false #single line invalid[11[,false{json\"\u0000\t\n#\"\u0000\r,null ,100]", CLENIENT, YAML); //both styles fail("[false //single line invalid[11[,false{json\"\u0000\t\n#\"\u0000\r,null/* mulitline comment \"\r\n\t \u0001 ** // **/ ,100]", STRICT, YAML, COMMENTS); pass(value, "[false //single line invalid[11[,false{json\"\u0000\t\n#\"\u0000\r,null/* mulitline comment \"\r\n\t \u0001 ** // **/ ,100]", ALL_COMMENTS, CLENIENT); } @Test public void testUnquotedFieldNames() throws IOException { assertEquals(ALL_COMMENTS, COMMENTS.or(YAML)); Map<String, Object> map = Maps.newHashMap(); map.put("field", false); map.put("0key", null); map.put("s'tr\r\t\n\u0000 ", "string"); fail("{ field : false, 0key : null, s'tr\\r\\t\\n\\u0000\\u0020 : \"string\"}"); pass(map, "{ field : false, 0key : null, s'tr\\r\\t\\n\\u0000\\u0020 : \"string\"}", UNQUOTED_FIELDS, LENIENT); //no field text at all map.put("", 789); fail("{ :\"over\", field : false, 0key : null, s'tr\\r\\t\\n\\u0000\\u0020 : \"string\", : 789}"); pass(map, "{ :\"over\", field : false, 0key : null, s'tr\\r\\t\\n\\u0000\\u0020 : \"string\", : 789}", UNQUOTED_FIELDS, LENIENT); } @SuppressWarnings({ "rawtypes", "unchecked" }) private static Map map(Object... contents) { assertTrue(contents.length % 2 == 0); Map map = new HashMap(); for (int i = 0; i < contents.length; i += 2) { assertFalse(map.containsKey(contents[i])); map.put(contents[i], contents[i + 1]); } return map; } @SuppressWarnings({ "rawtypes", "unchecked" }) private static List list(Object... contents) { return Arrays.asList(contents); } @Test @SuppressWarnings({ "rawtypes", "unchecked" }) public void testUnquotedStrings() throws IOException { //map tests fail("{'a': nulL}".replace('\'', '"')); pass(map("a", "nulL"), "{'a': nulL}".replace('\'', '"'), UNQUOTED_STRINGS); fail("{'a': 23a}".replace('\'', '"')); pass(map("a", "23a"), "{'a': 23a}".replace('\'', '"'), UNQUOTED_STRINGS); fail("{'a': true2}".replace('\'', '"')); pass(map("a", "true2"), "{'a': true2}".replace('\'', '"'), UNQUOTED_STRINGS); fail("{'a': ap[{}".replace('\'', '"')); pass(map("a", "ap[{"), "{'a': ap[{}".replace('\'', '"'), UNQUOTED_STRINGS); fail("{'a': }".replace('\'', '"')); pass(map("a", ""), "{'a': }".replace('\'', '"'), UNQUOTED_STRINGS); fail("{'a': ,}".replace('\'', '"'), STRICT, UNQUOTED_STRINGS); pass(map("a", ""), "{'a': ,}".replace('\'', '"'), UNQUOTED_STRINGS.or(COMMAS)); fail("{'a': ,'b' :}".replace('\'', '"')); pass(map("a", "", "b", ""), "{'a': ,'b' :}".replace('\'', '"'), UNQUOTED_STRINGS); fail("{'a': ap[]{,'b' :}".replace('\'', '"')); pass(map("a", "ap[]{", "b", ""), "{'a': ap[]{,'b' :}".replace('\'', '"'), UNQUOTED_STRINGS); //list tests fail("[nulL]".replace('\'', '"')); pass(list("nulL"), "[nulL]".replace('\'', '"'), UNQUOTED_STRINGS); fail("[true2, 23a, false{}[: , true2\n, ap[{} ]".replace('\'', '"')); pass(list("true2", "23a", "false{}[:", "true2", "ap[{}"), "[true2, 23a, false{}[: , true2\n, ap[{} ]".replace('\'', '"'), UNQUOTED_STRINGS); } @Test public void testSingleQuotes() throws IOException { assertEquals(ALL_COMMENTS, COMMENTS.or(YAML)); List<Object> value = Lists.newArrayList(); Map<String, Object> map = Maps.newHashMap(); map.put("single", "single"); map.put("s", "d"); map.put("d", "test"); value.add("single quote"); value.add("double"); value.add(map); fail("['single quote', \"double\", {'single' : 'single', 's' : \"d\", \"d\" : 'test'}]"); pass(value, "['single quote', \"double\", {'single' : 'single', 's' : \"d\", \"d\" : 'test'}]", SINGLE, LENIENT); } @Test public void testUnescapedControlChars() throws IOException { assertEquals(ALL_COMMENTS, COMMENTS.or(YAML)); List<Object> value = Lists.newArrayList(); Map<String, Object> map = Maps.newHashMap(); map.put("\r\t\b\n\f\u0003", "\u0005\u001F"); value.add("\u0000\r\t\n\t\b\f\u0012\u000b"); value.add(map); fail("['\u0000\r\t\n\t\b\f\u0012\\u000b', {'\r\t\b\n\f\u0003' : '\u0005\u001F'}]".replace('\'', '"')); pass(value, "['\u0000\r\t\n\t\b\f\u0012\\u000b', {'\r\t\b\n\f\u0003' : '\u0005\u001F'}]".replace('\'', '"'), UNESCAPED_CONTROL, LENIENT); pass(value, "['\u0000\r\t\n\t\b\f\u0012\\u000b', {'\r\t\b\n\f\u0003' : '\u0005\u001F'}]", SINGLE.or(UNESCAPED_CONTROL), LENIENT); } @Test public void testCaseInsensitiveLiterals() throws IOException { assertEquals(ALL_COMMENTS, COMMENTS.or(YAML)); List<Object> value = Arrays.<Object>asList(true, true, true, false, false, false, null, null, null); fail("[True, trUe, TRUE, False, falSe, FALSE, Null, nuLl, NULL]"); pass(value, "[True, trUe, TRUE, False, falSe, FALSE, Null, nuLl, NULL]", CASE, LENIENT); } @Test public void testBackslashAny() throws IOException { assertEquals(ALL_COMMENTS, COMMENTS.or(YAML)); Map<String, Object> map = Maps.newHashMap(); map.put("\test}", " \" \t"); List<Object> value = Arrays.<Object>asList("5o", map); fail("[\"\\5\\o\", {\"\\test\\}\" : \" \\\" \\\t\"}]"); pass(value, "[\"\\5\\o\", {\"\\test\\}\" : \" \\\" \\\t\"}]", BACKSLASH, LENIENT); } private static double d(String str) { return Double.parseDouble(str); } private static BigDecimal bd(String str) { return new BigDecimal(str); } private static BigInteger bi(String str, int radix) { return new BigInteger(str, radix); } @Test public void testAllNonStandardFeatures() throws IOException { assertEquals(ALL_COMMENTS, COMMENTS.or(YAML)); Map<String, Object> map = Maps.newHashMap(); map.put("unq'8\u0000", "test"); List<Object> value = Arrays.<Object>asList(true, false, null, 0xFFF, 0xFFFFFFFFFL, bi("FFFFFFFFFFFFFFFFFFFFFFF", 16), d("0xabcd.45p8d"), d("89f"), "\u0000\ny", "\t7", Double.NaN, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 8, 0, 0, d("0x00p5"), bd(".89"), d("+.9D"), map); fail("[,True, False, Null,, 0xFFF, 0xFFFFFFFFF, 0xFFFFFFFFFFFFFFFFFFFFFFF, /* 100[ */ 0xabcd.45p8d, 89f, # y5{\n \"\u0000\n\\y\", '\t\\7', NaN, nan, Inf, -inFiNitY , 0008, +0, //90{'90\r -0, 0x00p5, .89, +.9D, { , unq'8\u0000\n : 'test' ,,} ]"); pass(value, "[,True, False, Null,, 0xFFF, 0xFFFFFFFFF, 0xFFFFFFFFFFFFFFFFFFFFFFF, /* 100[ */ 0xabcd.45p8d, 89f, # y5{\n \"\u0000\n\\y\", '\t\\7', NaN, nan, Inf, -inFiNitY , 0008, +0, //90{'90\r -0, 0x00p5, .89, +.9D, { , unq'8\u0000\n : 'test' ,,} ]", LENIENT); } @Test public void testParseFirstObject() throws IOException { Map<String, Object> map = Maps.newHashMap(); map.put("k", "test"); map.put("n", 99); String json = "[ null, {\"k\" : \"test\", \"n\" : 99} ] invali json ["; ObjectBuilder builder = new ObjectBuilder(); JSONParser parser = JSONParser.create(new CharSequenceStream(json), builder, STRICT); parser.parseFirstObject(); assertTrue(parser.isComplete()); assertTrue(builder.isComplete()); assertJsonEquals(map, builder.get()); } @Test public void testParseFirstArray() throws IOException { String json = " {}}}}}}} [null, true, false, 1000] #67["; ObjectBuilder builder = new ObjectBuilder(); JSONParser parser = JSONParser.create(new CharSequenceStream(json), builder, STRICT); parser.parseFirstArray(); assertTrue(parser.isComplete()); assertTrue(builder.isComplete()); assertJsonEquals(Arrays.<Object>asList(null, true, false, 1000), builder.get()); } @Test public void testParseFirst() throws IOException { String json = "[null, true, false, 1000] #67["; ObjectBuilder builder = new ObjectBuilder(); JSONParser parser = JSONParser.create(new CharSequenceStream(json), builder, STRICT); parser.parseFirst(); assertTrue(parser.isComplete()); assertTrue(builder.isComplete()); assertJsonEquals(Arrays.<Object>asList(null, true, false, 1000), builder.get()); Map<String, Object> map = Maps.newHashMap(); map.put("k", "test"); map.put("n", 99); json = "{\"k\" : \"test\", \"n\" : 99} ]5t6$ invali json ["; builder = new ObjectBuilder(); parser = JSONParser.create(new CharSequenceStream(json), builder, STRICT); parser.parseFirst(); assertTrue(parser.isComplete()); assertTrue(builder.isComplete()); assertJsonEquals(map, builder.get()); } @Test @SuppressWarnings("StringEquality") public void testParsePart() throws IOException { // String text = " [true,\n\r \tfalse, null,[], {}, {\"key\":\"value\", \"array\" :\t[[], 8]}, \"string\", 1]\n\r"; String json[] = { " [true,\n\r \tfalse, null,[], {}, ", "{\"key\":\"value\", \"array\" ", ":\t[[], 8]}, \"string\", 1", "]", "\n\r \t" }; Map<String, Object> map = Maps.newHashMap(); map.put("key", "value"); map.put("array", Arrays.<Object>asList(Collections.emptyList(), 8)); List<Object> value = Arrays.<Object>asList(true, false, null, Collections.emptyList(), Collections.emptyMap(), map, "string", 1); Validator<JSONParser> validator = validator(SJ, SA, p(true), p(false), p(null), SA, EA, SO, EO, SO, sf("key"), p("value"), EF, sf("array"), SA, SA, EA, p(8), EA, EF, EO, p("string"), p(1), EA, EJ); JSONParser parser = JSONParser.create(Streams.empty(), validator, STRICT); for (String part : json) { parser.setSource(part); parser.parsePart(); if (part != json[3] && part != json[4]) { assertFalse(parser.isComplete()); } } parser.finish(); assertTrue(parser.isComplete()); validator.finish(); //now use the builder ObjectBuilder builder = new ObjectBuilder(); parser.reset(); parser.setHandler(builder); for (String part : json) { parser.setSource(part); parser.parsePart(); if (part != json[3] && part != json[4]) { assertFalse(parser.isComplete()); assertFalse(builder.isComplete()); } } parser.finish(); assertTrue(parser.isComplete()); assertTrue(builder.isComplete()); assertJsonEquals(value, builder.get()); } static void assertComplete(JSONParser parser) { assertTrue(parser.isComplete()); JSONHandler<?> handler = parser.getHandler(); if (handler instanceof ObjectBuilder) { assertTrue(((ObjectBuilder) handler).isComplete()); } else if (handler instanceof FragmentBuilder) { assertTrue(((FragmentBuilder) handler).isComplete()); } } static void assertNotComplete(JSONParser parser) { assertFalse(parser.isComplete()); JSONHandler<?> handler = parser.getHandler(); if (handler instanceof ObjectBuilder) { assertFalse(((ObjectBuilder) handler).isComplete()); } else if (handler instanceof FragmentBuilder) { assertFalse(((FragmentBuilder) handler).isComplete()); } } @Test public void testParseFragmentMap() throws IOException { Map<String, Object> map = Maps.newHashMap(); fpass(map, ": false"); fpass(map, ": false}"); fpass(map, ": {}"); fpass(map, ": []}"); fpass(map, ": [false]"); fpass(map, ": [false, [true, [{}]], {}]}"); fpass(map, ": [false, [true, [{}]], {}]"); fpass(map, ": false}"); fpass(map, ": 'testing'}".replace('\'', '"')); fpass(map, ": 'testing'}, \n".replace('\'', '"')); fpass(map, "}"); fpass(map, "\n} ,"); fpass(map, ", false} ,"); fpass(map, ",'only one root'} ,".replace('\'', '"')); fpass(map, ", [false, [true, [{}, [false]]]]} ,"); fpass(map, "false}"); fpass(map, ", []} ,\n"); fpass(map, " [false, [true, [{}]], {}]}"); fpass(map, " false}"); map.put("k", false); fpass(map, " 'k' : false", SINGLE); fpass(map, " 'k' : false}\t", SINGLE); fpass(map, " ,'k' : false", SINGLE); fpass(map, " ,'k' : false}", SINGLE); fpass(map, "false ,'k' : false}", SINGLE); fpass(map, ", 'testing' ,'k' : false}", SINGLE); fpass(map, "false ,'k' : false", SINGLE); fpass(map, "9959727.8713357 ,'k' : false", SINGLE); fpass(map, ", [[[[{}, false, []]]], {}] ,'k' : false", SINGLE); fpass(map, ", [[[[{}, false, []]]], {}] ,'k' : false}", SINGLE); fpass(map, " [[[[{}, false, []]]], {}] ,'k' : false", SINGLE); fpass(map, ", [[[[{}, false, []]]], {}] ,'k' : false}\t,\r", SINGLE); fpass(map, ", 'testing' ,'k' : false", SINGLE); fpass(map, ", 'testing' ,'k' : false", SINGLE); fpass(map, ", 'testing' ,'k' : false", SINGLE); fpass(map, ", 'testing' ,'k' : false", SINGLE); fpass(map, ", 'testing' ,'k' : false", SINGLE); fpass(map, ", 'testing' ,'k' : false", SINGLE); List<Object> list = Lists.newArrayList(); list.add(map); ffail(", 'testing' ,'k' : false]", SINGLE); fpass(list, ", 'testing' ,'k' : false}] ,", SINGLE); ffail("[, 'testing' ,'k' : false}", SINGLE); list.add(100); fpass(list, ", 'testing' ,'k' : false} , 100", SINGLE); fpass(list, ", 'testing' ,'k' : false} , 100 ,", SINGLE); fpass(list, ", 'testing' ,'k' : false} , 100 ] ,", SINGLE); // fpass(map, null, SINGLE); ffail("789 : false", SINGLE); ffail("789 : 'testing'", SINGLE); ffail(", null : true", SINGLE); ffail(", false : true", SINGLE); ffail("false , false : true", SINGLE); ffail("'too many', false , 'key' : true", SINGLE); ffail(", 'testing' ,true : false", SINGLE); ffail("'too many roots', 'testing' ,'t' : false", SINGLE); ffail("'too many roots', null}", SINGLE); ffail("false, null}", SINGLE); ffail("789 : false}", SINGLE); ffail("789 : 'testing'}", SINGLE); ffail(", null : true}", SINGLE); ffail(", false : true}", SINGLE); ffail("false , false : true}", SINGLE); ffail("'too many', false , 'key' : true}", SINGLE); ffail(", 'testing' ,true : false}", SINGLE); ffail("'too many roots', 'testing' ,'t' : false}", SINGLE); ffail("'too many roots', null}", SINGLE); ffail("false, null}", SINGLE); ffail("'too many roots', 'test'}", SINGLE); } @Test public void testParseFragmentList() throws IOException { List<Object> list = Lists.newArrayList(); fpass(null, ""); fpass(null, ","); fpass(null, "null"); fpass(false, "false"); fpass(true, "true"); fpass(100, "100"); fpass("test", "\"test\"\n\n"); fpass(list, "]"); ffail(",]"); fpass(list, ",]", COMMAS); fpass(list, " ] ,"); ffail(", ] "); fpass(list, ", ] ", COMMAS); ffail(", ] , "); fpass(list, ", ] , ", COMMAS); ffail(" [ ,"); fpass(list, " [ ,", COMMAS); fpass(list, ", [ "); ffail(", [ , "); fpass(list, ", [ , ", COMMAS); fpass(list, "[],"); fpass(list, ", [], "); list.add(true); fpass(list, "[true"); fpass(list, "[true ,"); fpass(list, " , [true"); fpass(list, ", [true ,"); fpass(list, "true]"); fpass(list, " , true]"); fpass(list, "true],"); fpass(list, ",true] ,"); list.add(false); fpass(list, "true, false"); fpass(list, ", true, false"); fpass(list, "true, false,"); fpass(list, ",true, false,"); fpass(list, "[true, false"); fpass(list, "[true, false ,"); fpass(list, ", [true, false"); fpass(list, " ,[true, false ,"); fpass(list, "true, false]"); fpass(list, "true, false] ,"); fpass(list, ", true, false]"); fpass(list, " ,true, false] ,"); fpass(list, "[true, false]"); fpass(list, "[true, false] ,"); fpass(list, ", [true, false]"); fpass(list, " ,[true, false] ,"); list = Arrays.<Object>asList(list); fpass(list, "[[true, false"); fpass(list, ", [[true, false,"); fpass(list, "true, false]]"); fpass(list, ", true, false]] ,"); } @Test public void testFragmentDeepList() throws IOException { int depth = 300000; StringBuilder sb = new StringBuilder(depth + 16); final List<Object> head = Lists.newArrayList(); List<Object> tail = head; for (; depth > 0; depth--) { List<Object> list = Lists.newArrayListWithCapacity(1); tail.add(list); tail = list; sb.append("]"); } head.add("test"); sb.append(", \"test\""); assertJsonEqualsNoRecurse(head, JSONValue.parseFragment(sb, STRICT)); } @Test public void testFragmentDeepList2() throws IOException { int depth = 300000; StringBuilder sb = new StringBuilder(depth + 16); final List<Object> head = Lists.newArrayList(); sb.append("["); List<Object> tail = head; for (; depth > 0; depth--) { List<Object> list = Lists.newArrayListWithCapacity(1); tail.add(list); tail = list; sb.append("["); } tail.add("test"); sb.append("\"test\""); assertJsonEqualsNoRecurse(head, JSONValue.parseFragment(sb, STRICT)); } @Test public void testFragmentShallowList() throws IOException { int depth = 100; StringBuilder sb = new StringBuilder(depth + 16); final List<Object> head = Lists.newArrayList(); List<Object> tail = head; for (; depth > 0; depth--) { List<Object> list = Lists.newArrayListWithCapacity(1); tail.add(list); tail = list; sb.append("]"); } head.add("test"); sb.append(", \"test\""); assertJsonEquals(head, JSONValue.parseFragment(sb, STRICT)); } @Test public void testFragmentDeepMap() throws IOException { int depth = 300000; String key = "k"; StringBuilder sb = new StringBuilder(depth + 16); sb.append(String.format("\"%s\" : ", key)); final Map<String, Object> head = MapUtils.newMutableSingletonMap(); Map<String, Object> tail = head; for (; depth > 0; depth--) { Map<String, Object> map = MapUtils.newMutableSingletonMap(); tail.put(key, map); tail = map; sb.append(String.format("{ \"%s\" : ", key)); } assertJsonEqualsNoRecurse(head, JSONValue.parseFragment(sb, STRICT)); } @Test public void testFragmentShallowMap() throws IOException { int depth = 100; String key = "k"; StringBuilder sb = new StringBuilder(depth + 16); sb.append(String.format("\"%s\" : ", key)); final Map<String, Object> head = MapUtils.newMutableSingletonMap(); Map<String, Object> tail = head; for (; depth > 0; depth--) { Map<String, Object> map = MapUtils.newMutableSingletonMap(); tail.put(key, map); tail = map; sb.append(String.format("{ \"%s\" : ", key)); } assertJsonEquals(head, JSONValue.parseFragment(sb, STRICT)); } }