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>"<object><a type='string'>A</a><b type='object'><c type='string'>C</c><d type='number'>123</d></b></object>"</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'&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<String> <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<MyBean> <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<List<String>> <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<String,String> <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<String,List<MyBean>> <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 }