001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.jose.util; 019 020 021import java.net.URI; 022import java.net.URISyntaxException; 023import java.text.ParseException; 024import java.util.Arrays; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028 029import net.minidev.json.JSONObject; 030import net.minidev.json.parser.JSONParser; 031 032 033/** 034 * JSON object helper methods. 035 * 036 * @author Vladimir Dzhuvinov 037 * @version 2021-10-08 038 */ 039public class JSONObjectUtils { 040 041 042 /** 043 * Parses a JSON object. 044 * 045 * <p>Specific JSON to Java entity mapping (as per JSON Smart): 046 * 047 * <ul> 048 * <li>JSON true|false map to {@code java.lang.Boolean}. 049 * <li>JSON numbers map to {@code java.lang.Number}. 050 * <ul> 051 * <li>JSON integer numbers map to {@code long}. 052 * <li>JSON fraction numbers map to {@code double}. 053 * </ul> 054 * <li>JSON strings map to {@code java.lang.String}. 055 * <li>JSON arrays map to {@code java.util.List<Object>}. 056 * <li>JSON objects map to {@code java.util.Map<String,Object>}. 057 * </ul> 058 * 059 * @param s The JSON object string to parse. Must not be {@code null}. 060 * 061 * @return The JSON object. 062 * 063 * @throws ParseException If the string cannot be parsed to a valid JSON 064 * object. 065 */ 066 public static Map<String, Object> parse(final String s) 067 throws ParseException { 068 069 return parse(s, -1); 070 } 071 072 073 /** 074 * Parses a JSON object with the option to limit the input string size. 075 * 076 * <p>Specific JSON to Java entity mapping (as per JSON Smart): 077 * 078 * <ul> 079 * <li>JSON true|false map to {@code java.lang.Boolean}. 080 * <li>JSON numbers map to {@code java.lang.Number}. 081 * <ul> 082 * <li>JSON integer numbers map to {@code long}. 083 * <li>JSON fraction numbers map to {@code double}. 084 * </ul> 085 * <li>JSON strings map to {@code java.lang.String}. 086 * <li>JSON arrays map to {@code java.util.List<Object>}. 087 * <li>JSON objects map to {@code java.util.Map<String,Object>}. 088 * </ul> 089 * 090 * @param s The JSON object string to parse. Must not be 091 * {@code null}. 092 * @param sizeLimit The max allowed size of the string to parse. A 093 * negative integer means no limit. 094 * 095 * @return The JSON object. 096 * 097 * @throws ParseException If the string cannot be parsed to a valid JSON 098 * object. 099 */ 100 public static Map<String, Object> parse(final String s, final int sizeLimit) 101 throws ParseException { 102 103 if (sizeLimit >= 0 && s.length() > sizeLimit) { 104 throw new ParseException("The parsed string is longer than the max accepted size of " + sizeLimit + " characters", 0); 105 } 106 107 Object o; 108 try { 109 o = new JSONParser(JSONParser.USE_HI_PRECISION_FLOAT | JSONParser.ACCEPT_TAILLING_SPACE).parse(s); 110 } catch (net.minidev.json.parser.ParseException e) { 111 throw new ParseException("Invalid JSON: " + e.getMessage(), 0); 112 } catch (Exception e) { 113 throw new ParseException("Unexpected exception: " + e.getMessage(), 0); 114 } catch (StackOverflowError e) { 115 throw new ParseException("Excessive JSON object and / or array nesting", 0); 116 } 117 118 if (o instanceof JSONObject) { 119 return (JSONObject)o; 120 } else { 121 throw new ParseException("JSON entity is not an object", 0); 122 } 123 } 124 125 126 /** 127 * Use {@link #parse(String)} instead. 128 * 129 * @param s The JSON object string to parse. Must not be {@code null}. 130 * 131 * @return The JSON object. 132 * 133 * @throws ParseException If the string cannot be parsed to a valid JSON 134 * object. 135 */ 136 @Deprecated 137 public static Map<String, Object> parseJSONObject(final String s) 138 throws ParseException { 139 140 return parse(s); 141 } 142 143 144 /** 145 * Gets a generic member of a JSON object. 146 * 147 * @param o The JSON object. Must not be {@code null}. 148 * @param key The JSON object member key. Must not be {@code null}. 149 * @param clazz The expected class of the JSON object member value. Must 150 * not be {@code null}. 151 * 152 * @return The JSON object member value, may be {@code null}. 153 * 154 * @throws ParseException If the value is not of the expected type. 155 */ 156 private static <T> T getGeneric(final Map<String, Object> o, final String key, final Class<T> clazz) 157 throws ParseException { 158 159 if (o.get(key) == null) { 160 return null; 161 } 162 163 Object value = o.get(key); 164 165 if (! clazz.isAssignableFrom(value.getClass())) { 166 throw new ParseException("Unexpected type of JSON object member with key " + key + "", 0); 167 } 168 169 @SuppressWarnings("unchecked") 170 T castValue = (T)value; 171 return castValue; 172 } 173 174 175 /** 176 * Gets a boolean member of a JSON object. 177 * 178 * @param o The JSON object. Must not be {@code null}. 179 * @param key The JSON object member key. Must not be {@code null}. 180 * 181 * @return The JSON object member value. 182 * 183 * @throws ParseException If the member is missing, the value is 184 * {@code null} or not of the expected type. 185 */ 186 public static boolean getBoolean(final Map<String, Object> o, final String key) 187 throws ParseException { 188 189 Boolean value = getGeneric(o, key, Boolean.class); 190 191 if (value == null) { 192 throw new ParseException("JSON object member with key " + key + " is missing or null", 0); 193 } 194 195 return value; 196 } 197 198 199 /** 200 * Gets an number member of a JSON object as {@code int}. 201 * 202 * @param o The JSON object. Must not be {@code null}. 203 * @param key The JSON object member key. Must not be {@code null}. 204 * 205 * @return The JSON object member value. 206 * 207 * @throws ParseException If the member is missing, the value is 208 * {@code null} or not of the expected type. 209 */ 210 public static int getInt(final Map<String, Object> o, final String key) 211 throws ParseException { 212 213 Number value = getGeneric(o, key, Number.class); 214 215 if (value == null) { 216 throw new ParseException("JSON object member with key " + key + " is missing or null", 0); 217 } 218 219 return value.intValue(); 220 } 221 222 223 /** 224 * Gets a number member of a JSON object as {@code long}. 225 * 226 * @param o The JSON object. Must not be {@code null}. 227 * @param key The JSON object member key. Must not be {@code null}. 228 * 229 * @return The JSON object member value. 230 * 231 * @throws ParseException If the member is missing, the value is 232 * {@code null} or not of the expected type. 233 */ 234 public static long getLong(final Map<String, Object> o, final String key) 235 throws ParseException { 236 237 Number value = getGeneric(o, key, Number.class); 238 239 if (value == null) { 240 throw new ParseException("JSON object member with key " + key + " is missing or null", 0); 241 } 242 243 return value.longValue(); 244 } 245 246 247 /** 248 * Gets a number member of a JSON object {@code float}. 249 * 250 * @param o The JSON object. Must not be {@code null}. 251 * @param key The JSON object member key. Must not be {@code null}. 252 * 253 * @return The JSON object member value, may be {@code null}. 254 * 255 * @throws ParseException If the member is missing, the value is 256 * {@code null} or not of the expected type. 257 */ 258 public static float getFloat(final Map<String, Object> o, final String key) 259 throws ParseException { 260 261 Number value = getGeneric(o, key, Number.class); 262 263 if (value == null) { 264 throw new ParseException("JSON object member with key " + key + " is missing or null", 0); 265 } 266 267 return value.floatValue(); 268 } 269 270 271 /** 272 * Gets a number member of a JSON object as {@code double}. 273 * 274 * @param o The JSON object. Must not be {@code null}. 275 * @param key The JSON object member key. Must not be {@code null}. 276 * 277 * @return The JSON object member value, may be {@code null}. 278 * 279 * @throws ParseException If the member is missing, the value is 280 * {@code null} or not of the expected type. 281 */ 282 public static double getDouble(final Map<String, Object> o, final String key) 283 throws ParseException { 284 285 Number value = getGeneric(o, key, Number.class); 286 287 if (value == null) { 288 throw new ParseException("JSON object member with key " + key + " is missing or null", 0); 289 } 290 291 return value.doubleValue(); 292 } 293 294 295 /** 296 * Gets a string member of a JSON object. 297 * 298 * @param o The JSON object. Must not be {@code null}. 299 * @param key The JSON object member key. Must not be {@code null}. 300 * 301 * @return The JSON object member value, may be {@code null}. 302 * 303 * @throws ParseException If the value is not of the expected type. 304 */ 305 public static String getString(final Map<String, Object> o, final String key) 306 throws ParseException { 307 308 return getGeneric(o, key, String.class); 309 } 310 311 312 /** 313 * Gets a string member of a JSON object as {@code java.net.URI}. 314 * 315 * @param o The JSON object. Must not be {@code null}. 316 * @param key The JSON object member key. Must not be {@code null}. 317 * 318 * @return The JSON object member value, may be {@code null}. 319 * 320 * @throws ParseException If the value is not of the expected type. 321 */ 322 public static URI getURI(final Map<String, Object> o, final String key) 323 throws ParseException { 324 325 String value = getString(o, key); 326 327 if (value == null) { 328 return null; 329 } 330 331 try { 332 return new URI(value); 333 334 } catch (URISyntaxException e) { 335 336 throw new ParseException(e.getMessage(), 0); 337 } 338 } 339 340 341 /** 342 * Gets a JSON array member of a JSON object. 343 * 344 * @param o The JSON object. Must not be {@code null}. 345 * @param key The JSON object member key. Must not be {@code null}. 346 * 347 * @return The JSON object member value, may be {@code null}. 348 * 349 * @throws ParseException If the value is not of the expected type. 350 */ 351 public static List<Object> getJSONArray(final Map<String, Object> o, final String key) 352 throws ParseException { 353 354 @SuppressWarnings("unchecked") 355 List<Object> jsonArray = getGeneric(o, key, List.class); 356 return jsonArray; 357 } 358 359 360 /** 361 * Gets a string array member of a JSON object. 362 * 363 * @param o The JSON object. Must not be {@code null}. 364 * @param key The JSON object member key. Must not be {@code null}. 365 * 366 * @return The JSON object member value, may be {@code null}. 367 * 368 * @throws ParseException If the value is not of the expected type. 369 */ 370 public static String[] getStringArray(final Map<String, Object> o, final String key) 371 throws ParseException { 372 373 List<Object> jsonArray = getJSONArray(o, key); 374 375 if (jsonArray == null) { 376 return null; 377 } 378 379 try { 380 return jsonArray.toArray(new String[0]); 381 } catch (ArrayStoreException e) { 382 throw new ParseException("JSON object member with key \"" + key + "\" is not an array of strings", 0); 383 } 384 } 385 386 /** 387 * Gets a JSON objects array member of a JSON object. 388 * 389 * @param o The JSON object. Must not be {@code null}. 390 * @param key The JSON object member key. Must not be {@code null}. 391 * 392 * @return The JSON object member value, may be {@code null}. 393 * 394 * @throws ParseException If the value is not of the expected type. 395 */ 396 public static Map<String, Object>[] getJSONObjectArray(final Map<String, Object> o, final String key) 397 throws ParseException { 398 399 List<Object> jsonArray = getJSONArray(o, key); 400 401 if (jsonArray == null) { 402 return null; 403 } 404 405 try { 406 return jsonArray.toArray(new HashMap[0]); 407 } catch (ArrayStoreException e) { 408 throw new ParseException("JSON object member with key \"" + key + "\" is not an array of JSON objects", 0); 409 } 410 } 411 412 /** 413 * Gets a string list member of a JSON object 414 * 415 * @param o The JSON object. Must not be {@code null}. 416 * @param key The JSON object member key. Must not be {@code null}. 417 * 418 * @return The JSON object member value, may be {@code null}. 419 * 420 * @throws ParseException If the value is not of the expected type. 421 */ 422 public static List<String> getStringList(final Map<String, Object> o, final String key) throws ParseException { 423 424 String[] array = getStringArray(o, key); 425 426 if (array == null) { 427 return null; 428 } 429 430 return Arrays.asList(array); 431 } 432 433 434 /** 435 * Gets a JSON object member of a JSON object. 436 * 437 * @param o The JSON object. Must not be {@code null}. 438 * @param key The JSON object member key. Must not be {@code null}. 439 * 440 * @return The JSON object member value, may be {@code null}. 441 * 442 * @throws ParseException If the value is not of the expected type. 443 */ 444 public static Map<String, Object> getJSONObject(final Map<String, Object> o, final String key) 445 throws ParseException { 446 447 Map<?,?> jsonObject = getGeneric(o, key, Map.class); 448 449 if (jsonObject == null) { 450 return null; 451 } 452 453 // Verify keys are String 454 for (Object oKey: jsonObject.keySet()) { 455 if (! (oKey instanceof String)) { 456 throw new ParseException("JSON object member with key " + key + " not a JSON object", 0); 457 } 458 } 459 @SuppressWarnings("unchecked") 460 Map<String, Object> castJSONObject = (Map<String, Object>)jsonObject; 461 return castJSONObject; 462 } 463 464 465 /** 466 * Gets a string member of a JSON object as {@link Base64URL}. 467 * 468 * @param o The JSON object. Must not be {@code null}. 469 * @param key The JSON object member key. Must not be {@code null}. 470 * 471 * @return The JSON object member value, may be {@code null}. 472 * 473 * @throws ParseException If the value is not of the expected type. 474 */ 475 public static Base64URL getBase64URL(final Map<String, Object> o, final String key) 476 throws ParseException { 477 478 String value = getString(o, key); 479 480 if (value == null) { 481 return null; 482 } 483 484 return new Base64URL(value); 485 } 486 487 488 /** 489 * Serialises the specified map to a JSON object using the entity 490 * mapping specified in {@link #parse(String)}. 491 * 492 * @param o The map. Must not be {@code null}. 493 * 494 * @return The JSON object as string. 495 */ 496 public static String toJSONString(final Map<String, ?> o) { 497 return JSONObject.toJSONString(o); 498 } 499 500 /** 501 * Creates a new JSON object (unordered). 502 * 503 * @return The new empty JSON object. 504 */ 505 public static Map<String, Object> newJSONObject() { 506 return new HashMap<>(); 507 } 508 509 510 /** 511 * Prevents public instantiation. 512 */ 513 private JSONObjectUtils() { } 514} 515