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.collections;
18  
19  import static org.apache.juneau.commons.utils.AssertionUtils.*;
20  import static org.apache.juneau.commons.utils.CollectionUtils.*;
21  import static org.apache.juneau.commons.utils.PredicateUtils.*;
22  import static org.apache.juneau.commons.utils.StringUtils.*;
23  import static org.apache.juneau.commons.utils.ThrowableUtils.*;
24  import static org.apache.juneau.commons.utils.Utils.*;
25  
26  import java.io.*;
27  import java.lang.reflect.*;
28  import java.util.*;
29  import java.util.function.*;
30  
31  import org.apache.juneau.*;
32  import org.apache.juneau.commons.utils.*;
33  import org.apache.juneau.json.*;
34  import org.apache.juneau.marshaller.*;
35  import org.apache.juneau.objecttools.*;
36  import org.apache.juneau.parser.*;
37  import org.apache.juneau.serializer.*;
38  import org.apache.juneau.swap.*;
39  
40  /**
41   * Java implementation of a JSON object.
42   *
43   * <p>
44   * An extension of {@link LinkedHashMap}, so all methods available in that class are also available to this class.
45   *
46   * <p>
47   * Note that the use of this class is optional for generating JSON. The serializers will accept any objects that implement the
48   * {@link java.util.Map} interface.  But this class provides some useful additional functionality when working with
49   * JSON models constructed from Java Collections Framework objects.  For example, a constructor is provided for
50   * converting a JSON object string directly into a {@link Map}.  It also contains accessor methods for to avoid common
51   * typecasting when accessing elements in a list.
52   *
53   * <h5 class='section'>Example:</h5>
54   * <p class='bjava'>
55   * 	<jc>// Construct an empty Map</jc>
56   * 	JsonMap <jv>map</jv> = JsonMap.<jsm>of</jsm>();
57   *
58   * 	<jc>// Construct a Map from JSON</jc>
59   * 	<jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{a:'A',b:{c:'C',d:123}}"</js>);
60   *
61   * 	<jc>// Construct a Map using the append method</jc>
62   * 	<jv>map</jv> = JsonMap.<jsm>of</jsm>().a(<js>"foo"</js>,<js>"x"</js>).a(<js>"bar"</js>,123).a(<js>"baz"</js>,<jk>true</jk>);
63   *
64   * 	<jc>// Construct a Map from XML generated by XmlSerializer</jc>
65   * 	String <jv>xml</jv> = <js>"&lt;object&gt;&lt;a type='string'&gt;A&lt;/a&gt;&lt;b type='object'&gt;&lt;c type='string'&gt;C&lt;/c&gt;&lt;d type='number'&gt;123&lt;/d&gt;&lt;/b&gt;&lt;/object&gt;"</js>;
66   * 	<jv>map</jv> = JsonMap.<jsm>of</jsm>(<jv>xml</jv>, XmlParser.<jsf>DEFAULT</jsf>);
67   *
68   * 	<jc>// Construct a Map from a URL GET parameter string generated by UrlEncodingParser</jc>
69   * 	String <jv>urlParams</jv> = <js>"?a='A'&amp;b={c:'C',d:123}"</js>;
70   * 	<jv>map</jv> = JsonMap.<jsm>of</jsm>(<jv>urlParams</jv>, UrlEncodingParser.<jsf>DEFAULT</jsf>);
71   *
72   * 	<jc>// Construct JSON from JsonMap</jc>
73   * 	<jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:'bar'},{baz:[123,true]}"</js>);
74   * 	String <jv>json</jv> = <jv>map</jv>.toString();  <jc>// Produces "{foo:'bar'},{baz:[123,true]}"</jc>
75   * 	<jv>json</jv> = <jv>map</jv>.toString(JsonSerializer.<jsf>DEFAULT</jsf>);  <jc>// Equivalent</jc>
76   * 	<jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>map</jv>);  <jc>// Equivalent</jc>
77   *
78   * 	<jc>// Get a map entry as an Integer</jc>
79   * 	<jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:123}"</js>);
80   * 	Integer <jv>integer</jv> = <jv>map</jv>.getInt(<js>"foo"</js>);
81   * 	<jv>integer</jv> = <jv>map</jv>.get(Integer.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
82   *
83   * 	<jc>// Get a map entry as a Float</jc>
84   * 	<jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:123}"</js>);
85   * 	Float <jv>_float</jv> = <jv>map</jv>.getFloat(<js>"foo"</js>);
86   * 	<jv>_float</jv> = <jv>map</jv>.get(Float.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
87   *
88   * 	<jc>// Same as above, except converted to a String</jc>
89   * 	<jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:123}"</js>);
90   * 	String <jv>string</jv> = <jv>map</jv>.getString(<js>"foo"</js>); <jc>// Returns "123"</jc>
91   * 	<jv>string</jv> = <jv>map</jv>.get(String.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
92   *
93   * 	<jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc>
94   * 	<jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{person:{name:'John Smith',age:45}}"</js>);
95   * 	Person <jv>person</jv> = <jv>map</jv>.get(Person.<jk>class</jk>, <js>"person"</js>);
96   *
97   * 	<jc>// Add an inner map</jc>
98   * 	JsonMap <jv>map1</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{a:1}"</js>);
99   * 	JsonMap <jv>map2</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{b:2}"</js>).setInner(<jv>map1</jv>);
100  * 	<jk>int</jk> <jv>_int</jv> = <jv>map2</jv>.getInt(<js>"a"</js>);  <jc>// a == 1 </jc>
101  * </p>
102  *
103  * <h5 class='section'>Notes:</h5><ul>
104  * 	<li class='warn'>This class is not thread safe.
105  * </ul>
106  */
107 public class JsonMap extends LinkedHashMap<String,Object> {
108 	private static class UnmodifiableJsonMap extends JsonMap {
109 		private static final long serialVersionUID = 1L;
110 
111 		@SuppressWarnings("synthetic-access")
112 		UnmodifiableJsonMap(JsonMap contents) {
113 			if (nn(contents))
114 				contents.forEach(super::put);
115 		}
116 
117 		@Override
118 		public boolean isUnmodifiable() { return true; }
119 
120 		@Override
121 		public Object put(String key, Object val) {
122 			throw unsupportedOpReadOnly();
123 		}
124 
125 		@Override
126 		public Object remove(Object key) {
127 			throw unsupportedOpReadOnly();
128 		}
129 	}
130 
131 	private static final long serialVersionUID = 1L;
132 
133 	/**
134 	 * An empty read-only JsonMap.
135 	 *
136 	 * @serial exclude
137 	 */
138 	public static final JsonMap EMPTY_MAP = new JsonMap() {
139 
140 		private static final long serialVersionUID = 1L;
141 
142 		@Override /* Overridden from Map */
143 		public Set<Map.Entry<String,Object>> entrySet() {
144 			return Collections.<String,Object>emptyMap().entrySet();
145 		}
146 
147 		@Override /* Overridden from Map */
148 		public Set<String> keySet() {
149 			return Collections.<String,Object>emptyMap().keySet();
150 		}
151 
152 		@Override /* Overridden from Map */
153 		public Object put(String key, Object value) {
154 			throw unsupportedOpReadOnly();
155 		}
156 
157 		@Override /* Overridden from Map */
158 		public Object remove(Object key) {
159 			throw unsupportedOpReadOnly();
160 		}
161 
162 		@Override /* Overridden from Map */
163 		public Collection<Object> values() {
164 			return mape().values();
165 		}
166 	};
167 
168 	/**
169 	 * Construct an empty map.
170 	 *
171 	 * @return An empty map.
172 	 */
173 	public static JsonMap create() {
174 		return new JsonMap();
175 	}
176 
177 	/**
178 	 * Construct an empty map.
179 	 *
180 	 * @return An empty map.
181 	 */
182 	public static JsonMap filteredMap() {
183 		return create().filtered();
184 	}
185 
186 	/**
187 	 * Construct a map initialized with the specified key/value pairs.
188 	 *
189 	 * <p>
190 	 * Same as {@link #of(Object...)} but calls {@link #filtered()} on the created map.
191 	 *
192 	 * <h5 class='section'>Examples:</h5>
193 	 * <p class='bjava'>
194 	 * 	JsonMap <jv>map</jv> = <jk>new</jk> JsonMap(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>);
195 	 * </p>
196 	 *
197 	 * @param keyValuePairs A list of key/value pairs to add to this map.
198 	 * @return A new map, never <jk>null</jk>.
199 	 */
200 	public static JsonMap filteredMap(Object...keyValuePairs) {
201 		return new JsonMap(keyValuePairs).filtered();
202 	}
203 
204 	/**
205 	 * Construct a map initialized with the specified map.
206 	 *
207 	 * @param values
208 	 * 	The map to copy.
209 	 * 	<br>Can be <jk>null</jk>.
210 	 * 	<br>Keys will be converted to strings using {@link Object#toString()}.
211 	 * @return A new map or <jk>null</jk> if the map was <jk>null</jk>.
212 	 */
213 	public static JsonMap of(Map<?,?> values) {
214 		return values == null ? null : new JsonMap(values);
215 	}
216 
217 	/**
218 	 * Construct a map initialized with the specified key/value pairs.
219 	 *
220 	 * <h5 class='section'>Examples:</h5>
221 	 * <p class='bjava'>
222 	 * 	JsonMap <jv>map</jv> = <jk>new</jk> JsonMap(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>);
223 	 * </p>
224 	 *
225 	 * @param keyValuePairs A list of key/value pairs to add to this map.
226 	 * @return A new map, never <jk>null</jk>.
227 	 */
228 	public static JsonMap of(Object...keyValuePairs) {
229 		return new JsonMap(keyValuePairs);
230 	}
231 
232 	/**
233 	 * Construct a map initialized with the specified JSON string.
234 	 *
235 	 * @param json
236 	 * 	The JSON text to parse.
237 	 * 	<br>Can be normal or simplified JSON.
238 	 * @return A new map or <jk>null</jk> if the string was null.
239 	 * @throws ParseException Malformed input encountered.
240 	 */
241 	public static JsonMap ofJson(CharSequence json) throws ParseException {
242 		return json == null ? null : new JsonMap(json);
243 	}
244 
245 	/**
246 	 * Construct a map initialized with the specified reader containing JSON.
247 	 *
248 	 * @param json
249 	 * 	The reader containing JSON text to parse.
250 	 * 	<br>Can contain normal or simplified JSON.
251 	 * @return A new map or <jk>null</jk> if the input was <jk>null</jk>.
252 	 * @throws ParseException Malformed input encountered.
253 	 */
254 	public static JsonMap ofJson(Reader json) throws ParseException {
255 		return json == null ? null : new JsonMap(json);
256 	}
257 
258 	/**
259 	 * Construct a map initialized with the specified string.
260 	 *
261 	 * @param in
262 	 * 	The input being parsed.
263 	 * 	<br>Can be <jk>null</jk>.
264 	 * @param p
265 	 * 	The parser to use to parse the input.
266 	 * 	<br>If <jk>null</jk>, uses {@link JsonParser}.
267 	 * @return A new map or <jk>null</jk> if the input was <jk>null</jk>.
268 	 * @throws ParseException Malformed input encountered.
269 	 */
270 	public static JsonMap ofText(CharSequence in, Parser p) throws ParseException {
271 		return in == null ? null : new JsonMap(in, p);
272 	}
273 
274 	/**
275 	 * Construct a map initialized with the specified string.
276 	 *
277 	 * @param in
278 	 * 	The reader containing the input being parsed.
279 	 * 	<br>Can contain normal or simplified JSON.
280 	 * @param p
281 	 * 	The parser to use to parse the input.
282 	 * 	<br>If <jk>null</jk>, uses {@link JsonParser}.
283 	 * @return A new map or <jk>null</jk> if the input was <jk>null</jk>.
284 	 * @throws ParseException Malformed input encountered.
285 	 */
286 	public static JsonMap ofText(Reader in, Parser p) throws ParseException {
287 		return in == null ? null : new JsonMap(in);
288 	}
289 
290 	/*
291 	 * If c1 is a child of c2 or the same as c2, returns c1.
292 	 * Otherwise, returns c2.
293 	 */
294 	private static ClassMeta<?> getNarrowedClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) {
295 		if (c2 == null || c2.isParentOf(c1.inner()))
296 			return c1;
297 		return c2;
298 	}
299 
300 	private transient BeanSession session;
301 	private Map<String,Object> inner;
302 
303 	private transient ObjectRest objectRest;
304 
305 	private transient Predicate<Object> valueFilter = x -> true;
306 
307 	/**
308 	 * Construct an empty map.
309 	 */
310 	public JsonMap() {}
311 
312 	/**
313 	 * Construct an empty map with the specified bean context.
314 	 *
315 	 * @param session The bean session to use for creating beans.
316 	 */
317 	public JsonMap(BeanSession session) {
318 		this.session = session;
319 	}
320 
321 	/**
322 	 * Construct a map initialized with the specified JSON.
323 	 *
324 	 * @param json
325 	 * 	The JSON text to parse.
326 	 * 	<br>Can be normal or simplified JSON.
327 	 * @throws ParseException Malformed input encountered.
328 	 */
329 	public JsonMap(CharSequence json) throws ParseException {
330 		this(json, JsonParser.DEFAULT);
331 	}
332 
333 	/**
334 	 * Construct a map initialized with the specified string.
335 	 *
336 	 * @param in
337 	 * 	The input being parsed.
338 	 * 	<br>Can be <jk>null</jk>.
339 	 * @param p
340 	 * 	The parser to use to parse the input.
341 	 * 	<br>If <jk>null</jk>, uses {@link JsonParser}.
342 	 * @throws ParseException Malformed input encountered.
343 	 */
344 	public JsonMap(CharSequence in, Parser p) throws ParseException {
345 		this(p == null ? BeanContext.DEFAULT_SESSION : p.getBeanContext().getSession());
346 		if (p == null)
347 			p = JsonParser.DEFAULT;
348 		if (ne(in))
349 			p.parseIntoMap(in, this, bs().string(), bs().object());
350 	}
351 
352 	/**
353 	 * Construct a map initialized with the specified map.
354 	 *
355 	 * @param in
356 	 * 	The map to copy.
357 	 * 	<br>Can be <jk>null</jk>.
358 	 * 	<br>Keys will be converted to strings using {@link Object#toString()}.
359 	 */
360 	public JsonMap(Map<?,?> in) {
361 		this();
362 		if (nn(in))
363 			in.forEach((k, v) -> put(k.toString(), v));
364 	}
365 
366 	/**
367 	 * Construct a map initialized with the specified key/value pairs.
368 	 *
369 	 * <h5 class='section'>Examples:</h5>
370 	 * <p class='bjava'>
371 	 * 	JsonMap <jv>map</jv> = <jk>new</jk> JsonMap(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>);
372 	 * </p>
373 	 *
374 	 * @param keyValuePairs A list of key/value pairs to add to this map.
375 	 */
376 	public JsonMap(Object...keyValuePairs) {
377 		assertArg(keyValuePairs.length % 2 == 0, "Odd number of parameters passed into JsonMap(Object...)");
378 		for (var i = 0; i < keyValuePairs.length; i += 2)
379 			put(s(keyValuePairs[i]), keyValuePairs[i + 1]);
380 	}
381 
382 	/**
383 	 * Construct a map initialized with the specified reader containing JSON.
384 	 *
385 	 * @param json
386 	 * 	The reader containing JSON text to parse.
387 	 * 	<br>Can contain normal or simplified JSON.
388 	 * @throws ParseException Malformed input encountered.
389 	 */
390 	public JsonMap(Reader json) throws ParseException {
391 		parse(json, JsonParser.DEFAULT);
392 	}
393 
394 	/**
395 	 * Construct a map initialized with the specified string.
396 	 *
397 	 * @param in
398 	 * 	The reader containing the input being parsed.
399 	 * 	<br>Can contain normal or simplified JSON.
400 	 * @param p
401 	 * 	The parser to use to parse the input.
402 	 * 	<br>If <jk>null</jk>, uses {@link JsonParser}.
403 	 * @throws ParseException Malformed input encountered.
404 	 */
405 	public JsonMap(Reader in, Parser p) throws ParseException {
406 		this(p == null ? BeanContext.DEFAULT_SESSION : p.getBeanContext().getSession());
407 		parse(in, p);
408 	}
409 
410 	/**
411 	 * Appends all the entries in the specified map to this map.
412 	 *
413 	 * @param values The map to copy.  Can be <jk>null</jk>.
414 	 * @return This object.
415 	 */
416 	public JsonMap append(Map<String,Object> values) {
417 		if (nn(values))
418 			super.putAll(values);
419 		return this;
420 	}
421 
422 	/**
423 	 * Adds an entry to this map.
424 	 *
425 	 * @param key The key.
426 	 * @param value The value.
427 	 * @return This object.
428 	 */
429 	public JsonMap append(String key, Object value) {
430 		put(key, value);
431 		return this;
432 	}
433 
434 	/**
435 	 * Adds the first value that matches the specified predicate.
436 	 *
437 	 * @param <T> The value types.
438 	 * @param test The predicate to match against.
439 	 * @param key The key.
440 	 * @param values The values to test.
441 	 * @return This object.
442 	 */
443 	@SafeVarargs
444 	public final <T> JsonMap appendFirst(Predicate<T> test, String key, T...values) {
445 		for (var v : values)
446 			if (test(test, v))
447 				return append(key, v);
448 		return this;
449 	}
450 
451 	/**
452 	 * Add if flag is <jk>true</jk>.
453 	 *
454 	 * @param flag The flag to check.
455 	 * @param key The key.
456 	 * @param value The value.
457 	 * @return This object.
458 	 */
459 	public JsonMap appendIf(boolean flag, String key, Object value) {
460 		if (flag)
461 			append(key, value);
462 		return this;
463 	}
464 
465 	/**
466 	 * Add if predicate matches value.
467 	 *
468 	 * @param <T> The value type.
469 	 * @param test The predicate to match against.
470 	 * @param key The key.
471 	 * @param value The value.
472 	 * @return This object.
473 	 */
474 	public <T> JsonMap appendIf(Predicate<T> test, String key, T value) {
475 		return appendIf(test(test, value), key, value);
476 	}
477 
478 	/**
479 	 * Adds a value in this map if the entry does not exist or the current value is <jk>null</jk>.
480 	 *
481 	 * @param key The map key.
482 	 * @param value The value to set if the current value does not exist or is <jk>null</jk>.
483 	 * @return This object.
484 	 */
485 	public JsonMap appendIfAbsent(String key, Object value) {
486 		return appendIfAbsentIf(x -> true, key, value);
487 	}
488 
489 	/**
490 	 * Adds a value in this map if the entry does not exist or the current value is <jk>null</jk> and the value matches the specified predicate.
491 	 *
492 	 * @param <T> The value type.
493 	 * @param predicate The predicate to test the value with.
494 	 * @param key The map key.
495 	 * @param value The value to set if the current value does not exist or is <jk>null</jk>.
496 	 * @return This object.
497 	 */
498 	public <T> JsonMap appendIfAbsentIf(Predicate<T> predicate, String key, T value) {
499 		Object o = get(key);
500 		if (o == null && predicate.test(value))
501 			put(key, value);
502 		return this;
503 	}
504 
505 	/**
506 	 * A synonym for {@link #toString()}
507 	 *
508 	 * @return This object as a JSON string.
509 	 */
510 	public String asJson() {
511 		return toString();
512 	}
513 
514 	/**
515 	 * Serialize this object to Simplified JSON using {@link Json5Serializer#DEFAULT_READABLE}.
516 	 *
517 	 * @return This object serialized as a string.
518 	 */
519 	public String asReadableString() {
520 		if (Json5Serializer.DEFAULT_READABLE == null)
521 			return s(this);
522 		return Json5Serializer.DEFAULT_READABLE.toString(this);
523 	}
524 
525 	/**
526 	 * Serialize this object to Simplified JSON using {@link Json5Serializer#DEFAULT}.
527 	 *
528 	 * @return This object serialized as a string.
529 	 */
530 	public String asString() {
531 		if (Json5Serializer.DEFAULT == null)
532 			return s(this);
533 		return Json5Serializer.DEFAULT.toString(this);
534 	}
535 
536 	/**
537 	 * Serialize this object into a string using the specified serializer.
538 	 *
539 	 * @param serializer The serializer to use to convert this object to a string.
540 	 * @return This object serialized as a string.
541 	 */
542 	public String asString(WriterSerializer serializer) {
543 		return serializer.toString(this);
544 	}
545 
546 	/**
547 	 * Converts this map into an object of the specified type.
548 	 *
549 	 * <p>
550 	 * If this map contains a <js>"_type"</js> entry, it must be the same as or a subclass of the <c>type</c>.
551 	 *
552 	 * @param <T> The class type to convert this map object to.
553 	 * @param type The class type to convert this map object to.
554 	 * @return The new object.
555 	 * @throws ClassCastException
556 	 * 	If the <js>"_type"</js> entry is present and not assignable from <c>type</c>
557 	 */
558 	@SuppressWarnings("unchecked")
559 	public <T> T cast(Class<T> type) {
560 		BeanSession bs = bs();
561 		ClassMeta<?> c2 = bs.getClassMeta(type);
562 		String typePropertyName = bs.getBeanTypePropertyName(c2);
563 		ClassMeta<?> c1 = bs.getBeanRegistry().getClassMeta((String)get(typePropertyName));
564 		ClassMeta<?> c = c1 == null ? c2 : narrowClassMeta(c1, c2);
565 		if (c.isObject())
566 			return (T)this;
567 		return (T)cast2(c);
568 	}
569 
570 	/**
571 	 * Same as {@link #cast(Class)}, except allows you to specify a {@link ClassMeta} parameter.
572 	 *
573 	 * @param <T> The class type to convert this map object to.
574 	 * @param cm The class type to convert this map object to.
575 	 * @return The new object.
576 	 * @throws ClassCastException
577 	 * 	If the <js>"_type"</js> entry is present and not assignable from <c>type</c>
578 	 */
579 	@SuppressWarnings({ "unchecked" })
580 	public <T> T cast(ClassMeta<T> cm) {
581 		BeanSession bs = bs();
582 		var c1 = bs.getBeanRegistry().getClassMeta((String)get(bs.getBeanTypePropertyName(cm)));
583 		var c = narrowClassMeta(c1, cm);
584 		return (T)cast2(c);
585 	}
586 
587 	@Override /* Overridden from Map */
588 	public boolean containsKey(Object key) {
589 		if (super.containsKey(key))
590 			return true;
591 		if (nn(inner))
592 			return inner.containsKey(key);
593 		return false;
594 	}
595 
596 	/**
597 	 * Returns <jk>true</jk> if the map contains the specified entry and the value is not null nor an empty string.
598 	 *
599 	 * <p>
600 	 * Always returns <jk>false</jk> if the value is not a {@link CharSequence}.
601 	 *
602 	 * @param key The key.
603 	 * @return <jk>true</jk> if the map contains the specified entry and the value is not null nor an empty string.
604 	 */
605 	public boolean containsKeyNotEmpty(String key) {
606 		Object val = get(key);
607 		if (val == null)
608 			return false;
609 		if (val instanceof CharSequence val2)
610 			return isNotBlank(val2);
611 		return false;
612 	}
613 
614 	/**
615 	 * Returns <jk>true</jk> if this map contains the specified key, ignoring the inner map if it exists.
616 	 *
617 	 * @param key The key to look up.
618 	 * @return <jk>true</jk> if this map contains the specified key.
619 	 */
620 	public boolean containsOuterKey(Object key) {
621 		return super.containsKey(key);
622 	}
623 
624 	/**
625 	 * Similar to {@link #remove(Object) remove(Object)}, but the key is a slash-delimited path used to traverse entries
626 	 * in this POJO.
627 	 *
628 	 * <p>
629 	 * For example, the following code is equivalent:
630 	 * </p>
631 	 * <p class='bjava'>
632 	 * 	JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>);
633 	 *
634 	 * 	<jc>// Long way</jc>
635 	 * 	<jv>map</jv>.getMap(<js>"foo"</js>).getList(<js>"bar"</js>).getMap(0).remove(<js>"baz"</js>);
636 	 *
637 	 * 	<jc>// Using this method</jc>
638 	 * 	<jv>map</jv>.deleteAt(<js>"foo/bar/0/baz"</js>);
639 	 * </p>
640 	 *
641 	 * <p>
642 	 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various
643 	 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays).
644 	 *
645 	 * @param path The path to the entry.
646 	 * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
647 	 */
648 	public Object deleteAt(String path) {
649 		return getObjectRest().delete(path);
650 	}
651 
652 	@Override /* Overridden from Map */
653 	public Set<Map.Entry<String,Object>> entrySet() {
654 		if (inner == null)
655 			return super.entrySet();
656 
657 		final Set<String> keySet = keySet();
658 		final Iterator<String> keys = keySet.iterator();
659 
660 		return new AbstractSet<>() {
661 
662 			@Override /* Overridden from Iterable */
663 			public Iterator<Map.Entry<String,Object>> iterator() {
664 
665 				return new Iterator<>() {
666 
667 					@Override /* Overridden from Iterator */
668 					public boolean hasNext() {
669 						return keys.hasNext();
670 					}
671 
672 					@Override /* Overridden from Iterator */
673 					public Map.Entry<String,Object> next() {
674 						return new Map.Entry<>() {
675 							String key = keys.next();
676 
677 							@Override /* Overridden from Map.Entry */
678 							public String getKey() { return key; }
679 
680 							@Override /* Overridden from Map.Entry */
681 							public Object getValue() { return get(key); }
682 
683 							@Override /* Overridden from Map.Entry */
684 							public Object setValue(Object object) {
685 								return put(key, object);
686 							}
687 						};
688 					}
689 
690 					@Override /* Overridden from Iterator */
691 					public void remove() {
692 						throw unsupportedOpReadOnly();
693 					}
694 				};
695 			}
696 
697 			@Override /* Overridden from Set */
698 			public int size() {
699 				return keySet.size();
700 			}
701 		};
702 	}
703 
704 	/**
705 	 * Returns a copy of this <c>JsonMap</c> without the specified keys.
706 	 *
707 	 * @param keys The keys of the entries not to copy.
708 	 * @return A new map without the keys and values from this map.
709 	 */
710 	public JsonMap exclude(String...keys) {
711 		var m2 = new JsonMap();
712 		this.forEach((k, v) -> {
713 			var exclude = false;
714 			for (var kk : keys)
715 				if (kk.equals(k))
716 					exclude = true;
717 			if (! exclude)
718 				m2.put(k, v);
719 		});
720 		return m2;
721 	}
722 
723 	/**
724 	 * Enables filtering based on default values.
725 	 *
726 	 * <p>
727 	 * Any of the following types will be ignored when set as values in this map:
728 	 * <ul>
729 	 * 	<li><jk>null</jk>
730 	 * 	<li><jk>false</jk>
731 	 * 	<li><c>-1</c> (any Number type)
732 	 * 	<li>Empty arrays/collections/maps.
733 	 * </ul>
734 	 * @return This object.
735 	 */
736 	public JsonMap filtered() {
737 		// @formatter:off
738 		return filtered(x -> ! (
739 			x == null
740 			|| (x instanceof Boolean x2 && x2.equals(false))
741 			|| (x instanceof Number x3 && x3.intValue() == -1)
742 		|| (isArray(x) && Array.getLength(x) == 0)
743 		|| (x instanceof Map x2 && x2.isEmpty())
744 		|| (x instanceof Collection x3 && x3.isEmpty())
745 		));
746 		// @formatter:on
747 	}
748 
749 	/**
750 	 * Enables filtering based on a predicate test.
751 	 *
752 	 * <p>
753 	 * If the predicate evaluates to <jk>false</jk> on values added to this map, the entry will be skipped.
754 	 *
755 	 * @param value The value tester predicate.
756 	 * @return This object.
757 	 */
758 	public JsonMap filtered(Predicate<Object> value) {
759 		valueFilter = value;
760 		return this;
761 	}
762 
763 	/**
764 	 * Returns the value for the first key in the list that has an entry in this map.
765 	 *
766 	 * <p>
767 	 * Casts or converts the value to the specified class type.
768 	 *
769 	 * <p>
770 	 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions.
771 	 *
772 	 * @param type The class type to convert the value to.
773 	 * @param <T> The class type to convert the value to.
774 	 * @param keys The keys to look up in order.
775 	 * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map.
776 	 */
777 	public <T> T find(Class<T> type, String...keys) {
778 		for (var key : keys)
779 			if (containsKey(key))
780 				return get(key, type);
781 		return null;
782 	}
783 
784 	/**
785 	 * Returns the value for the first key in the list that has an entry in this map.
786 	 *
787 	 * @param keys The keys to look up in order.
788 	 * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map.
789 	 */
790 	public Object find(String...keys) {
791 		for (var key : keys)
792 			if (containsKey(key))
793 				return get(key);
794 		return null;
795 	}
796 
797 	/**
798 	 * Returns the first entry that exists converted to a {@link Boolean}.
799 	 *
800 	 * <p>
801 	 * Shortcut for <code>find(Boolean.<jk>class</jk>, keys)</code>.
802 	 *
803 	 * @param keys The list of keys to look for.
804 	 * @return
805 	 * 	The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
806 	 * 	contains no mapping for any of the keys.
807 	 * @throws InvalidDataConversionException If value cannot be converted.
808 	 */
809 	public Boolean findBoolean(String...keys) {
810 		return find(Boolean.class, keys);
811 	}
812 
813 	/**
814 	 * Returns the first entry that exists converted to an {@link Integer}.
815 	 *
816 	 * <p>
817 	 * Shortcut for <code>find(Integer.<jk>class</jk>, keys)</code>.
818 	 *
819 	 * @param keys The list of keys to look for.
820 	 * @return
821 	 * 	The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
822 	 * 	contains no mapping for any of the keys.
823 	 * @throws InvalidDataConversionException If value cannot be converted.
824 	 */
825 	public Integer findInt(String...keys) {
826 		return find(Integer.class, keys);
827 	}
828 
829 	/**
830 	 * Searches for the specified key in this map ignoring case.
831 	 *
832 	 * @param key
833 	 * 	The key to search for.
834 	 * 	For performance reasons, it's preferable that the key be all lowercase.
835 	 * @return The key, or <jk>null</jk> if map does not contain this key.
836 	 */
837 	public String findKeyIgnoreCase(String key) {
838 		for (var k : keySet())
839 			if (eqic(key, k))
840 				return k;
841 		return null;
842 	}
843 
844 	/**
845 	 * Returns the first entry that exists converted to a {@link JsonList}.
846 	 *
847 	 * <p>
848 	 * Shortcut for <code>find(JsonList.<jk>class</jk>, keys)</code>.
849 	 *
850 	 * @param keys The list of keys to look for.
851 	 * @return
852 	 * 	The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
853 	 * 	contains no mapping for any of the keys.
854 	 * @throws InvalidDataConversionException If value cannot be converted.
855 	 */
856 	public JsonList findList(String...keys) {
857 		return find(JsonList.class, keys);
858 	}
859 
860 	/**
861 	 * Returns the first entry that exists converted to a {@link Long}.
862 	 *
863 	 * <p>
864 	 * Shortcut for <code>find(Long.<jk>class</jk>, keys)</code>.
865 	 *
866 	 * @param keys The list of keys to look for.
867 	 * @return
868 	 * 	The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
869 	 * 	contains no mapping for any of the keys.
870 	 * @throws InvalidDataConversionException If value cannot be converted.
871 	 */
872 	public Long findLong(String...keys) {
873 		return find(Long.class, keys);
874 	}
875 
876 	/**
877 	 * Returns the first entry that exists converted to a {@link JsonMap}.
878 	 *
879 	 * <p>
880 	 * Shortcut for <code>find(JsonMap.<jk>class</jk>, keys)</code>.
881 	 *
882 	 * @param keys The list of keys to look for.
883 	 * @return
884 	 * 	The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
885 	 * 	contains no mapping for any of the keys.
886 	 * @throws InvalidDataConversionException If value cannot be converted.
887 	 */
888 	public JsonMap findMap(String...keys) {
889 		return find(JsonMap.class, keys);
890 	}
891 
892 	/**
893 	 * Returns the first entry that exists converted to a {@link String}.
894 	 *
895 	 * <p>
896 	 * Shortcut for <code>find(String.<jk>class</jk>, keys)</code>.
897 	 *
898 	 * @param keys The list of keys to look for.
899 	 * @return
900 	 * 	The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
901 	 * 	contains no mapping for any of the keys.
902 	 */
903 	public String findString(String...keys) {
904 		return find(String.class, keys);
905 	}
906 
907 	@Override /* Overridden from Map */
908 	public Object get(Object key) {
909 		Object o = super.get(key);
910 		if (o == null && nn(inner))
911 			o = inner.get(key);
912 		return o;
913 	}
914 
915 	/**
916 	 * Same as {@link Map#get(Object) get()}, but casts or converts the value to the specified class type.
917 	 *
918 	 * <p>
919 	 * This is the preferred get method for simple types.
920 	 *
921 	 * <h5 class='section'>Examples:</h5>
922 	 * <p class='bjava'>
923 	 * 	JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>);
924 	 *
925 	 * 	<jc>// Value converted to a string.</jc>
926 	 * 	String <jv>string</jv> = <jv>map</jv>.get(<js>"key1"</js>, String.<jk>class</jk>);
927 	 *
928 	 * 	<jc>// Value converted to a bean.</jc>
929 	 * 	MyBean <jv>bean</jv> = <jv>map</jv>.get(<js>"key2"</js>, MyBean.<jk>class</jk>);
930 	 *
931 	 * 	<jc>// Value converted to a bean array.</jc>
932 	 * 	MyBean[] <jv>beanArray</jv> = <jv>map</jv>.get(<js>"key3"</js>, MyBean[].<jk>class</jk>);
933 	 *
934 	 * 	<jc>// Value converted to a linked-list of objects.</jc>
935 	 * 	List <jv>list</jv> = <jv>map</jv>.get(<js>"key4"</js>, LinkedList.<jk>class</jk>);
936 	 *
937 	 * 	<jc>// Value converted to a map of object keys/values.</jc>
938 	 * 	Map <jv>map2</jv> = <jv>map</jv>.get(<js>"key5"</js>, TreeMap.<jk>class</jk>);
939 	 * </p>
940 	 *
941 	 * <p>
942 	 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions.
943 	 *
944 	 * @param key The key.
945 	 * @param <T> The class type returned.
946 	 * @param type The class type returned.
947 	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
948 	 */
949 	public <T> T get(String key, Class<T> type) {
950 		return getWithDefault(key, (T)null, type);
951 	}
952 
953 	/**
954 	 * Same as {@link #get(String,Class)}, but allows for complex data types consisting of collections or maps.
955 	 *
956 	 * <p>
957 	 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps).
958 	 *
959 	 * <h5 class='section'>Examples:</h5>
960 	 * <p class='bjava'>
961 	 * 	JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>);
962 	 *
963 	 * 	<jc>// Value converted to a linked-list of strings.</jc>
964 	 * 	List&lt;String&gt; <jv>list1</jv> = <jv>map</jv>.get(<js>"key1"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
965 	 *
966 	 * 	<jc>// Value converted to a linked-list of beans.</jc>
967 	 * 	List&lt;MyBean&gt; <jv>list2</jv> = <jv>map</jv>.get(<js>"key2"</js>, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>);
968 	 *
969 	 * 	<jc>// Value converted to a linked-list of linked-lists of strings.</jc>
970 	 * 	List&lt;List&lt;String&gt;&gt; <jv>list3</jv> = <jv>map</jv>.get(<js>"key3"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
971 	 *
972 	 * 	<jc>// Value converted to a map of string keys/values.</jc>
973 	 * 	Map&lt;String,String&gt; <jv>map1</jv> = <jv>map</jv>.get(<js>"key4"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>);
974 	 *
975 	 * 	<jc>// Value converted to a map containing string keys and values of lists containing beans.</jc>
976 	 * 	Map&lt;String,List&lt;MyBean&gt;&gt; <jv>map2</jv> = <jv>map</jv>.get(<js>"key5"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>);
977 	 * </p>
978 	 *
979 	 * <p>
980 	 * <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type.
981 	 *
982 	 * <p>
983 	 * <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value types.
984 	 *
985 	 * <p>
986 	 * The array can be arbitrarily long to indicate arbitrarily complex data structures.
987 	 *
988 	 * <p>
989 	 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions.
990 	 *
991 	 * <h5 class='section'>Notes:</h5><ul>
992 	 * 	<li class='note'>
993 	 * 		Use the {@link #get(String, Class)} method instead if you don't need a parameterized map/collection.
994 	 * </ul>
995 	 *
996 	 * @param key The key.
997 	 * @param <T> The class type returned.
998 	 * @param type The class type returned.
999 	 * @param args The class type parameters.
1000 	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
1001 	 */
1002 	public <T> T get(String key, Type type, Type...args) {
1003 		return getWithDefault(key, null, type, args);
1004 	}
1005 
1006 	/**
1007 	 * Same as {@link #get(String,Class) get(String,Class)}, but the key is a slash-delimited path used to traverse
1008 	 * entries in this POJO.
1009 	 *
1010 	 * <p>
1011 	 * For example, the following code is equivalent:
1012 	 * </p>
1013 	 * <p class='bjava'>
1014 	 * 	JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>);
1015 	 *
1016 	 * 	<jc>// Long way</jc>
1017 	 * 	<jk>long</jk> <jv>_long</jv> = <jv>map</jv>.getMap(<js>"foo"</js>).getList(<js>"bar"</js>).getMap(<js>"0"</js>).getLong(<js>"baz"</js>);
1018 	 *
1019 	 * 	<jc>// Using this method</jc>
1020 	 * 	<jk>long</jk> <jv>_long</jv> = <jv>map</jv>.getAt(<js>"foo/bar/0/baz"</js>, <jk>long</jk>.<jk>class</jk>);
1021 	 * </p>
1022 	 *
1023 	 * <p>
1024 	 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various
1025 	 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays).
1026 	 *
1027 	 * @param path The path to the entry.
1028 	 * @param type The class type.
1029 	 *
1030 	 * @param <T> The class type.
1031 	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
1032 	 */
1033 	public <T> T getAt(String path, Class<T> type) {
1034 		return getObjectRest().get(path, type);
1035 	}
1036 
1037 	/**
1038 	 * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections.
1039 	 *
1040 	 * <p>
1041 	 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various
1042 	 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays).
1043 	 *
1044 	 * @param path The path to the entry.
1045 	 * @param type The class type.
1046 	 * @param args The class parameter types.
1047 	 *
1048 	 * @param <T> The class type.
1049 	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
1050 	 */
1051 	public <T> T getAt(String path, Type type, Type...args) {
1052 		return getObjectRest().get(path, type, args);
1053 	}
1054 
1055 	/**
1056 	 * Returns the {@link BeanSession} currently associated with this map.
1057 	 *
1058 	 * @return The {@link BeanSession} currently associated with this map.
1059 	 */
1060 	public BeanSession getBeanSession() { return session; }
1061 
1062 	/**
1063 	 * Returns the specified entry value converted to a {@link Boolean}.
1064 	 *
1065 	 * <p>
1066 	 * Shortcut for <code>get(key, Boolean.<jk>class</jk>)</code>.
1067 	 *
1068 	 * @param key The key.
1069 	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
1070 	 * @throws InvalidDataConversionException If value cannot be converted.
1071 	 */
1072 	public Boolean getBoolean(String key) {
1073 		return get(key, Boolean.class);
1074 	}
1075 
1076 	/**
1077 	 * Returns the specified entry value converted to a {@link Boolean}.
1078 	 *
1079 	 * <p>
1080 	 * Shortcut for <code>getWithDefault(key, defVal, Boolean.<jk>class</jk>)</code>.
1081 	 *
1082 	 * @param key The key.
1083 	 * @param defVal The default value if the map doesn't contain the specified mapping.
1084 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1085 	 * @throws InvalidDataConversionException If value cannot be converted.
1086 	 */
1087 	public Boolean getBoolean(String key, Boolean defVal) {
1088 		return getWithDefault(key, defVal, Boolean.class);
1089 	}
1090 
1091 	/**
1092 	 * Returns the class type of the object at the specified index.
1093 	 *
1094 	 * @param key The key into this map.
1095 	 * @return
1096 	 * 	The data type of the object at the specified key, or <jk>null</jk> if the value is null or does not exist.
1097 	 */
1098 	public ClassMeta<?> getClassMeta(String key) {
1099 		return bs().getClassMetaForObject(get(key));
1100 	}
1101 
1102 	/**
1103 	 * Returns the first key in the map.
1104 	 *
1105 	 * @return The first key in the map, or <jk>null</jk> if the map is empty.
1106 	 */
1107 	public String getFirstKey() { return isEmpty() ? null : keySet().iterator().next(); }
1108 
1109 	/**
1110 	 * Returns the specified entry value converted to an {@link Integer}.
1111 	 *
1112 	 * <p>
1113 	 * Shortcut for <code>get(key, Integer.<jk>class</jk>)</code>.
1114 	 *
1115 	 * @param key The key.
1116 	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
1117 	 * @throws InvalidDataConversionException If value cannot be converted.
1118 	 */
1119 	public Integer getInt(String key) {
1120 		return get(key, Integer.class);
1121 	}
1122 
1123 	/**
1124 	 * Returns the specified entry value converted to an {@link Integer}.
1125 	 *
1126 	 * <p>
1127 	 * Shortcut for <code>getWithDefault(key, defVal, Integer.<jk>class</jk>)</code>.
1128 	 *
1129 	 * @param key The key.
1130 	 * @param defVal The default value if the map doesn't contain the specified mapping.
1131 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1132 	 * @throws InvalidDataConversionException If value cannot be converted.
1133 	 */
1134 	public Integer getInt(String key, Integer defVal) {
1135 		return getWithDefault(key, defVal, Integer.class);
1136 	}
1137 
1138 	/**
1139 	 * Returns the specified entry value converted to a {@link JsonList}.
1140 	 *
1141 	 * <p>
1142 	 * Shortcut for <code>get(key, JsonList.<jk>class</jk>)</code>.
1143 	 *
1144 	 * @param key The key.
1145 	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
1146 	 * @throws InvalidDataConversionException If value cannot be converted.
1147 	 */
1148 	public JsonList getList(String key) {
1149 		return get(key, JsonList.class);
1150 	}
1151 
1152 	/**
1153 	 * Same as {@link #getList(String)} but creates a new empty {@link JsonList} if it doesn't already exist.
1154 	 *
1155 	 * @param key The key.
1156 	 * @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link JsonList}.
1157 	 * @return The converted value, or an empty value if the map contains no mapping for this key.
1158 	 * @throws InvalidDataConversionException If value cannot be converted.
1159 	 */
1160 	public JsonList getList(String key, boolean createIfNotExists) {
1161 		JsonList m = getWithDefault(key, null, JsonList.class);
1162 		if (m == null && createIfNotExists) {
1163 			m = new JsonList();
1164 			put(key, m);
1165 		}
1166 		return m;
1167 	}
1168 
1169 	/**
1170 	 * Same as {@link #getList(String, JsonList)} except converts the elements to the specified types.
1171 	 *
1172 	 * @param <E> The element type.
1173 	 * @param key The key.
1174 	 * @param elementType The element type class.
1175 	 * @param def The default value if the map doesn't contain the specified mapping.
1176 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1177 	 * @throws InvalidDataConversionException If value cannot be converted.
1178 	 */
1179 	public <E> List<E> getList(String key, Class<E> elementType, List<E> def) {
1180 		Object o = get(key);
1181 		if (o == null)
1182 			return def;
1183 		return bs().convertToType(o, List.class, elementType);
1184 	}
1185 
1186 	/**
1187 	 * Returns the specified entry value converted to a {@link JsonList}.
1188 	 *
1189 	 * <p>
1190 	 * Shortcut for <code>getWithDefault(key, defVal, JsonList.<jk>class</jk>)</code>.
1191 	 *
1192 	 * @param key The key.
1193 	 * @param defVal The default value if the map doesn't contain the specified mapping.
1194 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1195 	 * @throws InvalidDataConversionException If value cannot be converted.
1196 	 */
1197 	public JsonList getList(String key, JsonList defVal) {
1198 		return getWithDefault(key, defVal, JsonList.class);
1199 	}
1200 
1201 	/**
1202 	 * Returns the specified entry value converted to a {@link Long}.
1203 	 *
1204 	 * <p>
1205 	 * Shortcut for <code>get(key, Long.<jk>class</jk>)</code>.
1206 	 *
1207 	 * @param key The key.
1208 	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
1209 	 * @throws InvalidDataConversionException If value cannot be converted.
1210 	 */
1211 	public Long getLong(String key) {
1212 		return get(key, Long.class);
1213 	}
1214 
1215 	/**
1216 	 * Returns the specified entry value converted to a {@link Long}.
1217 	 *
1218 	 * <p>
1219 	 * Shortcut for <code>getWithDefault(key, defVal, Long.<jk>class</jk>)</code>.
1220 	 *
1221 	 * @param key The key.
1222 	 * @param defVal The default value if the map doesn't contain the specified mapping.
1223 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1224 	 * @throws InvalidDataConversionException If value cannot be converted.
1225 	 */
1226 	public Long getLong(String key, Long defVal) {
1227 		return getWithDefault(key, defVal, Long.class);
1228 	}
1229 
1230 	/**
1231 	 * Returns the specified entry value converted to a {@link Map}.
1232 	 *
1233 	 * <p>
1234 	 * Shortcut for <code>get(key, JsonMap.<jk>class</jk>)</code>.
1235 	 *
1236 	 * @param key The key.
1237 	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
1238 	 * @throws InvalidDataConversionException If value cannot be converted.
1239 	 */
1240 	public JsonMap getMap(String key) {
1241 		return get(key, JsonMap.class);
1242 	}
1243 
1244 	/**
1245 	 * Same as {@link #getMap(String)} but creates a new empty {@link JsonMap} if it doesn't already exist.
1246 	 *
1247 	 * @param key The key.
1248 	 * @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link JsonMap}.
1249 	 * @return The converted value, or an empty value if the map contains no mapping for this key.
1250 	 * @throws InvalidDataConversionException If value cannot be converted.
1251 	 */
1252 	public JsonMap getMap(String key, boolean createIfNotExists) {
1253 		var m = getWithDefault(key, null, JsonMap.class);
1254 		if (m == null && createIfNotExists) {
1255 			m = new JsonMap();
1256 			put(key, m);
1257 		}
1258 		return m;
1259 	}
1260 
1261 	/**
1262 	 * Same as {@link #getMap(String, JsonMap)} except converts the keys and values to the specified types.
1263 	 *
1264 	 * @param <K> The key type.
1265 	 * @param <V> The value type.
1266 	 * @param key The key.
1267 	 * @param keyType The key type class.
1268 	 * @param valType The value type class.
1269 	 * @param def The default value if the map doesn't contain the specified mapping.
1270 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1271 	 * @throws InvalidDataConversionException If value cannot be converted.
1272 	 */
1273 	public <K,V> Map<K,V> getMap(String key, Class<K> keyType, Class<V> valType, Map<K,V> def) {
1274 		Object o = get(key);
1275 		if (o == null)
1276 			return def;
1277 		return bs().convertToType(o, Map.class, keyType, valType);
1278 	}
1279 
1280 	/**
1281 	 * Returns the specified entry value converted to a {@link JsonMap}.
1282 	 *
1283 	 * <p>
1284 	 * Shortcut for <code>getWithDefault(key, defVal, JsonMap.<jk>class</jk>)</code>.
1285 	 *
1286 	 * @param key The key.
1287 	 * @param defVal The default value if the map doesn't contain the specified mapping.
1288 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1289 	 * @throws InvalidDataConversionException If value cannot be converted.
1290 	 */
1291 	public JsonMap getMap(String key, JsonMap defVal) {
1292 		return getWithDefault(key, defVal, JsonMap.class);
1293 	}
1294 
1295 	/**
1296 	 * Returns the specified entry value converted to a {@link String}.
1297 	 *
1298 	 * <p>
1299 	 * Shortcut for <code>get(key, String.<jk>class</jk>)</code>.
1300 	 *
1301 	 * @param key The key.
1302 	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
1303 	 */
1304 	public String getString(String key) {
1305 		return get(key, String.class);
1306 	}
1307 
1308 	/**
1309 	 * Returns the specified entry value converted to a {@link String}.
1310 	 *
1311 	 * <p>
1312 	 * Shortcut for <code>getWithDefault(key, defVal, String.<jk>class</jk>)</code>.
1313 	 *
1314 	 * @param key The key.
1315 	 * @param defVal The default value if the map doesn't contain the specified mapping.
1316 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1317 	 */
1318 	public String getString(String key, String defVal) {
1319 		return getWithDefault(key, defVal, String.class);
1320 	}
1321 
1322 	/**
1323 	 * Returns the specified entry value converted to a {@link String}.
1324 	 *
1325 	 * <p>
1326 	 * Shortcut for <code>get(key, String[].<jk>class</jk>)</code>.
1327 	 *
1328 	 * @param key The key.
1329 	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
1330 	 */
1331 	public String[] getStringArray(String key) {
1332 		return getStringArray(key, null);
1333 	}
1334 
1335 	/**
1336 	 * Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found.
1337 	 *
1338 	 * @param key The map key.
1339 	 * @param def The default value if value is not found.
1340 	 * @return The value converted to a string array.
1341 	 */
1342 	public String[] getStringArray(String key, String[] def) {
1343 		Object s = get(key, Object.class);
1344 		if (s == null)
1345 			return def;
1346 		String[] r = null;
1347 		if (s instanceof Collection<?> s2)
1348 			r = toStringArray(s2);
1349 		else if (s instanceof String[] s2)
1350 			r = s2;
1351 		else if (s instanceof Object[] s3)
1352 			r = toStringArray(l(s3));
1353 		else
1354 			r = StringUtils.splita(s(s));
1355 		return (r.length == 0 ? def : r);
1356 	}
1357 
1358 	/**
1359 	 * Same as {@link Map#get(Object) get()}, but converts the raw value to the specified class type using the specified
1360 	 * POJO swap.
1361 	 *
1362 	 * @param key The key.
1363 	 * @param objectSwap The swap class used to convert the raw type to a transformed type.
1364 	 * @param <T> The transformed class type.
1365 	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
1366 	 * @throws ParseException Malformed input encountered.
1367 	 */
1368 	@SuppressWarnings({ "rawtypes", "unchecked" })
1369 	public <T> T getSwapped(String key, ObjectSwap<T,?> objectSwap) throws ParseException {
1370 		try {
1371 			Object o = super.get(key);
1372 			if (o == null)
1373 				return null;
1374 			ObjectSwap swap = objectSwap;
1375 			return (T)swap.unswap(bs(), o, null);
1376 		} catch (ParseException e) {
1377 			throw e;
1378 		} catch (Exception e) {
1379 			throw new ParseException(e);
1380 		}
1381 	}
1382 
1383 	/**
1384 	 * Same as {@link Map#get(Object) get()}, but returns the default value if the key could not be found.
1385 	 *
1386 	 * @param key The key.
1387 	 * @param def The default value if the entry doesn't exist.
1388 	 * @return The value, or the default value if the entry doesn't exist.
1389 	 */
1390 	public Object getWithDefault(String key, Object def) {
1391 		Object o = get(key);
1392 		return (o == null ? def : o);
1393 	}
1394 
1395 	/**
1396 	 * Same as {@link #get(String,Class)} but returns a default value if the value does not exist.
1397 	 *
1398 	 * @param key The key.
1399 	 * @param def The default value.  Can be <jk>null</jk>.
1400 	 * @param <T> The class type returned.
1401 	 * @param type The class type returned.
1402 	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
1403 	 */
1404 	public <T> T getWithDefault(String key, T def, Class<T> type) {
1405 		return getWithDefault(key, def, type, new Type[0]);
1406 	}
1407 
1408 	/**
1409 	 * Same as {@link #get(String,Type,Type...)} but returns a default value if the value does not exist.
1410 	 *
1411 	 * @param key The key.
1412 	 * @param def The default value.  Can be <jk>null</jk>.
1413 	 * @param <T> The class type returned.
1414 	 * @param type The class type returned.
1415 	 * @param args The class type parameters.
1416 	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
1417 	 */
1418 	public <T> T getWithDefault(String key, T def, Type type, Type...args) {
1419 		Object o = get(key);
1420 		if (o == null)
1421 			return def;
1422 		T t = bs().convertToType(o, type, args);
1423 		return t == null ? def : t;
1424 	}
1425 
1426 	/**
1427 	 * Returns a copy of this <c>JsonMap</c> with only the specified keys.
1428 	 *
1429 	 * @param keys The keys of the entries to copy.
1430 	 * @return A new map with just the keys and values from this map.
1431 	 */
1432 	public JsonMap include(String...keys) {
1433 		var m2 = new JsonMap();
1434 		this.forEach((k, v) -> {
1435 			for (var kk : keys)
1436 				if (kk.equals(k))
1437 					m2.put(kk, v);
1438 		});
1439 		return m2;
1440 	}
1441 
1442 	/**
1443 	 * Set an inner map in this map to allow for chained get calls.
1444 	 *
1445 	 * <p>
1446 	 * If {@link #get(Object)} returns <jk>null</jk>, then {@link #get(Object)} will be called on the inner map.
1447 	 *
1448 	 * <p>
1449 	 * In addition to providing the ability to chain maps, this method also provides the ability to wrap an existing map
1450 	 * inside another map so that you can add entries to the outer map without affecting the values on the inner map.
1451 	 *
1452 	 * <p class='bjava'>
1453 	 * 	JsonMap <jv>map1</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:1}"</js>);
1454 	 * 	JsonMap <jv>map2</jv> = JsonMap.<jsm>of</jsm>().setInner(<jv>map1</jv>);
1455 	 * 	<jv>map2</jv>.put(<js>"foo"</js>, 2);                      <jc>// Overwrite the entry</jc>
1456 	 * 	<jk>int</jk> <jv>foo1</jv> = <jv>map1</jv>.getInt(<js>"foo"</js>);           <jc>// foo1 == 1 </jc>
1457 	 * 	<jk>int</jk> <jv>foo2</jv> = <jv>map2</jv>.getInt(<js>"foo"</js>);           <jc>// foo2 == 2 </jc>
1458 	 * </p>
1459 	 *
1460 	 * @param inner
1461 	 * 	The inner map.
1462 	 * 	Can be <jk>null</jk> to remove the inner map from an existing map.
1463 	 * @return This object.
1464 	 */
1465 	public JsonMap inner(Map<String,Object> inner) {
1466 		this.inner = inner;
1467 		return this;
1468 	}
1469 
1470 	/**
1471 	 * Returns <jk>true</jk> if this map is unmodifiable.
1472 	 *
1473 	 * @return <jk>true</jk> if this map is unmodifiable.
1474 	 */
1475 	public boolean isUnmodifiable() { return false; }
1476 
1477 	/**
1478 	 * The opposite of {@link #removeAll(String...)}.
1479 	 *
1480 	 * <p>
1481 	 * Discards all keys from this map that aren't in the specified list.
1482 	 *
1483 	 * @param keys The keys to keep.
1484 	 * @return This map.
1485 	 */
1486 	public JsonMap keepAll(String...keys) {
1487 		for (var i = keySet().iterator(); i.hasNext();) {
1488 			var remove = true;
1489 			var key = i.next();
1490 			for (var k : keys) {
1491 				if (k.equals(key)) {
1492 					remove = false;
1493 					break;
1494 				}
1495 			}
1496 			if (remove)
1497 				i.remove();
1498 		}
1499 		return this;
1500 	}
1501 
1502 	@Override /* Overridden from Map */
1503 	public Set<String> keySet() {
1504 		if (inner == null)
1505 			return super.keySet();
1506 		LinkedHashSet<String> s = set();
1507 		s.addAll(inner.keySet());
1508 		s.addAll(super.keySet());
1509 		return s;
1510 	}
1511 
1512 	/**
1513 	 * Returns a modifiable copy of this map if it's unmodifiable.
1514 	 *
1515 	 * @return A modifiable copy of this map if it's unmodifiable, or this map if it is already modifiable.
1516 	 */
1517 	public JsonMap modifiable() {
1518 		if (isUnmodifiable())
1519 			return new JsonMap(this);
1520 		return this;
1521 	}
1522 
1523 	/**
1524 	 * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays.
1525 	 *
1526 	 * <p>
1527 	 * For example, the following code is equivalent:
1528 	 * </p>
1529 	 * <p class='bjava'>
1530 	 * 	JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>);
1531 	 *
1532 	 * 	<jc>// Long way</jc>
1533 	 * 	<jv>map</jv>.getMap(<js>"foo"</js>).getList(<js>"bar"</js>).append(123);
1534 	 *
1535 	 * 	<jc>// Using this method</jc>
1536 	 * 	<jv>map</jv>.postAt(<js>"foo/bar"</js>, 123);
1537 	 * </p>
1538 	 *
1539 	 * <p>
1540 	 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various
1541 	 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays).
1542 	 *
1543 	 * @param path The path to the entry.
1544 	 * @param o The new value.
1545 	 * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
1546 	 */
1547 	public Object postAt(String path, Object o) {
1548 		return getObjectRest().post(path, o);
1549 	}
1550 
1551 	@Override
1552 	public Object put(String key, Object value) {
1553 		if (valueFilter.test(value))
1554 			super.put(key, value);
1555 		return null;
1556 	}
1557 
1558 	/**
1559 	 * Same as <c>put(String,Object)</c>, but the key is a slash-delimited path used to traverse entries in this
1560 	 * POJO.
1561 	 *
1562 	 * <p>
1563 	 * For example, the following code is equivalent:
1564 	 * </p>
1565 	 * <p class='bjava'>
1566 	 * 	JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>);
1567 	 *
1568 	 * 	<jc>// Long way</jc>
1569 	 * 	<jv>map</jv>.getMap(<js>"foo"</js>).getList(<js>"bar"</js>).getMap(<js>"0"</js>).put(<js>"baz"</js>, 123);
1570 	 *
1571 	 * 	<jc>// Using this method</jc>
1572 	 * 	<jv>map</jv>.putAt(<js>"foo/bar/0/baz"</js>, 123);
1573 	 * </p>
1574 	 *
1575 	 * <p>
1576 	 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various
1577 	 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays).
1578 	 *
1579 	 * @param path The path to the entry.
1580 	 * @param o The new value.
1581 	 * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
1582 	 */
1583 	public Object putAt(String path, Object o) {
1584 		return getObjectRest().put(path, o);
1585 	}
1586 
1587 	/**
1588 	 * Convenience method for inserting JSON directly into an attribute on this object.
1589 	 *
1590 	 * <p>
1591 	 * The JSON text can be an object (i.e. <js>"{...}"</js>) or an array (i.e. <js>"[...]"</js>).
1592 	 *
1593 	 * @param key The key.
1594 	 * @param json The JSON text that will be parsed into an Object and then inserted into this map.
1595 	 * @throws ParseException Malformed input encountered.
1596 	 */
1597 	public void putJson(String key, String json) throws ParseException {
1598 		this.put(key, JsonParser.DEFAULT.parse(json, Object.class));
1599 	}
1600 
1601 	/**
1602 	 * Convenience method for removing several keys at once.
1603 	 *
1604 	 * @param keys The list of keys to remove.
1605 	 */
1606 	public void removeAll(Collection<String> keys) {
1607 		keys.forEach(this::remove);
1608 	}
1609 
1610 	/**
1611 	 * Convenience method for removing several keys at once.
1612 	 *
1613 	 * @param keys The list of keys to remove.
1614 	 */
1615 	public void removeAll(String...keys) {
1616 		for (var k : keys)
1617 			remove(k);
1618 	}
1619 
1620 	/**
1621 	 * Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,Boolean.<jk>class</jk>)</code>.
1622 	 *
1623 	 * @param key The key.
1624 	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
1625 	 * @throws InvalidDataConversionException If value cannot be converted.
1626 	 */
1627 	public Boolean removeBoolean(String key) {
1628 		return removeBoolean(key, null);
1629 	}
1630 
1631 	/**
1632 	 * Equivalent to calling <code>removeWithDefault(key,def,Boolean.<jk>class</jk>)</code>.
1633 	 *
1634 	 * @param key The key.
1635 	 * @param def The default value if the map doesn't contain the specified mapping.
1636 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1637 	 * @throws InvalidDataConversionException If value cannot be converted.
1638 	 */
1639 	public Boolean removeBoolean(String key, Boolean def) {
1640 		return removeWithDefault(key, def, Boolean.class);
1641 	}
1642 
1643 	/**
1644 	 * Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,Integer.<jk>class</jk>)</code>.
1645 	 *
1646 	 * @param key The key.
1647 	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
1648 	 * @throws InvalidDataConversionException If value cannot be converted.
1649 	 */
1650 	public Integer removeInt(String key) {
1651 		return removeInt(key, null);
1652 	}
1653 
1654 	/**
1655 	 * Equivalent to calling <code>removeWithDefault(key,def,Integer.<jk>class</jk>)</code>.
1656 	 *
1657 	 * @param key The key.
1658 	 * @param def The default value if the map doesn't contain the specified mapping.
1659 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1660 	 * @throws InvalidDataConversionException If value cannot be converted.
1661 	 */
1662 	public Integer removeInt(String key, Integer def) {
1663 		return removeWithDefault(key, def, Integer.class);
1664 	}
1665 
1666 	/**
1667 	 * Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,String.<jk>class</jk>)</code>.
1668 	 *
1669 	 * @param key The key.
1670 	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
1671 	 * @throws InvalidDataConversionException If value cannot be converted.
1672 	 */
1673 	public String removeString(String key) {
1674 		return removeString(key, null);
1675 	}
1676 
1677 	/**
1678 	 * Equivalent to calling <code>removeWithDefault(key,def,String.<jk>class</jk>)</code>.
1679 	 *
1680 	 * @param key The key.
1681 	 * @param def The default value if the map doesn't contain the specified mapping.
1682 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1683 	 * @throws InvalidDataConversionException If value cannot be converted.
1684 	 */
1685 	public String removeString(String key, String def) {
1686 		return removeWithDefault(key, def, String.class);
1687 	}
1688 
1689 	/**
1690 	 * Equivalent to calling <c>get(class,key,def)</c> followed by <c>remove(key);</c>
1691 	 * @param key The key.
1692 	 * @param defVal The default value if the map doesn't contain the specified mapping.
1693 	 * @param type The class type.
1694 	 *
1695 	 * @param <T> The class type.
1696 	 * @return The converted value, or the default value if the map contains no mapping for this key.
1697 	 * @throws InvalidDataConversionException If value cannot be converted.
1698 	 */
1699 	public <T> T removeWithDefault(String key, T defVal, Class<T> type) {
1700 		T t = getWithDefault(key, defVal, type);
1701 		remove(key);
1702 		return t;
1703 	}
1704 
1705 	/**
1706 	 * Override the default bean session used for converting POJOs.
1707 	 *
1708 	 * <p>
1709 	 * Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases.
1710 	 *
1711 	 * <p>
1712 	 * Useful if you're serializing/parsing beans with transforms defined.
1713 	 *
1714 	 * @param session The new bean session.
1715 	 * @return This object.
1716 	 */
1717 	public JsonMap session(BeanSession session) {
1718 		this.session = session;
1719 		return this;
1720 	}
1721 
1722 	/**
1723 	 * Sets the {@link BeanSession} currently associated with this map.
1724 	 *
1725 	 * @param value The {@link BeanSession} currently associated with this map.
1726 	 * @return This object.
1727 	 */
1728 	public JsonMap setBeanSession(BeanSession value) {
1729 		session = value;
1730 		return this;
1731 	}
1732 
1733 	@Override /* Overridden from Object */
1734 	public String toString() {
1735 		return Json5.of(this);
1736 	}
1737 
1738 	/**
1739 	 * Returns an unmodifiable copy of this map if it's modifiable.
1740 	 *
1741 	 * @return An unmodifiable copy of this map if it's modifiable, or this map if it is already unmodifiable.
1742 	 */
1743 	public JsonMap unmodifiable() {
1744 		if (this instanceof UnmodifiableJsonMap this2)
1745 			return this2;
1746 		return new UnmodifiableJsonMap(this);
1747 	}
1748 
1749 	/**
1750 	 * Convenience method for serializing this map to the specified <c>Writer</c> using the
1751 	 * {@link JsonSerializer#DEFAULT} serializer.
1752 	 *
1753 	 * @param w The writer to serialize this object to.
1754 	 * @return This object.
1755 	 * @throws IOException If a problem occurred trying to write to the writer.
1756 	 * @throws SerializeException If a problem occurred trying to convert the output.
1757 	 */
1758 	public JsonMap writeTo(Writer w) throws IOException, SerializeException {
1759 		JsonSerializer.DEFAULT.serialize(this, w);
1760 		return this;
1761 	}
1762 
1763 	private BeanSession bs() {
1764 		if (session == null)
1765 			session = BeanContext.DEFAULT_SESSION;
1766 		return session;
1767 	}
1768 
1769 	/*
1770 	 * Converts this map to the specified class type.
1771 	 */
1772 	@SuppressWarnings({ "unchecked", "rawtypes" })
1773 	private <T> T cast2(ClassMeta<T> cm) {
1774 
1775 		BeanSession bs = bs();
1776 		try {
1777 			Object value = get("value");
1778 
1779 			if (cm.isMap()) {
1780 				Map m2 = (cm.canCreateNewInstance() ? (Map)cm.newInstance() : new JsonMap(bs));
1781 				ClassMeta<?> kType = cm.getKeyType(), vType = cm.getValueType();
1782 				forEach((k, v) -> {
1783 					if (! k.equals(bs.getBeanTypePropertyName(cm))) {
1784 
1785 						// Attempt to recursively cast child maps.
1786 						if (v instanceof JsonMap v2)
1787 							v = v2.cast(vType);
1788 
1789 						Object k2 = (kType.isString() ? k : bs.convertToType(k, kType));
1790 						v = (vType.isObject() ? v : bs.convertToType(v, vType));
1791 
1792 						m2.put(k2, v);
1793 					}
1794 				});
1795 				return (T)m2;
1796 
1797 			} else if (cm.isBean()) {
1798 				BeanMap<? extends T> bm = bs.newBeanMap(cm.inner());
1799 
1800 				// Iterate through all the entries in the map and set the individual field values.
1801 				forEach((k, v) -> {
1802 					if (! k.equals(bs.getBeanTypePropertyName(cm))) {
1803 
1804 						// Attempt to recursively cast child maps.
1805 						if (v instanceof JsonMap v2)
1806 							v = v2.cast(bm.getProperty(k).getMeta().getClassMeta());
1807 
1808 						bm.put(k, v);
1809 					}
1810 				});
1811 
1812 				return bm.getBean();
1813 
1814 			} else if (cm.isCollectionOrArray()) {
1815 				var items = (List)get("items");
1816 				return bs.convertToType(items, cm);
1817 
1818 			} else if (nn(value)) {
1819 				return bs.convertToType(value, cm);
1820 			}
1821 
1822 		} catch (Exception e) {
1823 			throw bex(e, cm.inner(), "Error occurred attempting to cast to an object of type ''{0}''", cn(cm));
1824 		}
1825 
1826 		throw bex(cm.inner(), "Cannot convert to class type ''{0}''.  Only beans and maps can be converted using this method.", cn(cm));
1827 	}
1828 
1829 	private ObjectRest getObjectRest() {
1830 		if (objectRest == null)
1831 			objectRest = new ObjectRest(this);
1832 		return objectRest;
1833 	}
1834 
1835 	/*
1836 	 * Combines the class specified by a "_type" attribute with the ClassMeta
1837 	 * passed in through the cast(ClassMeta) method.
1838 	 * The rule is that child classes supersede parent classes, and c2 supersedes c1
1839 	 * if one isn't the parent of another.
1840 	 */
1841 	private ClassMeta<?> narrowClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) {
1842 		if (c1 == null)
1843 			return c2;
1844 		ClassMeta<?> c = getNarrowedClassMeta(c1, c2);
1845 		if (c1.isMap()) {
1846 			ClassMeta<?> k = getNarrowedClassMeta(c1.getKeyType(), c2.getKeyType());
1847 			ClassMeta<?> v = getNarrowedClassMeta(c1.getValueType(), c2.getValueType());
1848 			return bs().getClassMeta(c.inner(), k, v);
1849 		}
1850 		if (c1.isCollection()) {
1851 			ClassMeta<?> e = getNarrowedClassMeta(c1.getElementType(), c2.getElementType());
1852 			return bs().getClassMeta(c.inner(), e);
1853 		}
1854 		return c;
1855 	}
1856 
1857 	private void parse(Reader r, Parser p) throws ParseException {
1858 		if (p == null)
1859 			p = JsonParser.DEFAULT;
1860 		p.parseIntoMap(r, this, bs().string(), bs().object());
1861 	}
1862 }