1 /* 2 * The contents of this file are subject to the terms of the Common Development and 3 * Distribution License (the License). You may not use this file except in compliance with the 4 * License. 5 * 6 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 7 * specific language governing permission and limitations under the License. 8 * 9 * When distributing Covered Software, include this CDDL Header Notice in each file and include 10 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 11 * Header, with the fields enclosed by brackets [] replaced by your own identifying 12 * information: "Portions Copyrighted [year] [name of copyright owner]". 13 * 14 * Copyright © 2010–2011 ApexIdentity Inc. All rights reserved. 15 * Portions Copyrighted 2011-2017 ForgeRock AS. 16 */ 17 18 package org.forgerock.json; 19 20 import java.util.AbstractMap; 21 import java.util.AbstractSet; 22 import java.util.ArrayList; 23 import java.util.Collection; 24 import java.util.Collections; 25 import java.util.Iterator; 26 import java.util.LinkedHashMap; 27 import java.util.List; 28 import java.util.Locale; 29 import java.util.Map; 30 import java.util.Set; 31 32 import org.forgerock.util.Function; 33 import org.forgerock.util.RangeSet; 34 35 /** 36 * Represents a value in a JSON object model structure. JSON values are 37 * represented with standard Java objects: {@link String}, {@link Number}, 38 * {@link Map}, {@link List}, {@link Boolean} and {@code null}. 39 */ 40 public class JsonValue implements Cloneable, Iterable<JsonValue> { 41 42 /** 43 * Returns a mutable JSON array containing the provided objects. This method 44 * is provided as a convenience method for constructing JSON arrays. Example 45 * usage: 46 * 47 * <pre> 48 * JsonValue value = json(array(1, 2, 3)); 49 * </pre> 50 * 51 * @param objects 52 * The array elements. 53 * @return A JSON array. 54 */ 55 public static List<Object> array(final Object... objects) { 56 List<Object> array = new ArrayList<>(objects.length); 57 for (Object o : objects) { 58 array.add(unwrap(o)); 59 } 60 return array; 61 } 62 63 /** 64 * Returns a JSON field for inclusion in a JSON object using 65 * {@link #object(java.util.Map.Entry...) object} only if its value is not {@code null}. 66 * Example usage: 67 * 68 * <pre> 69 * JsonValue value = json(object(fieldIfNotNull("uid", getUid())); 70 * </pre> 71 * <p> 72 * Note: This feature depends on the {@link #object(java.util.Map.Entry...)} method that 73 * checks if the entry is not {@code null} before including it into the map. 74 * </p> 75 * 76 * @param key 77 * The JSON field name. 78 * @param value 79 * The JSON field value (may be {@code null}). 80 * @return The JSON field for inclusion in a JSON object or {@code null}. 81 * @see #object(java.util.Map.Entry...) 82 */ 83 public static Map.Entry<String, Object> fieldIfNotNull(final String key, final Object value) { 84 return value != null ? field(key, value) : null; 85 } 86 87 /** 88 * Returns a JSON field for inclusion in a JSON object using 89 * {@link #object(java.util.Map.Entry...) object}. This method is provided 90 * as a convenience method for constructing JSON objects. Example usage: 91 * 92 * <pre> 93 * JsonValue value = json(object(field("uid", "bjensen"), field("age", 30))); 94 * </pre> 95 * 96 * @param key 97 * The JSON field name. 98 * @param value 99 * The JSON field value. 100 * @return The JSON field for inclusion in a JSON object. 101 */ 102 public static Map.Entry<String, Object> field(final String key, final Object value) { 103 return new AbstractMap.SimpleImmutableEntry<>(key, unwrap(value)); 104 } 105 106 /** 107 * Returns a JSON value whose content is the provided object. This method is 108 * provided as a convenience method for constructing JSON objects, instead 109 * of using {@link #JsonValue(Object)}. Example usage: 110 * 111 * <pre> 112 * JsonValue value = 113 * json(object(field("uid", "bjensen"), 114 * field("roles", array("sales", "marketing")))); 115 * </pre> 116 * 117 * @param object 118 * the Java object representing the JSON value. 119 * @return The JSON value. 120 */ 121 public static JsonValue json(final Object object) { 122 return new JsonValue(unwrap(object)); 123 } 124 125 /** 126 * Returns a JSON object comprised of the provided JSON 127 * {@link #field(String, Object) fields}. This method is provided as a 128 * convenience method for constructing JSON objects. Example usage: 129 * 130 * <pre> 131 * JsonValue value = json(object(field("uid", "bjensen"), field("age", 30))); 132 * </pre> 133 * 134 * @param fields 135 * The list of {@link #field(String, Object) fields} to include 136 * in the JSON object. {@code null} elements are allowed, but are 137 * not included in the returned map (this makes it easier to 138 * include optional elements). 139 * @return The JSON object. 140 */ 141 @SafeVarargs 142 public static Map<String, Object> object(final Map.Entry<String, Object>... fields) { 143 final Map<String, Object> object = object(fields.length); 144 for (final Map.Entry<String, Object> field : fields) { 145 if (field != null) { 146 object.put(field.getKey(), unwrap(field.getValue())); 147 } 148 } 149 return object; 150 } 151 152 /** 153 * Produces an empty JSON object pre-allocated for {@code size} 154 * {@link #field(String, Object) fields}. This method is provided as a 155 * convenience method for constructing JSON objects. Example usage: 156 * 157 * <pre> 158 * JsonValue value = json(object(20)); 159 * for (Map.Entry<String, Object> entry : someMap.entrySet()) { 160 * value.put(entry.getKey(), entry.getValue()); 161 * } 162 * </pre> 163 * 164 * @param size 165 * The size of the JSON object to allocate. 166 * @return The [empty] JSON object. 167 */ 168 public static Map<String, Object> object(int size) { 169 return new LinkedHashMap<>(size); 170 } 171 172 /** 173 * Returns the key as an list index value. If the string does not represent 174 * a valid list index value, then {@code -1} is returned. 175 * 176 * @param key 177 * the key to be converted into an list index value. 178 * @return the converted index value, or {@code -1} if invalid. 179 */ 180 public static int toIndex(final String key) { 181 if (key == null || key.isEmpty()) { 182 return -1; 183 } 184 185 // verify that every character is a digit (this also prevents negative values) 186 int result = 0; 187 188 for (int i = 0; i < key.length(); ++i) { 189 final char c = key.charAt(i); 190 if (c < '0' || c > '9') { 191 return -1; 192 } 193 result = result * 10 + (c - '0'); 194 } 195 return result; 196 } 197 198 /** 199 * Unwrap the object if it is a JsonValue - used when combining JsonValues so we 200 * do not get nested JsonValue wrappers. 201 * 202 * @param object the object to unwrap. 203 * @return the unwrapped object. 204 */ 205 private static Object unwrap(final Object object) { 206 return object instanceof JsonValue 207 ? ((JsonValue) object).getObject() 208 : object; 209 } 210 211 /** The Java object representing this JSON value. */ 212 private Object object; 213 214 /** The pointer to the value within a JSON structure. */ 215 private JsonPointer pointer; 216 217 /** 218 * Constructs a JSON value object with a given object. This constructor will 219 * automatically unwrap {@link JsonValue} objects. 220 * 221 * @param object 222 * the Java object representing the JSON value. 223 */ 224 public JsonValue(final Object object) { 225 this(object, null); 226 } 227 228 /** 229 * Constructs a JSON value object with a given object and pointer. This 230 * constructor will automatically unwrap {@link JsonValue} objects. 231 * 232 * @param object 233 * the Java object representing the JSON value. 234 * @param pointer 235 * the pointer to the value in a JSON structure. 236 */ 237 public JsonValue(final Object object, final JsonPointer pointer) { 238 this.object = object; 239 this.pointer = pointer; 240 if (object instanceof JsonValue) { 241 final JsonValue jv = (JsonValue) object; 242 this.object = jv.object; 243 if (pointer == null) { 244 this.pointer = jv.pointer; 245 } 246 } 247 if (this.pointer == null) { 248 this.pointer = new JsonPointer(); 249 } 250 } 251 252 /** 253 * Adds the specified value to the list. Adding a value to a list shifts any 254 * existing elements at or above the specified index to the right by one. 255 * 256 * @param index 257 * the {@code List} index of the value to add. 258 * @param object 259 * the java object to add. 260 * @return this JSON value. 261 * @throws JsonValueException 262 * if this JSON value is not a {@code List} or index is out of 263 * range. 264 */ 265 public JsonValue add(final int index, final Object object) { 266 final List<Object> list = required().asList(); 267 if (index < 0 || index > list.size()) { 268 throw new JsonValueException(this, "List index out of range: " + index); 269 } 270 list.add(index, unwrap(object)); 271 return this; 272 } 273 274 /** 275 * Adds the value identified by the specified pointer, relative to this 276 * value as root. If doing so would require the creation of a new object or 277 * list, a {@code JsonValueException} will be thrown. 278 * <p> 279 * NOTE: values may be added to a list using the reserved JSON pointer token 280 * "-". For example, the pointer "/a/b/-" will add a new element to the list 281 * referenced by "/a/b". 282 * 283 * @param pointer 284 * identifies the child value to add. 285 * @param object 286 * the Java object value to add. 287 * @return this JSON value. 288 * @throws JsonValueException 289 * if the specified pointer is invalid. 290 */ 291 public JsonValue add(final JsonPointer pointer, final Object object) { 292 navigateToParentOf(pointer).required().addToken(pointer.leaf(), object); 293 return this; 294 } 295 296 /** 297 * Adds the specified value to the end of the list. This method is 298 * equivalent to the following code: 299 * 300 * <pre> 301 * add(size(), object); 302 * </pre> 303 * 304 * @param object 305 * the java object to add. 306 * @return this JSON value. 307 * @throws JsonValueException 308 * if this JSON value is not a {@code List}. 309 */ 310 public JsonValue add(final Object object) { 311 if (isList()) { 312 return add(size(), object); 313 } 314 throw new JsonValueException(this, "Expecting a List"); 315 } 316 317 /** 318 * Adds the specified value. 319 * <p> 320 * If adding to a list value, the specified key must be parseable as an 321 * unsigned base-10 integer and be less than or equal to the list size. 322 * Adding a value to a list shifts any existing elements at or above the 323 * specified index to the right by one. 324 * 325 * @param key 326 * the {@code Map} key or {@code List} index to add. 327 * @param object 328 * the Java object to add. 329 * @return this JSON value. 330 * @throws JsonValueException 331 * if not a {@code Map} or {@code List}, the {@code Map} key 332 * already exists, or the {@code List} index is out of range. 333 */ 334 public JsonValue add(final String key, final Object object) { 335 if (isMap()) { 336 final Map<String, Object> map = asMap(); 337 if (map.containsKey(key)) { 338 throw new JsonValueException(this, "Map key " + key + " already exists"); 339 } 340 map.put(key, unwrap(object)); 341 } else if (isList()) { 342 add(toIndex(key), object); 343 } else { 344 throw new JsonValueException(this, "Expecting a Map or List"); 345 } 346 return this; 347 } 348 349 /** 350 * Adds the value identified by the specified pointer, relative to this 351 * value as root. Missing parent objects or lists will be created on demand. 352 * <p> 353 * NOTE: values may be added to a list using the reserved JSON pointer token 354 * "-". For example, the pointer "/a/b/-" will add a new element to the list 355 * referenced by "/a/b". 356 * 357 * @param pointer 358 * identifies the child value to add. 359 * @param object 360 * the Java object value to add. 361 * @return this JSON value. 362 * @throws JsonValueException 363 * if the specified pointer is invalid. 364 */ 365 public JsonValue addPermissive(final JsonPointer pointer, final Object object) { 366 navigateToParentOfPermissive(pointer).addToken(pointer.leaf(), object); 367 return this; 368 } 369 370 /** 371 * Returns the JSON value as a {@link Boolean} object. If the value is 372 * {@code null}, this method returns {@code null}. 373 * 374 * @return the boolean value. 375 * @throws JsonValueException 376 * if the JSON value is not a boolean type. 377 */ 378 public Boolean asBoolean() { 379 return (object == null ? null : (Boolean) (expect(Boolean.class).object)); 380 } 381 382 /** 383 * Returns the JSON value as a {@link Double} object. This may involve 384 * rounding. If the JSON value is {@code null}, this method returns 385 * {@code null}. 386 * 387 * @return the double-precision floating point value. 388 * @throws JsonValueException 389 * if the JSON value is not a number. 390 */ 391 public Double asDouble() { 392 return (object == null ? null : Double.valueOf(asNumber().doubleValue())); 393 } 394 395 /** 396 * Returns the JSON value as an {@link Integer} object. This may involve 397 * rounding or truncation. If the JSON value is {@code null}, this method 398 * returns {@code null}. 399 * 400 * @return the integer value. 401 * @throws JsonValueException 402 * if the JSON value is not a number. 403 */ 404 public Integer asInteger() { 405 return (object == null ? null : Integer.valueOf(asNumber().intValue())); 406 } 407 408 /** 409 * Returns the JSON value as a {@link Collection} object. If the JSON value is 410 * {@code null}, this method returns {@code null}. 411 * 412 * @return the collection value, or {@code null} if no value. 413 * @throws JsonValueException 414 * if the JSON value is not a {@code Collection}. 415 */ 416 public Collection<Object> asCollection() { 417 return asCollection(Object.class); 418 } 419 420 /** 421 * Returns the JSON value as a {@link List} object. If the JSON value is 422 * {@code null}, this method returns {@code null}. 423 * The returned {@link List} is <b>not</b> a copy : any interaction with it 424 * will affect the {@link JsonValue}. 425 * 426 * @return the list value, or {@code null} if no value. 427 * @throws JsonValueException 428 * if the JSON value is not a {@code List}. 429 */ 430 public List<Object> asList() { 431 return asList(Object.class); 432 } 433 434 /** 435 * Returns the JSON value as a {@link Collection} containing objects of the 436 * specified type. If the value is {@code null}, this method returns 437 * {@code null}. If any of the elements of the collection are not {@code null} and 438 * not of the specified type, {@code JsonValueException} is thrown. 439 * The returned {@link Collection} is <b>not</b> a copy : any interaction with it 440 * will affect the {@link JsonValue}. 441 * 442 * @param <E> 443 * the type of elements in this collection 444 * @param type 445 * the type of object that all elements are expected to be. 446 * @return the collection value, or {@code null} if no value. 447 * @throws JsonValueException 448 * if the JSON value is not a {@code Collection} or contains an 449 * unexpected type. 450 * @throws NullPointerException 451 * if {@code type} is {@code null}. 452 */ 453 @SuppressWarnings("unchecked") 454 public <E> Collection<E> asCollection(final Class<E> type) { 455 if (object != null) { 456 expect(Collection.class); 457 if (type != Object.class) { 458 final Collection<Object> coll = (Collection<Object>) this.object; 459 for (final Object element : coll) { 460 if (element != null && !type.isInstance(element)) { 461 throw new JsonValueException(this, "Expecting a Collection of " + type.getName() 462 + " elements"); 463 } 464 } 465 } 466 } 467 return (Collection<E>) object; 468 } 469 470 /** 471 * Returns the JSON value as a {@link List} containing objects of the 472 * specified type. If the value is {@code null}, this method returns 473 * {@code null}. If any of the elements of the list are not {@code null} and 474 * not of the specified type, {@code JsonValueException} is thrown. 475 * The returned {@link List} is <b>not</b> a copy : any interaction with it 476 * will affect the {@link JsonValue}. 477 * 478 * @param <E> 479 * the type of elements in this list 480 * @param type 481 * the type of object that all elements are expected to be. 482 * @return the list value, or {@code null} if no value. 483 * @throws JsonValueException 484 * if the JSON value is not a {@code List} or contains an unexpected type. 485 * @throws NullPointerException 486 * if {@code type} is {@code null}. 487 */ 488 @SuppressWarnings("unchecked") 489 public <E> List<E> asList(final Class<E> type) { 490 if (object != null) { 491 expect(List.class); 492 if (type != Object.class) { 493 final List<Object> list = (List<Object>) this.object; 494 for (final Object element : list) { 495 if (element != null && !type.isInstance(element)) { 496 throw new JsonValueException(this, "Expecting a List of " + type.getName() 497 + " elements"); 498 } 499 } 500 } 501 } 502 return (List<E>) object; 503 } 504 505 /** 506 * Returns the JSON value as an object whose type 507 * (and value) is specified by a transformation function. It is up to to the 508 * transformation function to transform/enforce source types of the elements 509 * in the Json source element and to decide what to do depending on the kind 510 * of {@link JsonValue} : if it is null, a {@link String}, a {@link List}, 511 * or {@link Map}. If the type-transformation cannot occur, 512 * the exception specified by the transformation function is thrown. 513 * 514 * @param <V> 515 * the type of element 516 * @param <E> 517 * the type of exception thrown by the transformation function 518 * @param transformFunction 519 * a {@link Function} to transform the JsonValue element to the desired type 520 * @return the value, or {@code null} if no value. 521 * @throws E 522 * if the JsonValue element cannot be transformed 523 * @throws NullPointerException 524 * if {@code transformFunction} is {@code null}. 525 */ 526 public <V, E extends Exception> V as(final Function<JsonValue, V, E> transformFunction) throws E { 527 return transformFunction.apply(this); 528 } 529 530 /** 531 * Returns the JSON value as a {@link Long} object. This may involve 532 * rounding or truncation. If the JSON value is {@code null}, this method 533 * returns {@code null}. 534 * 535 * @return the long integer value. 536 * @throws JsonValueException 537 * if the JSON value is not a number. 538 */ 539 public Long asLong() { 540 return (object == null ? null : Long.valueOf(asNumber().longValue())); 541 } 542 543 /** 544 * Returns the JSON value as a {@code Map} object. If the JSON value is 545 * {@code null}, this method returns {@code null}. 546 * 547 * @return the map value, or {@code null} if no value. 548 * @throws JsonValueException 549 * if the JSON value is not a {@code Map}. 550 */ 551 @SuppressWarnings("unchecked") 552 public Map<String, Object> asMap() { 553 return (object == null ? null : (Map<String, Object>) (expect(Map.class).object)); 554 } 555 556 /** 557 * Returns the JSON value as a {@link Map} containing objects of the 558 * specified type. If the value is {@code null}, this method returns 559 * {@code null}. If any of the values of the map are not {@code null} and 560 * not of the specified type, {@code JsonValueException} is thrown. 561 * 562 * @param <V> 563 * the type of values in this map 564 * @param type 565 * the type of object that all values are expected to be. 566 * @return the map value, or {@code null} if no value. 567 * @throws JsonValueException 568 * if the JSON value is not a {@code Map} or contains an 569 * unexpected type. 570 * @throws NullPointerException 571 * if {@code type} is {@code null}. 572 */ 573 @SuppressWarnings("unchecked") 574 public <V> Map<String, V> asMap(final Class<V> type) { 575 if (object != null) { 576 expect(Map.class); 577 if (type != Object.class) { 578 final Map<String, Object> map = (Map<String, Object>) this.object; 579 for (final Object element : map.values()) { 580 if (element != null && !type.isInstance(element)) { 581 throw new JsonValueException(this, "Expecting a Map of " + type.getName() 582 + " elements"); 583 } 584 } 585 } 586 } 587 return (Map<String, V>) object; 588 } 589 /** 590 * Returns the JSON value as a {@link Map} containing a collection of 591 * objects of the specified type. If the value is {@code null}, this method 592 * returns {@code null}. If any of the values of the map are not {@code null} and 593 * not of the specified type, {@code JsonValueException} is thrown. 594 * 595 * @param <E> 596 * the type of elements in the collection 597 * @param elementType 598 * the type of object that all collection elements are 599 * expected to be. 600 * @return the map value, or {@code null} if no value. 601 * @throws JsonValueException 602 * if the JSON value is not a {@code Map} or contains an 603 * unexpected type. 604 * @throws NullPointerException 605 * if {@code type} is {@code null}. 606 */ 607 @SuppressWarnings("unchecked") 608 public <E> Map<String, List<E>> asMapOfList(final Class<E> elementType) { 609 if (object != null) { 610 expect(Map.class); 611 if (elementType != Object.class) { 612 final Map<String, Object> map = (Map<String, Object>) this.object; 613 for (final Object value : map.values()) { 614 if (value != null && !(value instanceof List)) { 615 throw new JsonValueException(this, "Expecting a Map of List values"); 616 } 617 final List<?> list = (List<?>) value; 618 for (final Object element : list) { 619 if (element != null && !elementType.isInstance(element)) { 620 throw new JsonValueException(this, "Expecting a Map of Lists with " 621 + elementType.getName() + " elements"); 622 } 623 } 624 } 625 } 626 } 627 return (Map<String, List<E>>) object; 628 } 629 630 /** 631 * Returns the JSON value as a {@code Number} object. If the JSON value is 632 * {@code null}, this method returns {@code null}. 633 * 634 * @return the numeric value. 635 * @throws JsonValueException 636 * if the JSON value is not a number. 637 */ 638 public Number asNumber() { 639 return (object == null ? null : (Number) (expect(Number.class).object)); 640 } 641 642 /** 643 * Returns the JSON value as a {@code String} object. If the JSON value is 644 * {@code null}, this method returns {@code null}. 645 * 646 * @return the string value. 647 * @throws JsonValueException 648 * if the JSON value is not a string. 649 */ 650 public String asString() { 651 return (object == null ? null : (String) (expect(String.class).object)); 652 } 653 654 /** 655 * Removes all child values from this JSON value, if it has any. 656 */ 657 public void clear() { 658 if (isMap()) { 659 asMap().clear(); 660 } else if (isCollection()) { 661 asCollection().clear(); 662 } 663 } 664 665 /** 666 * Returns a shallow copy of this JSON value. If this JSON value contains a 667 * {@code Map} or a {@code List} object, the returned JSON 668 * value will contain a shallow copy of the original contained object. 669 * <p> 670 * The new value's members can be modified without affecting the original 671 * value. Modifying the member's members will almost certainly affect the 672 * original value. To avoid this, use the {@link #copy} method to return a 673 * deep copy of the JSON value. 674 * <p> 675 * This method does not traverse the value's members, nor will it apply any 676 * transformations. 677 * 678 * @return a shallow copy of this JSON value. 679 */ 680 @Override 681 public JsonValue clone() { 682 final JsonValue result = new JsonValue(this.object, this.pointer); 683 if (isMap()) { 684 result.object = new LinkedHashMap<>(this.asMap()); 685 } else if (isList()) { 686 result.object = new ArrayList<>(this.asList()); 687 } 688 return result; 689 } 690 691 /** 692 * Returns {@code true} this JSON value contains an item with the specified 693 * value. 694 * 695 * @param object 696 * the object to seek within this JSON value. 697 * @return {@code true} if this value contains the specified member value. 698 */ 699 public boolean contains(final Object object) { 700 boolean result = false; 701 if (isMap()) { 702 result = asMap().containsValue(object); 703 } else if (isCollection()) { 704 result = asCollection().contains(object); 705 } 706 return result; 707 } 708 709 /** 710 * Returns a deep copy of this JSON value. 711 * <p> 712 * Note: This method is recursive, and currently has no ability to detect or 713 * correct for structures containing cyclic references. Processing such a 714 * structure will result in a {@link StackOverflowError} being thrown. 715 * 716 * @return a deep copy of this JSON value. 717 */ 718 public JsonValue copy() { 719 // TODO: track original values to resolve cyclic references 720 final JsonValue result = new JsonValue(object, pointer); // start with shallow copy 721 if (this.isMap()) { 722 final Map<String, Object> map = object(size()); 723 for (final String key : keys()) { 724 map.put(key, this.get(key).copy().getObject()); // recursion 725 } 726 result.object = map; 727 } else if (isList()) { 728 final ArrayList<Object> list = new ArrayList<>(size()); 729 for (final JsonValue element : this) { 730 list.add(element.copy().getObject()); // recursion 731 } 732 result.object = list; 733 } 734 return result; 735 } 736 737 /** 738 * Defaults the JSON value to the specified value if it is currently 739 * {@code null}. 740 * 741 * @param object 742 * the object to default to. 743 * @return this JSON value or a new JSON value containing the default value. 744 */ 745 public JsonValue defaultTo(final Object object) { 746 return (this.object != null ? this : new JsonValue(object, this.pointer)); 747 } 748 749 /** 750 * Called to enforce that the JSON value is of a particular type. A value of 751 * {@code null} is allowed. 752 * 753 * @param type 754 * the class that the underlying value must have. 755 * @return this JSON value. 756 * @throws JsonValueException 757 * if the value is not the specified type. 758 */ 759 public JsonValue expect(final Class<?> type) { 760 if (object != null && !type.isInstance(object)) { 761 throw new JsonValueException(this, "Expecting a " + type.getName()); 762 } 763 return this; 764 } 765 766 /** 767 * Returns the specified child value. If this JSON value is not a 768 * {@link List} or if no such child exists, then a JSON value containing a 769 * {@code null} is returned. 770 * 771 * @param index 772 * index of child element value to return. 773 * @return the child value, or a JSON value containing {@code null}. 774 * @throws JsonValueException 775 * if index is negative. 776 */ 777 public JsonValue get(final int index) { 778 Object result = null; 779 if (index < 0) { 780 throw new JsonValueException(this, "List index out of range: " + index); 781 } 782 if (isList() && index >= 0) { 783 final List<Object> list = asList(); 784 if (index < list.size()) { 785 result = list.get(index); 786 } 787 } 788 return new JsonValue(result, pointer.child(index)); 789 } 790 791 /** 792 * Returns the specified child value with a pointer, relative to this value 793 * as root. If the specified child value does not exist, then {@code null} 794 * is returned. 795 * 796 * @param pointer 797 * the JSON pointer identifying the child value to return. 798 * @return the child value, or {@code null} if no such value exists. 799 */ 800 public JsonValue get(final JsonPointer pointer) { 801 JsonValue result = this; 802 for (final String token : pointer) { 803 final JsonValue member = result.get(token); 804 if (member.isNull() && !result.isDefined(token)) { 805 return null; // undefined value yields null, not a JSON value containing null 806 } 807 result = member; 808 } 809 return result; 810 } 811 812 /** 813 * Returns the specified item value. If no such member value exists, then a 814 * JSON value containing {@code null} is returned. 815 * 816 * @param key 817 * the {@code Map} key or {@code List} index identifying the item 818 * to return. 819 * @return a JSON value containing the value or {@code null}. 820 */ 821 public JsonValue get(final String key) { 822 Object result = null; 823 if (isMap()) { 824 result = asMap().get(key); 825 } else if (isList()) { 826 final List<Object> list = asList(); 827 final int index = toIndex(key); 828 if (index >= 0 && index < list.size()) { 829 result = list.get(index); 830 } 831 } 832 return new JsonValue(result, pointer.child(key)); 833 } 834 835 /** 836 * Returns the raw Java object representing this JSON value. 837 * 838 * @return the raw Java object representing this JSON value. 839 */ 840 public Object getObject() { 841 return object; 842 } 843 844 /** 845 * Returns the pointer of the JSON value in its JSON structure. 846 * 847 * @return the pointer of the JSON value in its JSON structure. 848 */ 849 public JsonPointer getPointer() { 850 return pointer; 851 } 852 853 /** 854 * Returns {@code true} if the JSON value is a {@link Boolean}. 855 * 856 * @return {@code true} if the JSON value is a {@link Boolean}. 857 */ 858 public boolean isBoolean() { 859 return (object != null && object instanceof Boolean); 860 } 861 862 /** 863 * Returns {@code true} if this JSON value contains the specified item. 864 * 865 * @param key 866 * the {@code Map} key or {@code List} index of the item to seek. 867 * @return {@code true} if this JSON value contains the specified member. 868 * @throws NullPointerException 869 * if {@code key} is {@code null}. 870 */ 871 public boolean isDefined(final String key) { 872 boolean result = false; 873 if (isMap()) { 874 result = asMap().containsKey(key); 875 } else if (isList()) { 876 final int index = toIndex(key); 877 result = (index >= 0 && index < asList().size()); 878 } 879 return result; 880 } 881 882 /** 883 * Returns {@code true} if the JSON value is a {@link Collection}. 884 * 885 * @return {@code true} if the JSON value is a {@link Collection}. 886 */ 887 public boolean isCollection() { 888 return (object instanceof Collection); 889 } 890 891 /** 892 * Returns {@code true} if the JSON value is a {@link List}. 893 * 894 * @return {@code true} if the JSON value is a {@link List}. 895 */ 896 public boolean isList() { 897 return (object instanceof List); 898 } 899 900 /** 901 * Returns {@code true} if the JSON value is a {@link Map}. 902 * 903 * @return {@code true} if the JSON value is a {@link Map}. 904 */ 905 public boolean isMap() { 906 return (object instanceof Map); 907 } 908 909 /** 910 * Returns {@code true} if the value is {@code null}. 911 * 912 * @return {@code true} if the value is {@code null}. 913 */ 914 public boolean isNull() { 915 return (object == null); 916 } 917 918 /** 919 * Returns {@code true} if the value is not {@code null}. 920 * 921 * @return {@code true} if the value is not {@code null}. 922 */ 923 public boolean isNotNull() { 924 return !isNull(); 925 } 926 927 /** 928 * Returns {@code true} if the JSON value is a {@link Number}. 929 * 930 * @return {@code true} if the JSON value is a {@link Number}. 931 */ 932 public boolean isNumber() { 933 return (object != null && object instanceof Number); 934 } 935 936 /** 937 * Returns {@code true} if the JSON value is a {@link String}. 938 * 939 * @return {@code true} if the JSON value is a {@link String}. 940 */ 941 public boolean isString() { 942 return (object != null && object instanceof String); 943 } 944 945 /** 946 * Returns an iterator over the child values that this JSON value contains. 947 * If this value is a {@link Map}, then the order of the 948 * resulting child values is undefined. Calling the {@link Iterator#remove()} 949 * method of the returned iterator will throw a {@link UnsupportedOperationException}. 950 * 951 * @return an iterator over the child values that this JSON value contains. 952 */ 953 @Override 954 public Iterator<JsonValue> iterator() { 955 if (isList()) { // optimize for list 956 return new Iterator<JsonValue>() { 957 int cursor = 0; 958 Iterator<Object> i = asList().iterator(); 959 960 @Override 961 public boolean hasNext() { 962 return i.hasNext(); 963 } 964 965 @Override 966 public JsonValue next() { 967 final Object element = i.next(); 968 return new JsonValue(element, pointer.child(cursor++)); 969 } 970 971 @Override 972 public void remove() { 973 throw new UnsupportedOperationException(); 974 } 975 }; 976 } else { 977 return new Iterator<JsonValue>() { 978 Iterator<String> i = keys().iterator(); 979 980 @Override 981 public boolean hasNext() { 982 return i.hasNext(); 983 } 984 985 @Override 986 public JsonValue next() { 987 return get(i.next()); 988 } 989 990 @Override 991 public void remove() { 992 throw new UnsupportedOperationException(); 993 } 994 }; 995 } 996 } 997 998 /** 999 * Returns the set of keys for this JSON value's child values. If this value 1000 * is a {@code Map}, then the order of the resulting keys is the same as the 1001 * underlying Map implementation. If there are no child values, this method 1002 * returns an empty set. 1003 * 1004 * @return the set of keys for this JSON value's child values. 1005 */ 1006 public Set<String> keys() { 1007 if (isMap()) { 1008 return asMap().keySet(); 1009 } else if (isList()) { 1010 return new AbstractSet<String>() { 1011 final RangeSet range = new RangeSet(JsonValue.this.size()); // 0 through size-1 inclusive 1012 1013 @Override 1014 public boolean contains(final Object o) { 1015 boolean result = false; 1016 if (o instanceof String) { 1017 try { 1018 result = range.contains(Integer.valueOf((String) o)); 1019 } catch (final NumberFormatException nfe) { 1020 // ignore; yields false 1021 } 1022 } 1023 return result; 1024 } 1025 1026 @Override 1027 public Iterator<String> iterator() { 1028 return new Iterator<String>() { 1029 Iterator<Integer> i = range.iterator(); 1030 1031 @Override 1032 public boolean hasNext() { 1033 return i.hasNext(); 1034 } 1035 1036 @Override 1037 public String next() { 1038 return i.next().toString(); 1039 } 1040 1041 @Override 1042 public void remove() { 1043 throw new UnsupportedOperationException(); 1044 } 1045 }; 1046 } 1047 1048 @Override 1049 public int size() { 1050 return range.size(); 1051 } 1052 }; 1053 } else { 1054 return Collections.emptySet(); 1055 } 1056 } 1057 1058 /** 1059 * Sets the value of the specified child list element. 1060 * 1061 * @param index 1062 * the {@code List} index identifying the child value to set. 1063 * @param object 1064 * the Java value to assign to the list element. 1065 * @return this JSON value. 1066 * @throws JsonValueException 1067 * if this JSON value is not a {@code List} or index is out of 1068 * range. 1069 */ 1070 public JsonValue put(final int index, final Object object) { 1071 final List<Object> list = required().asList(); 1072 if (index < 0 || index > list.size()) { 1073 throw new JsonValueException(this, "List index out of range: " + index); 1074 } else if (index == list.size()) { // appending to end of list 1075 list.add(unwrap(object)); 1076 } else { // replacing existing element 1077 list.set(index, unwrap(object)); 1078 } 1079 return this; 1080 } 1081 1082 /** 1083 * Sets the value identified by the specified pointer, relative to this 1084 * value as root. If doing so would require the creation of a new object or 1085 * list, a {@code JsonValueException} will be thrown. 1086 * <p> 1087 * NOTE: values may be added to a list using the reserved JSON pointer token 1088 * "-". For example, the pointer "/a/b/-" will add a new element to the list 1089 * referenced by "/a/b". 1090 * 1091 * @param pointer 1092 * identifies the child value to set. 1093 * @param object 1094 * the Java object value to set. 1095 * @return this JSON value. 1096 * @throws JsonValueException 1097 * if the specified pointer is invalid. 1098 */ 1099 public JsonValue put(final JsonPointer pointer, final Object object) { 1100 navigateToParentOf(pointer).required().putToken(pointer.leaf(), object); 1101 return this; 1102 } 1103 1104 /** 1105 * Sets the value of the specified member. 1106 * <p> 1107 * If setting a list element, the specified key must be parseable as an 1108 * unsigned base-10 integer and be less than or equal to the size of the 1109 * list. 1110 * 1111 * @param key 1112 * the {@code Map} key or {@code List} index identifying the 1113 * child value to set. 1114 * @param object 1115 * the object value to assign to the member. 1116 * @return this JSON value. 1117 * @throws JsonValueException 1118 * if this JSON value is not a {@code Map} or {@code List}. 1119 * @throws NullPointerException 1120 * if {@code key} is {@code null}. 1121 */ 1122 public JsonValue put(final String key, final Object object) { 1123 if (key == null) { 1124 throw new NullPointerException(); 1125 } else if (isMap()) { 1126 asMap().put(key, unwrap(object)); 1127 } else if (isList()) { 1128 put(toIndex(key), object); 1129 } else { 1130 throw new JsonValueException(this, "Expecting a Map or List"); 1131 } 1132 return this; 1133 } 1134 1135 /** 1136 * Sets the value identified by the specified pointer, relative to this 1137 * value as root. Missing parent objects or lists will be created on demand. 1138 * <p> 1139 * NOTE: values may be added to a list using the reserved JSON pointer token 1140 * "-". For example, the pointer "/a/b/-" will add a new element to the list 1141 * referenced by "/a/b". 1142 * 1143 * @param pointer 1144 * identifies the child value to set. 1145 * @param object 1146 * the Java object value to set. 1147 * @return this JSON value. 1148 * @throws JsonValueException 1149 * if the specified pointer is invalid. 1150 */ 1151 public JsonValue putPermissive(final JsonPointer pointer, final Object object) { 1152 navigateToParentOfPermissive(pointer).putToken(pointer.leaf(), object); 1153 return this; 1154 } 1155 1156 /** 1157 * Removes the specified child value, shifting any subsequent elements to 1158 * the left. If the JSON value is not a {@code List}, calling this method 1159 * has no effect. 1160 * 1161 * @param index 1162 * the {@code List} index identifying the child value to remove. 1163 */ 1164 public void remove(final int index) { 1165 if (index >= 0 && isList()) { 1166 final List<Object> list = asList(); 1167 if (index < list.size()) { 1168 list.remove(index); 1169 } 1170 } 1171 } 1172 1173 /** 1174 * Removes the specified child value with a pointer, relative to this value 1175 * as root. If the specified child value is not defined, calling this method 1176 * has no effect. 1177 * 1178 * @param pointer 1179 * the JSON pointer identifying the child value to remove. 1180 */ 1181 public void remove(final JsonPointer pointer) { 1182 navigateToParentOf(pointer).remove(pointer.leaf()); 1183 } 1184 1185 /** 1186 * Removes the specified child value. If the specified child value is not 1187 * defined, calling this method has no effect. 1188 * 1189 * @param key 1190 * the {@code Map} key or {@code List} index identifying the 1191 * child value to remove. 1192 */ 1193 public void remove(final String key) { 1194 if (isMap()) { 1195 asMap().remove(key); 1196 } else if (isList()) { 1197 remove(toIndex(key)); 1198 } 1199 } 1200 1201 /** 1202 * Throws a {@code JsonValueException} if the JSON value is {@code null}. 1203 * 1204 * @return this JSON value. 1205 * @throws JsonValueException 1206 * if the JSON value is {@code null}. 1207 */ 1208 public JsonValue required() { 1209 if (object == null) { 1210 throw new JsonValueException(this, "Expecting a value"); 1211 } 1212 return this; 1213 } 1214 1215 /** 1216 * Sets the Java object representing this JSON value. 1217 * <p> 1218 * This method will automatically unwrap {@link JsonValue} objects. 1219 * 1220 * @param object 1221 * the object to set. 1222 */ 1223 public void setObject(final Object object) { 1224 this.object = object; 1225 if (object instanceof JsonValue) { 1226 final JsonValue jv = (JsonValue) object; 1227 this.object = jv.object; 1228 } 1229 } 1230 1231 /** 1232 * Returns the number of values that this JSON value contains. 1233 * 1234 * @return the number of values that this JSON value contains. 1235 */ 1236 public int size() { 1237 if (isMap()) { 1238 return asMap().size(); 1239 } else if (isCollection()) { 1240 return asCollection().size(); 1241 } else { 1242 return 0; 1243 } 1244 } 1245 1246 /** 1247 * Returns a string representation of the JSON value. The result 1248 * resembles—but is not guaranteed to conform to—JSON syntax. This method 1249 * does not apply transformations to the value's children. 1250 * 1251 * @return a string representation of the JSON value. 1252 */ 1253 @SuppressWarnings("unchecked") 1254 @Override 1255 public String toString() { 1256 final StringBuilder sb = new StringBuilder(); 1257 if (isNull()) { 1258 sb.append("null"); 1259 } else if (isMap()) { 1260 sb.append("{ "); 1261 final Map<Object, Object> map = (Map<Object, Object>) object; 1262 for (final Iterator<Object> i = map.keySet().iterator(); i.hasNext();) { 1263 final Object key = i.next(); 1264 sb.append('"'); 1265 appendEscapedString(sb, key.toString()); 1266 sb.append("\": "); 1267 sb.append(new JsonValue(map.get(key)).toString()); // recursion 1268 if (i.hasNext()) { 1269 sb.append(", "); 1270 } 1271 } 1272 sb.append(" }"); 1273 } else if (isCollection()) { 1274 sb.append("[ "); 1275 for (final Iterator<Object> i = ((Collection<Object>) object).iterator(); i.hasNext();) { 1276 sb.append(new JsonValue(i.next()).toString()); // recursion 1277 if (i.hasNext()) { 1278 sb.append(", "); 1279 } 1280 } 1281 sb.append(" ]"); 1282 } else if (isString()) { 1283 sb.append('"'); 1284 appendEscapedString(sb, object.toString()); 1285 sb.append('"'); 1286 } else { 1287 sb.append(object.toString()); 1288 } 1289 return sb.toString(); 1290 } 1291 1292 /** 1293 * As per json.org a string is any Unicode character except " or \ or 1294 * control characters. Special characters will be escaped using a \ as 1295 * follows: 1296 * <ul> 1297 * <li> {@literal \ "} - double quote 1298 * <li> {@literal \ \} - back slash 1299 * <li> {@literal \ b} - backspace 1300 * <li> {@literal \ f} - form feed 1301 * <li> {@literal \ n} - new line 1302 * <li> {@literal \ r} - carriage return 1303 * <li> {@literal \ t} - tab 1304 * <li> {@literal \ u xxxx} - other control characters. 1305 * </ul> 1306 */ 1307 private static void appendEscapedString(final StringBuilder sb, final String s) { 1308 final int size = s.length(); 1309 for (int i = 0; i < size; i++) { 1310 final char c = s.charAt(i); 1311 switch (c) { 1312 // Escape characters which must be escaped. 1313 case '"': 1314 sb.append("\\\""); 1315 break; 1316 case '\\': 1317 sb.append("\\\\"); 1318 break; 1319 // Escape common controls to the C equivalent to make them easier to read. 1320 case '\b': 1321 sb.append("\\b"); 1322 break; 1323 case '\f': 1324 sb.append("\\f"); 1325 break; 1326 case '\n': 1327 sb.append("\\n"); 1328 break; 1329 case '\r': 1330 sb.append("\\r"); 1331 break; 1332 case '\t': 1333 sb.append("\\t"); 1334 break; 1335 default: 1336 if (Character.isISOControl(c)) { 1337 final String hex = Integer.toHexString(c).toUpperCase(Locale.ENGLISH); 1338 final int hexPadding = 4 - hex.length(); 1339 sb.append("\\u"); 1340 for (int j = 0; j < hexPadding; j++) { 1341 sb.append('0'); 1342 } 1343 sb.append(hex); 1344 } else { 1345 sb.append(c); 1346 } 1347 } 1348 } 1349 } 1350 1351 private void addToken(final String token, final Object object) { 1352 if (isEndOfListToken(token) && isList()) { 1353 add(object); 1354 } else { 1355 add(token, object); 1356 } 1357 } 1358 1359 private boolean isEndOfListToken(final String token) { 1360 return token.equals("-"); 1361 } 1362 1363 private boolean isIndexToken(final String token) { 1364 if (token.isEmpty()) { 1365 return false; 1366 } else { 1367 for (int i = 0; i < token.length(); i++) { 1368 final char c = token.charAt(i); 1369 if (!Character.isDigit(c)) { 1370 return false; 1371 } 1372 } 1373 return true; 1374 } 1375 } 1376 1377 private JsonValue navigateToParentOf(final JsonPointer pointer) { 1378 JsonValue jv = this; 1379 final int size = pointer.size(); 1380 for (int n = 0; n < size - 1; n++) { 1381 jv = jv.get(pointer.get(n)); 1382 if (jv.isNull()) { 1383 break; 1384 } 1385 } 1386 return jv; 1387 } 1388 1389 private JsonValue navigateToParentOfPermissive(final JsonPointer pointer) { 1390 JsonValue jv = this; 1391 final int size = pointer.size(); 1392 for (int n = 0; n < size - 1; n++) { 1393 final String token = pointer.get(n); 1394 final JsonValue next = jv.get(token); 1395 if (next.isNotNull()) { 1396 jv = next; 1397 } else if (isIndexToken(token)) { 1398 throw new JsonValueException(this, "Expecting a value"); 1399 } else { 1400 // Create the field based on the type of the next token. 1401 final String nextToken = pointer.get(n + 1); 1402 if (isEndOfListToken(nextToken)) { 1403 jv.add(token, new ArrayList<>()); 1404 jv = jv.get(token); 1405 } else if (isIndexToken(nextToken)) { 1406 throw new JsonValueException(this, "Expecting a value"); 1407 } else { 1408 jv.putPermissive(new JsonPointer(token), new LinkedHashMap<>()); 1409 jv = jv.get(token); 1410 } 1411 } 1412 } 1413 return jv; 1414 } 1415 1416 private void putToken(final String token, final Object object) { 1417 if (isEndOfListToken(token) && isList()) { 1418 add(object); 1419 } else { 1420 put(token, object); 1421 } 1422 } 1423 1424 /** 1425 * Performs a deep comparison of this JSON value with another JSON value, and returns whether the two objects 1426 * are identical. Fails fast in that a {@code false} is returned as soon as a difference is detected. 1427 * <p> 1428 * <b>Note:</b> Only values recognisable as JSON primitives ({@link Map}, {@link List}, {@link Number}, 1429 * {@link Boolean}, {@link String} and {@code null}) are supported. 1430 * </p> 1431 * @param other another value. 1432 * @return whether the two objects are equal. 1433 * @throws NullPointerException if {@code other} is {@code null}. 1434 * @throws IllegalArgumentException if this or the {@code other} value contains non-JSON primitive values. 1435 */ 1436 public boolean isEqualTo(JsonValue other) { 1437 return JsonPatch.isEqual(this, other); 1438 } 1439 1440 /** 1441 * Performs a deep comparison of this JSON vlaue with another JSON value, and produces a 1442 * JSON Patch value, which contains the operations necessary to modify the current value 1443 * to arrive at the {@code target} value. 1444 * 1445 * @param target the intended target value. 1446 * @return the resulting JSON Patch value. 1447 * @throws NullPointerException if either of {@code original} or {@code target} are {@code null}. 1448 */ 1449 public JsonValue diff(JsonValue target) { 1450 return JsonPatch.diff(this, target); 1451 } 1452 1453 /** 1454 * Applies a set of modifications in a JSON patch value to the current object, resulting 1455 * in the intended target value. In the event of a failure, this method does not revert 1456 * any modifications applied up to the point of failure. 1457 * 1458 * @param patch the JSON Patch value, specifying the modifications to apply to the original value. 1459 * @throws JsonValueException if application of the patch failed. 1460 */ 1461 public void patch(JsonValue patch) { 1462 JsonPatch.patch(this, patch); 1463 } 1464 }