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