1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.json; 18 19 import java.util.ArrayList; 20 import java.util.Collection; 21 import java.util.Iterator; 22 import java.util.LinkedHashMap; 23 import java.util.Map; 24 import java.util.Objects; 25 import java.util.Set; 26 27 // Note: this class was written without inspecting the non-free org.json sourcecode. 28 29 /** 30 * A modifiable set of name/value mappings. Names are unique, non-null strings. 31 * Values may be any mix of {@link JSONObject JSONObjects}, {@link JSONArray 32 * JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}. 33 * Values may not be {@code null}, {@link Double#isNaN() NaNs}, {@link 34 * Double#isInfinite() infinities}, or of any type not listed here. 35 * 36 * <p>This class can coerce values to another type when requested. 37 * <ul> 38 * <li>When the requested type is a boolean, strings will be coerced using a 39 * case-insensitive comparison to "true" and "false". 40 * <li>When the requested type is a double, other {@link Number} types will 41 * be coerced using {@link Number#doubleValue() doubleValue}. Strings 42 * that can be coerced using {@link Double#valueOf(String)} will be. 43 * <li>When the requested type is an int, other {@link Number} types will 44 * be coerced using {@link Number#intValue() intValue}. Strings 45 * that can be coerced using {@link Double#valueOf(String)} will be, 46 * and then cast to int. 47 * <li><a name="lossy">When the requested type is a long, other {@link Number} types will 48 * be coerced using {@link Number#longValue() longValue}. Strings 49 * that can be coerced using {@link Double#valueOf(String)} will be, 50 * and then cast to long. This two-step conversion is lossy for very 51 * large values. For example, the string "9223372036854775806" yields the 52 * long 9223372036854775807.</a> 53 * <li>When the requested type is a String, other non-null values will be 54 * coerced using {@link String#valueOf(Object)}. Although null cannot be 55 * coerced, the sentinel value {@link JSONObject#NULL} is coerced to the 56 * string "null". 57 * </ul> 58 * 59 * <p>This class can look up both mandatory and optional values: 60 * <ul> 61 * <li>Use <code>get<i>Type</i>()</code> to retrieve a mandatory value. This 62 * fails with a {@code JSONException} if the requested name has no value 63 * or if the value cannot be coerced to the requested type. 64 * <li>Use <code>opt<i>Type</i>()</code> to retrieve an optional value. This 65 * returns a system- or user-supplied default if the requested name has no 66 * value or if the value cannot be coerced to the requested type. 67 * </ul> 68 * 69 * <p><strong>Warning:</strong> this class represents null in two incompatible 70 * ways: the standard Java {@code null} reference, and the sentinel value {@link 71 * JSONObject#NULL}. In particular, calling {@code put(name, null)} removes the 72 * named entry from the object but {@code put(name, JSONObject.NULL)} stores an 73 * entry whose value is {@code JSONObject.NULL}. 74 * 75 * <p>Instances of this class are not thread safe. Although this class is 76 * nonfinal, it was not designed for inheritance and should not be subclassed. 77 * In particular, self-use by overrideable methods is not specified. See 78 * <i>Effective Java</i> Item 17, "Design and Document or inheritance or else 79 * prohibit it" for further information. 80 */ 81 public class JSONObject { 82 83 private static final Double NEGATIVE_ZERO = -0d; 84 85 /** 86 * A sentinel value used to explicitly define a name with no value. Unlike 87 * {@code null}, names with this value: 88 * <ul> 89 * <li>show up in the {@link #names} array 90 * <li>show up in the {@link #keys} iterator 91 * <li>return {@code true} for {@link #has(String)} 92 * <li>do not throw on {@link #get(String)} 93 * <li>are included in the encoded JSON string. 94 * </ul> 95 * 96 * <p>This value violates the general contract of {@link Object#equals} by 97 * returning true when compared to {@code null}. Its {@link #toString} 98 * method returns "null". 99 */ 100 public static final Object NULL = new Object() { 101 @Override public boolean equals(Object o) { 102 return o == this || o == null; // API specifies this broken equals implementation 103 } 104 // at least make the broken equals(null) consistent with Objects.hashCode(null). 105 @Override public int hashCode() { return Objects.hashCode(null); } 106 @Override public String toString() { 107 return "null"; 108 } 109 }; 110 111 private final LinkedHashMap<String, Object> nameValuePairs; 112 113 /** 114 * Creates a {@code JSONObject} with no name/value mappings. 115 */ 116 public JSONObject() { 117 nameValuePairs = new LinkedHashMap<String, Object>(); 118 } 119 120 /** 121 * Creates a new {@code JSONObject} by copying all name/value mappings from 122 * the given map. 123 * 124 * @param copyFrom a map whose keys are of type {@link String} and whose 125 * values are of supported types. 126 * @throws NullPointerException if any of the map's keys are null. 127 */ 128 /* (accept a raw type for API compatibility) */ 129 public JSONObject(Map copyFrom) { 130 this(); 131 Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom; 132 for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) { 133 /* 134 * Deviate from the original by checking that keys are non-null and 135 * of the proper type. (We still defer validating the values). 136 */ 137 String key = (String) entry.getKey(); 138 if (key == null) { 139 throw new NullPointerException("key == null"); 140 } 141 nameValuePairs.put(key, wrap(entry.getValue())); 142 } 143 } 144 145 /** 146 * Creates a new {@code JSONObject} with name/value mappings from the next 147 * object in the tokener. 148 * 149 * @param readFrom a tokener whose nextValue() method will yield a 150 * {@code JSONObject}. 151 * @throws JSONException if the parse fails or doesn't yield a 152 * {@code JSONObject}. 153 */ 154 public JSONObject(JSONTokener readFrom) throws JSONException { 155 /* 156 * Getting the parser to populate this could get tricky. Instead, just 157 * parse to temporary JSONObject and then steal the data from that. 158 */ 159 Object object = readFrom.nextValue(); 160 if (object instanceof JSONObject) { 161 this.nameValuePairs = ((JSONObject) object).nameValuePairs; 162 } else { 163 throw JSON.typeMismatch(object, "JSONObject"); 164 } 165 } 166 167 /** 168 * Creates a new {@code JSONObject} with name/value mappings from the JSON 169 * string. 170 * 171 * @param json a JSON-encoded string containing an object. 172 * @throws JSONException if the parse fails or doesn't yield a {@code 173 * JSONObject}. 174 */ 175 public JSONObject(String json) throws JSONException { 176 this(new JSONTokener(json)); 177 } 178 179 /** 180 * Creates a new {@code JSONObject} by copying mappings for the listed names 181 * from the given object. Names that aren't present in {@code copyFrom} will 182 * be skipped. 183 */ 184 public JSONObject(JSONObject copyFrom, String[] names) throws JSONException { 185 this(); 186 for (String name : names) { 187 Object value = copyFrom.opt(name); 188 if (value != null) { 189 nameValuePairs.put(name, value); 190 } 191 } 192 } 193 194 /** 195 * Returns the number of name/value mappings in this object. 196 */ 197 public int length() { 198 return nameValuePairs.size(); 199 } 200 201 /** 202 * Maps {@code name} to {@code value}, clobbering any existing name/value 203 * mapping with the same name. 204 * 205 * @return this object. 206 */ 207 public JSONObject put(String name, boolean value) throws JSONException { 208 nameValuePairs.put(checkName(name), value); 209 return this; 210 } 211 212 /** 213 * Maps {@code name} to {@code value}, clobbering any existing name/value 214 * mapping with the same name. 215 * 216 * @param value a finite value. May not be {@link Double#isNaN() NaNs} or 217 * {@link Double#isInfinite() infinities}. 218 * @return this object. 219 */ 220 public JSONObject put(String name, double value) throws JSONException { 221 nameValuePairs.put(checkName(name), JSON.checkDouble(value)); 222 return this; 223 } 224 225 /** 226 * Maps {@code name} to {@code value}, clobbering any existing name/value 227 * mapping with the same name. 228 * 229 * @return this object. 230 */ 231 public JSONObject put(String name, int value) throws JSONException { 232 nameValuePairs.put(checkName(name), value); 233 return this; 234 } 235 236 /** 237 * Maps {@code name} to {@code value}, clobbering any existing name/value 238 * mapping with the same name. 239 * 240 * @return this object. 241 */ 242 public JSONObject put(String name, long value) throws JSONException { 243 nameValuePairs.put(checkName(name), value); 244 return this; 245 } 246 247 /** 248 * Maps {@code name} to {@code value}, clobbering any existing name/value 249 * mapping with the same name. If the value is {@code null}, any existing 250 * mapping for {@code name} is removed. 251 * 252 * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, 253 * Integer, Long, Double, {@link #NULL}, or {@code null}. May not be 254 * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() 255 * infinities}. 256 * @return this object. 257 */ 258 public JSONObject put(String name, Object value) throws JSONException { 259 if (value == null) { 260 nameValuePairs.remove(name); 261 return this; 262 } 263 if (value instanceof Number) { 264 // deviate from the original by checking all Numbers, not just floats & doubles 265 JSON.checkDouble(((Number) value).doubleValue()); 266 } 267 nameValuePairs.put(checkName(name), value); 268 return this; 269 } 270 271 /** 272 * Equivalent to {@code put(name, value)} when both parameters are non-null; 273 * does nothing otherwise. 274 */ 275 public JSONObject putOpt(String name, Object value) throws JSONException { 276 if (name == null || value == null) { 277 return this; 278 } 279 return put(name, value); 280 } 281 282 /** 283 * Appends {@code value} to the array already mapped to {@code name}. If 284 * this object has no mapping for {@code name}, this inserts a new mapping. 285 * If the mapping exists but its value is not an array, the existing 286 * and new values are inserted in order into a new array which is itself 287 * mapped to {@code name}. In aggregate, this allows values to be added to a 288 * mapping one at a time. 289 * 290 * <p> Note that {@code append(String, Object)} provides better semantics. 291 * In particular, the mapping for {@code name} will <b>always</b> be a 292 * {@link JSONArray}. Using {@code accumulate} will result in either a 293 * {@link JSONArray} or a mapping whose type is the type of {@code value} 294 * depending on the number of calls to it. 295 * 296 * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, 297 * Integer, Long, Double, {@link #NULL} or null. May not be {@link 298 * Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. 299 */ 300 // TODO: Change {@code append) to {@link #append} when append is 301 // unhidden. 302 public JSONObject accumulate(String name, Object value) throws JSONException { 303 Object current = nameValuePairs.get(checkName(name)); 304 if (current == null) { 305 return put(name, value); 306 } 307 308 if (current instanceof JSONArray) { 309 JSONArray array = (JSONArray) current; 310 array.checkedPut(value); 311 } else { 312 JSONArray array = new JSONArray(); 313 array.checkedPut(current); 314 array.checkedPut(value); 315 nameValuePairs.put(name, array); 316 } 317 return this; 318 } 319 320 /** 321 * Appends values to the array mapped to {@code name}. A new {@link JSONArray} 322 * mapping for {@code name} will be inserted if no mapping exists. If the existing 323 * mapping for {@code name} is not a {@link JSONArray}, a {@link JSONException} 324 * will be thrown. 325 * 326 * @throws JSONException if {@code name} is {@code null} or if the mapping for 327 * {@code name} is non-null and is not a {@link JSONArray}. 328 * 329 * @hide 330 */ 331 public JSONObject append(String name, Object value) throws JSONException { 332 Object current = nameValuePairs.get(checkName(name)); 333 334 final JSONArray array; 335 if (current instanceof JSONArray) { 336 array = (JSONArray) current; 337 } else if (current == null) { 338 JSONArray newArray = new JSONArray(); 339 nameValuePairs.put(name, newArray); 340 array = newArray; 341 } else { 342 throw new JSONException("Key " + name + " is not a JSONArray"); 343 } 344 345 array.checkedPut(value); 346 347 return this; 348 } 349 350 String checkName(String name) throws JSONException { 351 if (name == null) { 352 throw new JSONException("Names must be non-null"); 353 } 354 return name; 355 } 356 357 /** 358 * Removes the named mapping if it exists; does nothing otherwise. 359 * 360 * @return the value previously mapped by {@code name}, or null if there was 361 * no such mapping. 362 */ 363 public Object remove(String name) { 364 return nameValuePairs.remove(name); 365 } 366 367 /** 368 * Returns true if this object has no mapping for {@code name} or if it has 369 * a mapping whose value is {@link #NULL}. 370 */ 371 public boolean isNull(String name) { 372 Object value = nameValuePairs.get(name); 373 return value == null || value == NULL; 374 } 375 376 /** 377 * Returns true if this object has a mapping for {@code name}. The mapping 378 * may be {@link #NULL}. 379 */ 380 public boolean has(String name) { 381 return nameValuePairs.containsKey(name); 382 } 383 384 /** 385 * Returns the value mapped by {@code name}, or throws if no such mapping exists. 386 * 387 * @throws JSONException if no such mapping exists. 388 */ 389 public Object get(String name) throws JSONException { 390 Object result = nameValuePairs.get(name); 391 if (result == null) { 392 throw new JSONException("No value for " + name); 393 } 394 return result; 395 } 396 397 /** 398 * Returns the value mapped by {@code name}, or null if no such mapping 399 * exists. 400 */ 401 public Object opt(String name) { 402 return nameValuePairs.get(name); 403 } 404 405 /** 406 * Returns the value mapped by {@code name} if it exists and is a boolean or 407 * can be coerced to a boolean, or throws otherwise. 408 * 409 * @throws JSONException if the mapping doesn't exist or cannot be coerced 410 * to a boolean. 411 */ 412 public boolean getBoolean(String name) throws JSONException { 413 Object object = get(name); 414 Boolean result = JSON.toBoolean(object); 415 if (result == null) { 416 throw JSON.typeMismatch(name, object, "boolean"); 417 } 418 return result; 419 } 420 421 /** 422 * Returns the value mapped by {@code name} if it exists and is a boolean or 423 * can be coerced to a boolean, or false otherwise. 424 */ 425 public boolean optBoolean(String name) { 426 return optBoolean(name, false); 427 } 428 429 /** 430 * Returns the value mapped by {@code name} if it exists and is a boolean or 431 * can be coerced to a boolean, or {@code fallback} otherwise. 432 */ 433 public boolean optBoolean(String name, boolean fallback) { 434 Object object = opt(name); 435 Boolean result = JSON.toBoolean(object); 436 return result != null ? result : fallback; 437 } 438 439 /** 440 * Returns the value mapped by {@code name} if it exists and is a double or 441 * can be coerced to a double, or throws otherwise. 442 * 443 * @throws JSONException if the mapping doesn't exist or cannot be coerced 444 * to a double. 445 */ 446 public double getDouble(String name) throws JSONException { 447 Object object = get(name); 448 Double result = JSON.toDouble(object); 449 if (result == null) { 450 throw JSON.typeMismatch(name, object, "double"); 451 } 452 return result; 453 } 454 455 /** 456 * Returns the value mapped by {@code name} if it exists and is a double or 457 * can be coerced to a double, or {@code NaN} otherwise. 458 */ 459 public double optDouble(String name) { 460 return optDouble(name, Double.NaN); 461 } 462 463 /** 464 * Returns the value mapped by {@code name} if it exists and is a double or 465 * can be coerced to a double, or {@code fallback} otherwise. 466 */ 467 public double optDouble(String name, double fallback) { 468 Object object = opt(name); 469 Double result = JSON.toDouble(object); 470 return result != null ? result : fallback; 471 } 472 473 /** 474 * Returns the value mapped by {@code name} if it exists and is an int or 475 * can be coerced to an int, or throws otherwise. 476 * 477 * @throws JSONException if the mapping doesn't exist or cannot be coerced 478 * to an int. 479 */ 480 public int getInt(String name) throws JSONException { 481 Object object = get(name); 482 Integer result = JSON.toInteger(object); 483 if (result == null) { 484 throw JSON.typeMismatch(name, object, "int"); 485 } 486 return result; 487 } 488 489 /** 490 * Returns the value mapped by {@code name} if it exists and is an int or 491 * can be coerced to an int, or 0 otherwise. 492 */ 493 public int optInt(String name) { 494 return optInt(name, 0); 495 } 496 497 /** 498 * Returns the value mapped by {@code name} if it exists and is an int or 499 * can be coerced to an int, or {@code fallback} otherwise. 500 */ 501 public int optInt(String name, int fallback) { 502 Object object = opt(name); 503 Integer result = JSON.toInteger(object); 504 return result != null ? result : fallback; 505 } 506 507 /** 508 * Returns the value mapped by {@code name} if it exists and is a long or 509 * can be coerced to a long, or throws otherwise. 510 * Note that JSON represents numbers as doubles, 511 * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON. 512 * 513 * @throws JSONException if the mapping doesn't exist or cannot be coerced 514 * to a long. 515 */ 516 public long getLong(String name) throws JSONException { 517 Object object = get(name); 518 Long result = JSON.toLong(object); 519 if (result == null) { 520 throw JSON.typeMismatch(name, object, "long"); 521 } 522 return result; 523 } 524 525 /** 526 * Returns the value mapped by {@code name} if it exists and is a long or 527 * can be coerced to a long, or 0 otherwise. Note that JSON represents numbers as doubles, 528 * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON. 529 */ 530 public long optLong(String name) { 531 return optLong(name, 0L); 532 } 533 534 /** 535 * Returns the value mapped by {@code name} if it exists and is a long or 536 * can be coerced to a long, or {@code fallback} otherwise. Note that JSON represents 537 * numbers as doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer 538 * numbers via JSON. 539 */ 540 public long optLong(String name, long fallback) { 541 Object object = opt(name); 542 Long result = JSON.toLong(object); 543 return result != null ? result : fallback; 544 } 545 546 /** 547 * Returns the value mapped by {@code name} if it exists, coercing it if 548 * necessary, or throws if no such mapping exists. 549 * 550 * @throws JSONException if no such mapping exists. 551 */ 552 public String getString(String name) throws JSONException { 553 Object object = get(name); 554 String result = JSON.toString(object); 555 if (result == null) { 556 throw JSON.typeMismatch(name, object, "String"); 557 } 558 return result; 559 } 560 561 /** 562 * Returns the value mapped by {@code name} if it exists, coercing it if 563 * necessary, or the empty string if no such mapping exists. 564 */ 565 public String optString(String name) { 566 return optString(name, ""); 567 } 568 569 /** 570 * Returns the value mapped by {@code name} if it exists, coercing it if 571 * necessary, or {@code fallback} if no such mapping exists. 572 */ 573 public String optString(String name, String fallback) { 574 Object object = opt(name); 575 String result = JSON.toString(object); 576 return result != null ? result : fallback; 577 } 578 579 /** 580 * Returns the value mapped by {@code name} if it exists and is a {@code 581 * JSONArray}, or throws otherwise. 582 * 583 * @throws JSONException if the mapping doesn't exist or is not a {@code 584 * JSONArray}. 585 */ 586 public JSONArray getJSONArray(String name) throws JSONException { 587 Object object = get(name); 588 if (object instanceof JSONArray) { 589 return (JSONArray) object; 590 } else { 591 throw JSON.typeMismatch(name, object, "JSONArray"); 592 } 593 } 594 595 /** 596 * Returns the value mapped by {@code name} if it exists and is a {@code 597 * JSONArray}, or null otherwise. 598 */ 599 public JSONArray optJSONArray(String name) { 600 Object object = opt(name); 601 return object instanceof JSONArray ? (JSONArray) object : null; 602 } 603 604 /** 605 * Returns the value mapped by {@code name} if it exists and is a {@code 606 * JSONObject}, or throws otherwise. 607 * 608 * @throws JSONException if the mapping doesn't exist or is not a {@code 609 * JSONObject}. 610 */ 611 public JSONObject getJSONObject(String name) throws JSONException { 612 Object object = get(name); 613 if (object instanceof JSONObject) { 614 return (JSONObject) object; 615 } else { 616 throw JSON.typeMismatch(name, object, "JSONObject"); 617 } 618 } 619 620 /** 621 * Returns the value mapped by {@code name} if it exists and is a {@code 622 * JSONObject}, or null otherwise. 623 */ 624 public JSONObject optJSONObject(String name) { 625 Object object = opt(name); 626 return object instanceof JSONObject ? (JSONObject) object : null; 627 } 628 629 /** 630 * Returns an array with the values corresponding to {@code names}. The 631 * array contains null for names that aren't mapped. This method returns 632 * null if {@code names} is either null or empty. 633 */ 634 public JSONArray toJSONArray(JSONArray names) throws JSONException { 635 JSONArray result = new JSONArray(); 636 if (names == null) { 637 return null; 638 } 639 int length = names.length(); 640 if (length == 0) { 641 return null; 642 } 643 for (int i = 0; i < length; i++) { 644 String name = JSON.toString(names.opt(i)); 645 result.put(opt(name)); 646 } 647 return result; 648 } 649 650 /** 651 * Returns an iterator of the {@code String} names in this object. The 652 * returned iterator supports {@link Iterator#remove() remove}, which will 653 * remove the corresponding mapping from this object. If this object is 654 * modified after the iterator is returned, the iterator's behavior is 655 * undefined. The order of the keys is undefined. 656 */ 657 public Iterator<String> keys() { 658 return nameValuePairs.keySet().iterator(); 659 } 660 661 /** 662 * Returns the set of {@code String} names in this object. The returned set 663 * is a view of the keys in this object. {@link Set#remove(Object)} will remove 664 * the corresponding mapping from this object and set iterator behaviour 665 * is undefined if this object is modified after it is returned. 666 * 667 * See {@link #keys()}. 668 * 669 * @hide. 670 */ 671 public Set<String> keySet() { 672 return nameValuePairs.keySet(); 673 } 674 675 /** 676 * Returns an array containing the string names in this object. This method 677 * returns null if this object contains no mappings. 678 */ 679 public JSONArray names() { 680 return nameValuePairs.isEmpty() 681 ? null 682 : new JSONArray(new ArrayList<String>(nameValuePairs.keySet())); 683 } 684 685 /** 686 * Encodes this object as a compact JSON string, such as: 687 * <pre>{"query":"Pizza","locations":[94043,90210]}</pre> 688 */ 689 @Override public String toString() { 690 try { 691 JSONStringer stringer = new JSONStringer(); 692 writeTo(stringer); 693 return stringer.toString(); 694 } catch (JSONException e) { 695 return null; 696 } 697 } 698 699 /** 700 * Encodes this object as a human readable JSON string for debugging, such 701 * as: 702 * <pre> 703 * { 704 * "query": "Pizza", 705 * "locations": [ 706 * 94043, 707 * 90210 708 * ] 709 * }</pre> 710 * 711 * @param indentSpaces the number of spaces to indent for each level of 712 * nesting. 713 */ 714 public String toString(int indentSpaces) throws JSONException { 715 JSONStringer stringer = new JSONStringer(indentSpaces); 716 writeTo(stringer); 717 return stringer.toString(); 718 } 719 720 void writeTo(JSONStringer stringer) throws JSONException { 721 stringer.object(); 722 for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) { 723 stringer.key(entry.getKey()).value(entry.getValue()); 724 } 725 stringer.endObject(); 726 } 727 728 /** 729 * Encodes the number as a JSON string. 730 * 731 * @param number a finite value. May not be {@link Double#isNaN() NaNs} or 732 * {@link Double#isInfinite() infinities}. 733 */ 734 public static String numberToString(Number number) throws JSONException { 735 if (number == null) { 736 throw new JSONException("Number must be non-null"); 737 } 738 739 double doubleValue = number.doubleValue(); 740 JSON.checkDouble(doubleValue); 741 742 // the original returns "-0" instead of "-0.0" for negative zero 743 if (number.equals(NEGATIVE_ZERO)) { 744 return "-0"; 745 } 746 747 long longValue = number.longValue(); 748 if (doubleValue == (double) longValue) { 749 return Long.toString(longValue); 750 } 751 752 return number.toString(); 753 } 754 755 /** 756 * Encodes {@code data} as a JSON string. This applies quotes and any 757 * necessary character escaping. 758 * 759 * @param data the string to encode. Null will be interpreted as an empty 760 * string. 761 */ 762 public static String quote(String data) { 763 if (data == null) { 764 return "\"\""; 765 } 766 try { 767 JSONStringer stringer = new JSONStringer(); 768 stringer.open(JSONStringer.Scope.NULL, ""); 769 stringer.value(data); 770 stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); 771 return stringer.toString(); 772 } catch (JSONException e) { 773 throw new AssertionError(); 774 } 775 } 776 777 /** 778 * Wraps the given object if necessary. 779 * 780 * <p>If the object is null or , returns {@link #NULL}. 781 * If the object is a {@code JSONArray} or {@code JSONObject}, no wrapping is necessary. 782 * If the object is {@code NULL}, no wrapping is necessary. 783 * If the object is an array or {@code Collection}, returns an equivalent {@code JSONArray}. 784 * If the object is a {@code Map}, returns an equivalent {@code JSONObject}. 785 * If the object is a primitive wrapper type or {@code String}, returns the object. 786 * Otherwise if the object is from a {@code java} package, returns the result of {@code toString}. 787 * If wrapping fails, returns null. 788 */ 789 public static Object wrap(Object o) { 790 if (o == null) { 791 return NULL; 792 } 793 if (o instanceof JSONArray || o instanceof JSONObject) { 794 return o; 795 } 796 if (o.equals(NULL)) { 797 return o; 798 } 799 try { 800 if (o instanceof Collection) { 801 return new JSONArray((Collection) o); 802 } else if (o.getClass().isArray()) { 803 return new JSONArray(o); 804 } 805 if (o instanceof Map) { 806 return new JSONObject((Map) o); 807 } 808 if (o instanceof Boolean || 809 o instanceof Byte || 810 o instanceof Character || 811 o instanceof Double || 812 o instanceof Float || 813 o instanceof Integer || 814 o instanceof Long || 815 o instanceof Short || 816 o instanceof String) { 817 return o; 818 } 819 if (o.getClass().getPackage().getName().startsWith("java.")) { 820 return o.toString(); 821 } 822 } catch (Exception ignored) { 823 } 824 return null; 825 } 826 } 827