001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.collections; 018 019import static org.apache.juneau.commons.utils.AssertionUtils.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.*; 021import static org.apache.juneau.commons.utils.PredicateUtils.*; 022import static org.apache.juneau.commons.utils.StringUtils.*; 023import static org.apache.juneau.commons.utils.ThrowableUtils.*; 024import static org.apache.juneau.commons.utils.Utils.*; 025 026import java.io.*; 027import java.lang.reflect.*; 028import java.util.*; 029import java.util.function.*; 030 031import org.apache.juneau.*; 032import org.apache.juneau.commons.utils.*; 033import org.apache.juneau.json.*; 034import org.apache.juneau.marshaller.*; 035import org.apache.juneau.objecttools.*; 036import org.apache.juneau.parser.*; 037import org.apache.juneau.serializer.*; 038import org.apache.juneau.swap.*; 039 040/** 041 * Java implementation of a JSON object. 042 * 043 * <p> 044 * An extension of {@link LinkedHashMap}, so all methods available in that class are also available to this class. 045 * 046 * <p> 047 * Note that the use of this class is optional for generating JSON. The serializers will accept any objects that implement the 048 * {@link java.util.Map} interface. But this class provides some useful additional functionality when working with 049 * JSON models constructed from Java Collections Framework objects. For example, a constructor is provided for 050 * converting a JSON object string directly into a {@link Map}. It also contains accessor methods for to avoid common 051 * typecasting when accessing elements in a list. 052 * 053 * <h5 class='section'>Example:</h5> 054 * <p class='bjava'> 055 * <jc>// Construct an empty Map</jc> 056 * JsonMap <jv>map</jv> = JsonMap.<jsm>of</jsm>(); 057 * 058 * <jc>// Construct a Map from JSON</jc> 059 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{a:'A',b:{c:'C',d:123}}"</js>); 060 * 061 * <jc>// Construct a Map using the append method</jc> 062 * <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>); 063 * 064 * <jc>// Construct a Map from XML generated by XmlSerializer</jc> 065 * 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>; 066 * <jv>map</jv> = JsonMap.<jsm>of</jsm>(<jv>xml</jv>, XmlParser.<jsf>DEFAULT</jsf>); 067 * 068 * <jc>// Construct a Map from a URL GET parameter string generated by UrlEncodingParser</jc> 069 * String <jv>urlParams</jv> = <js>"?a='A'&b={c:'C',d:123}"</js>; 070 * <jv>map</jv> = JsonMap.<jsm>of</jsm>(<jv>urlParams</jv>, UrlEncodingParser.<jsf>DEFAULT</jsf>); 071 * 072 * <jc>// Construct JSON from JsonMap</jc> 073 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:'bar'},{baz:[123,true]}"</js>); 074 * String <jv>json</jv> = <jv>map</jv>.toString(); <jc>// Produces "{foo:'bar'},{baz:[123,true]}"</jc> 075 * <jv>json</jv> = <jv>map</jv>.toString(JsonSerializer.<jsf>DEFAULT</jsf>); <jc>// Equivalent</jc> 076 * <jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>map</jv>); <jc>// Equivalent</jc> 077 * 078 * <jc>// Get a map entry as an Integer</jc> 079 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:123}"</js>); 080 * Integer <jv>integer</jv> = <jv>map</jv>.getInt(<js>"foo"</js>); 081 * <jv>integer</jv> = <jv>map</jv>.get(Integer.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> 082 * 083 * <jc>// Get a map entry as a Float</jc> 084 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:123}"</js>); 085 * Float <jv>_float</jv> = <jv>map</jv>.getFloat(<js>"foo"</js>); 086 * <jv>_float</jv> = <jv>map</jv>.get(Float.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> 087 * 088 * <jc>// Same as above, except converted to a String</jc> 089 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:123}"</js>); 090 * String <jv>string</jv> = <jv>map</jv>.getString(<js>"foo"</js>); <jc>// Returns "123"</jc> 091 * <jv>string</jv> = <jv>map</jv>.get(String.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> 092 * 093 * <jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc> 094 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{person:{name:'John Smith',age:45}}"</js>); 095 * Person <jv>person</jv> = <jv>map</jv>.get(Person.<jk>class</jk>, <js>"person"</js>); 096 * 097 * <jc>// Add an inner map</jc> 098 * JsonMap <jv>map1</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{a:1}"</js>); 099 * 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 */ 107public 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}