View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.json;
18  
19  import static org.apache.juneau.TestUtils.*;
20  import static org.apache.juneau.junit.bct.BctAssertions.*;
21  import static org.junit.jupiter.api.Assertions.*;
22  
23  import java.io.*;
24  
25  import org.apache.juneau.*;
26  import org.apache.juneau.collections.*;
27  import org.apache.juneau.parser.*;
28  import org.junit.jupiter.api.*;
29  
30  class JsonParser_Test extends TestBase {
31  
32  	private static final JsonParser p = JsonParser.DEFAULT;
33  	private static final JsonParser sp = JsonParser.DEFAULT_STRICT;
34  
35  	//====================================================================================================
36  	// Test invalid input
37  	//====================================================================================================
38  	@Test void a01_invalidJson() {
39  		assertThrows(ParseException.class, ()->p.parse("{\na:1,\nb:xxx\n}", Object.class));
40  	}
41  
42  	@Test void a02_nonExistentAttribute() throws Exception {
43  		var json = "{foo:,bar:}";
44  		var m = p.parse(json, JsonMap.class);
45  		assertEquals("{foo:null,bar:null}", m.toString());
46  	}
47  
48  	@Test void a03_nonStringAsString() throws Exception {
49  		var json = "123";
50  		var s = p.parse(json, String.class);
51  
52  		// Strict mode does not allow unquoted values.
53  		assertThrowsWithMessage(Exception.class, "Did not find quote character", ()->sp.parse("123", String.class));
54  
55  		assertEquals("123", s);
56  
57  		json = " 123 ";
58  		// Strict mode does not allow unquoted values.
59  		assertThrowsWithMessage(Exception.class, "Did not find quote character", ()->sp.parse(" 123 ", String.class));
60  
61  		s = p.parse(json, String.class);
62  		assertEquals("123", s);
63  
64  		json = "{\"fa\":123}";
65  		assertThrowsWithMessage(Exception.class, "Did not find quote character", ()->sp.parse("{\"fa\":123}", A.class));
66  
67  		var a = p.parse(json, A.class);
68  		assertEquals("123", a.fa);
69  
70  		json = " { \"fa\" : 123 } ";
71  		assertThrowsWithMessage(Exception.class, "Did not find quote character", ()->sp.parse(" { \"fa\" : 123 } ", A.class));
72  
73  		a = p.parse(json, A.class);
74  		assertEquals("123", a.fa);
75  
76  		assertThrowsWithMessage(Exception.class, "Invalid quote character", ()->sp.parse("'123'", String.class));
77  	}
78  
79  	public static class A {
80  		public String fa;
81  	}
82  
83  	@Test void a04_strictMode() {
84  		var p2 = sp;
85  		assertThrowsWithMessage(Exception.class, "Missing value detected.", ()->p2.parse("{\"foo\":,\"bar\":}", JsonMap.class));
86  		assertThrowsWithMessage(Exception.class, "Invalid quote character", ()->p2.parse("{\"foo\":'bar'}", JsonMap.class));
87  		assertThrowsWithMessage(Exception.class, "Invalid quote character", ()->p2.parse("{'foo':\"bar\"}", JsonMap.class));
88  		assertThrowsWithMessage(Exception.class, "Unquoted attribute detected.", ()->p2.parse("{foo:\"bar\"}", JsonMap.class));
89  		assertThrowsWithMessage(Exception.class, "String concatenation detected.", ()->p2.parse("{\"foo\":\"bar\"+\"baz\"}", JsonMap.class));
90  		assertThrowsWithMessage(Exception.class, "String concatenation detected.", ()->p2.parse("{\"foo\":\"bar\" + \"baz\"}", JsonMap.class));
91  		assertThrowsWithMessage(Exception.class, "Javascript comment detected.", ()->p2.parse("{\"foo\":/*comment*/\"bar\"}", JsonMap.class));
92  	}
93  
94  	/**
95  	 * JSON numbers and booleans should be representable as strings and converted accordingly.
96  	 */
97  	@Test void a05_primitivesAsStrings() throws Exception {
98  		var p2 = JsonParser.DEFAULT;
99  		var s = Json5Serializer.DEFAULT;
100 
101 		var json = "{f01:'1',f02:'1',f03:'true',f04:'true',f05:'1',f06:'1',f07:'1',f08:'1',f09:'1',f10:'1'}";
102 		var b = p2.parse(json, B.class);
103 		assertEquals("{f01:1,f02:1,f03:true,f04:true,f05:1.0,f06:1.0,f07:1,f08:1,f09:1,f10:1}", s.toString(b));
104 
105 		json = "{f01:'',f02:'',f03:'',f04:'',f05:'',f06:'',f07:'',f08:'',f09:'',f10:''}";
106 		b = p2.parse(json, B.class);
107 		assertEquals("{f01:0,f02:0,f03:false,f04:false,f05:0.0,f06:0.0,f07:0,f08:0,f09:0,f10:0}", s.toString(b));
108 	}
109 
110 	public static class B {
111 		public int f01;
112 		public Integer f02;
113 		public boolean f03;
114 		public Boolean f04;
115 		public float f05;
116 		public Float f06;
117 		public long f07;
118 		public Long f08;
119 		public byte f09;
120 		public Byte f10;
121 	}
122 
123 	//====================================================================================================
124 	// testInvalidJsonNumbers
125 	// Lax parser allows octal and hexadecimal numbers.  Strict parser does not.
126 	//====================================================================================================
127 	@Test void a06_invalidJsonNumbers() throws Exception {
128 		var p1 = JsonParser.DEFAULT;
129 		var p2 = JsonParser.DEFAULT_STRICT;
130 
131 		// Lax allows blank strings interpreted as 0, strict does not.
132 		var s = "\"\"";
133 		var r = p1.parse(s, Number.class);
134 		assertEquals(0, r.intValue());
135 		assertTrue(r instanceof Integer);
136 		assertThrowsWithMessage(Exception.class, "Invalid JSON number", ()->p2.parse("\"\"", Number.class));
137 
138 		// Either should allow 0 or -0.
139 		s = "0";
140 		r = p1.parse(s, Number.class);
141 		assertEquals(0, r.intValue());
142 		assertTrue(r instanceof Integer);
143 		r = p2.parse(s, Number.class);
144 		assertEquals(0, r.intValue());
145 		assertTrue(r instanceof Integer);
146 
147 		s = "-0";
148 		r = p1.parse(s, Number.class);
149 		assertEquals(0, r.intValue());
150 		assertTrue(r instanceof Integer);
151 		r = p2.parse(s, Number.class);
152 		assertEquals(0, r.intValue());
153 		assertTrue(r instanceof Integer);
154 
155 		// Lax allows 0123 and -0123, strict does not.
156 		s = "0123";
157 		r = p1.parse(s, Number.class);
158 		assertEquals(0123, r.intValue());
159 		assertTrue(r instanceof Integer);
160 		assertThrowsWithMessage(Exception.class, "Invalid JSON number", ()->p2.parse("0123", Number.class));
161 		s = "-0123";
162 		r = p1.parse(s, Number.class);
163 		assertEquals(-0123, r.intValue());
164 		assertTrue(r instanceof Integer);
165 		assertThrowsWithMessage(Exception.class, "Invalid JSON number", ()->p2.parse("-0123", Number.class));
166 
167 		// Lax allows 0x123 and -0x123, strict does not.
168 		s = "0x123";
169 		r = p1.parse(s, Number.class);
170 		assertEquals(0x123, r.intValue());
171 		assertTrue(r instanceof Integer);
172 		assertThrowsWithMessage(Exception.class, "Invalid JSON number", ()->p2.parse("0x123", Number.class));
173 		s = "-0x123";
174 		r = p1.parse(s, Number.class);
175 		assertEquals(-0x123, r.intValue());
176 		assertTrue(r instanceof Integer);
177 		assertThrowsWithMessage(Exception.class, "Invalid JSON number", ()->p2.parse("-0x123", Number.class));
178 	}
179 
180 	//====================================================================================================
181 	// testUnquotedStrings
182 	// Lax parser allows unquoted strings if POJO can be converted from a string.
183 	//====================================================================================================
184 	@Test void a07_unquotedStrings() throws Exception {
185 		var p1 = JsonParser.DEFAULT;
186 		var p2 = JsonParser.DEFAULT_STRICT;
187 
188 		var s = "foobar";
189 		var c = p1.parse(s, C.class);
190 		assertEquals("f=foobar", c.toString());
191 
192 		assertThrows(ParseException.class, ()->p2.parse(s, C.class));
193 	}
194 
195 	public static class C {
196 		String f;
197 		public static C valueOf(String s) {
198 			var c = new C();
199 			c.f = s;
200 			return c;
201 		}
202 		@Override /* Overridden from Object */
203 		public String toString() {
204 			return "f="+f;
205 		}
206 	}
207 
208 	//====================================================================================================
209 	// testStreamsAutoClose
210 	// Validates PARSER_autoCloseStreams.
211 	//====================================================================================================
212 	@Test void a08_streamsAutoClose() throws Exception {
213 		var p2 = JsonParser.DEFAULT.copy().autoCloseStreams().build();
214 		var r = reader("{foo:'bar'}{baz:'qux'}");
215 
216 		var x = p2.parse(r, JsonMap.class);
217 		assertBean(x, "foo", "bar");
218 		assertThrowsWithMessage(Exception.class, "Reader is closed", ()->p2.parse(r, JsonMap.class));
219 	}
220 
221 	//====================================================================================================
222 	// testMultipleObjectsInStream
223 	// Validates that readers are not closed so that we can read streams of POJOs.
224 	//====================================================================================================
225 	@Test void a09_multipleObjectsInStream() throws Exception {
226 		var p2 = JsonParser.create().unbuffered().build();
227 		var r = reader("{foo:'bar'}{baz:'qux'}");
228 
229 		var x = (Object)p2.parse(r, JsonMap.class);
230 		assertBean(x, "foo", "bar");
231 		x = p2.parse(r, JsonMap.class);
232 		assertBean(x, "baz", "qux");
233 
234 		r = reader("[123][456]");
235 		x = p2.parse(r, JsonList.class);
236 		assertList(x, "123");
237 		x = p2.parse(r, JsonList.class);
238 		assertList(x, "456");
239 	}
240 
241 	private static Reader reader(String in) {
242 		return new CloseableStringReader(in);
243 	}
244 }