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-06-04 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 } 115 116 if (o instanceof JSONObject) { 117 return (JSONObject)o; 118 } else { 119 throw new ParseException("JSON entity is not an object", 0); 120 } 121 } 122 123 124 /** 125 * Use {@link #parse(String)} instead. 126 * 127 * @param s The JSON object string to parse. Must not be {@code null}. 128 * 129 * @return The JSON object. 130 * 131 * @throws ParseException If the string cannot be parsed to a valid JSON 132 * object. 133 */ 134 @Deprecated 135 public static Map<String, Object> parseJSONObject(final String s) 136 throws ParseException { 137 138 return parse(s); 139 } 140 141 142 /** 143 * Gets a generic member of a JSON object. 144 * 145 * @param o The JSON object. Must not be {@code null}. 146 * @param key The JSON object member key. Must not be {@code null}. 147 * @param clazz The expected class of the JSON object member value. Must 148 * not be {@code null}. 149 * 150 * @return The JSON object member value, may be {@code null}. 151 * 152 * @throws ParseException If the value is not of the expected type. 153 */ 154 @SuppressWarnings("unchecked") 155 private static <T> T getGeneric(final Map<String, Object> o, final String key, final Class<T> clazz) 156 throws ParseException { 157 158 if (o.get(key) == null) { 159 return null; 160 } 161 162 Object value = o.get(key); 163 164 if (! clazz.isAssignableFrom(value.getClass())) { 165 throw new ParseException("Unexpected type of JSON object member with key \"" + key + "\"", 0); 166 } 167 168 return (T)value; 169 } 170 171 172 /** 173 * Gets a boolean member of a JSON object. 174 * 175 * @param o The JSON object. Must not be {@code null}. 176 * @param key The JSON object member key. Must not be {@code null}. 177 * 178 * @return The JSON object member value. 179 * 180 * @throws ParseException If the member is missing, the value is 181 * {@code null} or not of the expected type. 182 */ 183 public static boolean getBoolean(final Map<String, Object> o, final String key) 184 throws ParseException { 185 186 Boolean value = getGeneric(o, key, Boolean.class); 187 188 if (value == null) { 189 throw new ParseException("JSON object member with key \"" + key + "\" is missing or null", 0); 190 } 191 192 return value; 193 } 194 195 196 /** 197 * Gets an number member of a JSON object as {@code int}. 198 * 199 * @param o The JSON object. Must not be {@code null}. 200 * @param key The JSON object member key. Must not be {@code null}. 201 * 202 * @return The JSON object member value. 203 * 204 * @throws ParseException If the member is missing, the value is 205 * {@code null} or not of the expected type. 206 */ 207 public static int getInt(final Map<String, Object> o, final String key) 208 throws ParseException { 209 210 Number value = getGeneric(o, key, Number.class); 211 212 if (value == null) { 213 throw new ParseException("JSON object member with key \"" + key + "\" is missing or null", 0); 214 } 215 216 return value.intValue(); 217 } 218 219 220 /** 221 * Gets a number member of a JSON object as {@code long}. 222 * 223 * @param o The JSON object. Must not be {@code null}. 224 * @param key The JSON object member key. Must not be {@code null}. 225 * 226 * @return The JSON object member value. 227 * 228 * @throws ParseException If the member is missing, the value is 229 * {@code null} or not of the expected type. 230 */ 231 public static long getLong(final Map<String, Object> o, final String key) 232 throws ParseException { 233 234 Number value = getGeneric(o, key, Number.class); 235 236 if (value == null) { 237 throw new ParseException("JSON object member with key \"" + key + "\" is missing or null", 0); 238 } 239 240 return value.longValue(); 241 } 242 243 244 /** 245 * Gets a number member of a JSON object {@code float}. 246 * 247 * @param o The JSON object. Must not be {@code null}. 248 * @param key The JSON object member key. Must not be {@code null}. 249 * 250 * @return The JSON object member value, may be {@code null}. 251 * 252 * @throws ParseException If the member is missing, the value is 253 * {@code null} or not of the expected type. 254 */ 255 public static float getFloat(final Map<String, Object> o, final String key) 256 throws ParseException { 257 258 Number value = getGeneric(o, key, Number.class); 259 260 if (value == null) { 261 throw new ParseException("JSON object member with key \"" + key + "\" is missing or null", 0); 262 } 263 264 return value.floatValue(); 265 } 266 267 268 /** 269 * Gets a number member of a JSON object as {@code double}. 270 * 271 * @param o The JSON object. Must not be {@code null}. 272 * @param key The JSON object member key. Must not be {@code null}. 273 * 274 * @return The JSON object member value, may be {@code null}. 275 * 276 * @throws ParseException If the member is missing, the value is 277 * {@code null} or not of the expected type. 278 */ 279 public static double getDouble(final Map<String, Object> o, final String key) 280 throws ParseException { 281 282 Number value = getGeneric(o, key, Number.class); 283 284 if (value == null) { 285 throw new ParseException("JSON object member with key \"" + key + "\" is missing or null", 0); 286 } 287 288 return value.doubleValue(); 289 } 290 291 292 /** 293 * Gets a string member of a JSON object. 294 * 295 * @param o The JSON object. Must not be {@code null}. 296 * @param key The JSON object member key. Must not be {@code null}. 297 * 298 * @return The JSON object member value, may be {@code null}. 299 * 300 * @throws ParseException If the value is not of the expected type. 301 */ 302 public static String getString(final Map<String, Object> o, final String key) 303 throws ParseException { 304 305 return getGeneric(o, key, String.class); 306 } 307 308 309 /** 310 * Gets a string member of a JSON object as {@code java.net.URI}. 311 * 312 * @param o The JSON object. Must not be {@code null}. 313 * @param key The JSON object member key. Must not be {@code null}. 314 * 315 * @return The JSON object member value, may be {@code null}. 316 * 317 * @throws ParseException If the value is not of the expected type. 318 */ 319 public static URI getURI(final Map<String, Object> o, final String key) 320 throws ParseException { 321 322 String value = getString(o, key); 323 324 if (value == null) { 325 return null; 326 } 327 328 try { 329 return new URI(value); 330 331 } catch (URISyntaxException e) { 332 333 throw new ParseException(e.getMessage(), 0); 334 } 335 } 336 337 338 /** 339 * Gets a JSON array member of a JSON object. 340 * 341 * @param o The JSON object. Must not be {@code null}. 342 * @param key The JSON object member key. Must not be {@code null}. 343 * 344 * @return The JSON object member value, may be {@code null}. 345 * 346 * @throws ParseException If the value is not of the expected type. 347 */ 348 public static List<Object> getJSONArray(final Map<String, Object> o, final String key) 349 throws ParseException { 350 351 return getGeneric(o, key, List.class); 352 } 353 354 355 /** 356 * Gets a string array member of a JSON object. 357 * 358 * @param o The JSON object. Must not be {@code null}. 359 * @param key The JSON object member key. Must not be {@code null}. 360 * 361 * @return The JSON object member value, may be {@code null}. 362 * 363 * @throws ParseException If the value is not of the expected type. 364 */ 365 public static String[] getStringArray(final Map<String, Object> o, final String key) 366 throws ParseException { 367 368 List<Object> jsonArray = getJSONArray(o, key); 369 370 if (jsonArray == null) { 371 return null; 372 } 373 374 try { 375 return jsonArray.toArray(new String[0]); 376 377 } catch (ArrayStoreException e) { 378 379 throw new ParseException("JSON object member with key \"" + key + "\" is not an array of strings", 0); 380 } 381 } 382 383 384 /** 385 * Gets a string list member of a JSON object 386 * 387 * @param o The JSON object. Must not be {@code null}. 388 * @param key The JSON object member key. Must not be {@code null}. 389 * 390 * @return The JSON object member value, may be {@code null}. 391 * 392 * @throws ParseException If the value is not of the expected type. 393 */ 394 public static List<String> getStringList(final Map<String, Object> o, final String key) throws ParseException { 395 396 String[] array = getStringArray(o, key); 397 398 if (array == null) { 399 return null; 400 } 401 402 return Arrays.asList(array); 403 } 404 405 406 /** 407 * Gets a JSON object member of a JSON object. 408 * 409 * @param o The JSON object. Must not be {@code null}. 410 * @param key The JSON object member key. Must not be {@code null}. 411 * 412 * @return The JSON object member value, may be {@code null}. 413 * 414 * @throws ParseException If the value is not of the expected type. 415 */ 416 public static Map<String, Object> getJSONObject(final Map<String, Object> o, final String key) 417 throws ParseException { 418 419 return getGeneric(o, key, JSONObject.class); 420 } 421 422 423 /** 424 * Gets a string member of a JSON object as {@link Base64URL}. 425 * 426 * @param o The JSON object. Must not be {@code null}. 427 * @param key The JSON object member key. Must not be {@code null}. 428 * 429 * @return The JSON object member value, may be {@code null}. 430 * 431 * @throws ParseException If the value is not of the expected type. 432 */ 433 public static Base64URL getBase64URL(final Map<String, Object> o, final String key) 434 throws ParseException { 435 436 String value = getString(o, key); 437 438 if (value == null) { 439 return null; 440 } 441 442 return new Base64URL(value); 443 } 444 445 446 /** 447 * Serialises the specified map to a JSON object using the entity 448 * mapping specified in {@link #parse(String)}. 449 * 450 * @param o The map. Must not be {@code null}. 451 * 452 * @return The JSON object as string. 453 */ 454 public static String toJSONString(final Map<String, ?> o) { 455 return JSONObject.toJSONString(o); 456 } 457 458 459 /** 460 * Creates a new JSON object (unordered). 461 * 462 * @return The new empty JSON object. 463 */ 464 public static Map<String, Object> newJSONObject() { 465 return new HashMap<>(); 466 } 467 468 469 /** 470 * Prevents public instantiation. 471 */ 472 private JSONObjectUtils() { } 473} 474