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.List;
026
027import net.minidev.json.JSONArray;
028import net.minidev.json.JSONObject;
029import net.minidev.json.parser.JSONParser;
030
031
032/**
033 * JSON object helper methods for parsing and typed retrieval of member values.
034 *
035 * @author Vladimir Dzhuvinov
036 * @version 2021-07-01
037 */
038public class JSONObjectUtils {
039
040
041        /**
042         * Parses a JSON object.
043         *
044         * <p>Specific JSON to Java entity mapping (as per JSON Smart):
045         *
046         * <ul>
047         *     <li>JSON true|false map to {@code java.lang.Boolean}.
048         *     <li>JSON numbers map to {@code java.lang.Number}.
049         *         <ul>
050         *             <li>JSON integer numbers map to {@code long}.
051         *             <li>JSON fraction numbers map to {@code double}.
052         *         </ul>
053         *     <li>JSON strings map to {@code java.lang.String}.
054         *     <li>JSON arrays map to {@code net.minidev.json.JSONArray}.
055         *     <li>JSON objects map to {@code net.minidev.json.JSONObject}.
056         * </ul>
057         *
058         * @param s The JSON object string to parse. Must not be {@code null}.
059         *
060         * @return The JSON object.
061         *
062         * @throws ParseException If the string cannot be parsed to a valid JSON 
063         *                        object.
064         */
065        public static JSONObject parse(final String s)
066                throws ParseException {
067
068                return parse(s, -1);
069        }
070
071
072        /**
073         * Parses a JSON object with the option to limit the input string size.
074         *
075         * <p>Specific JSON to Java entity mapping (as per JSON Smart):
076         *
077         * <ul>
078         *     <li>JSON true|false map to {@code java.lang.Boolean}.
079         *     <li>JSON numbers map to {@code java.lang.Number}.
080         *         <ul>
081         *             <li>JSON integer numbers map to {@code long}.
082         *             <li>JSON fraction numbers map to {@code double}.
083         *         </ul>
084         *     <li>JSON strings map to {@code java.lang.String}.
085         *     <li>JSON arrays map to {@code java.util.List<Object>}.
086         *     <li>JSON objects map to {@code java.util.Map<String,Object>}.
087         * </ul>
088         *
089         * @param s         The JSON object string to parse. Must not be
090         *                  {@code null}.
091         * @param sizeLimit The max allowed size of the string to parse. A
092         *                  negative integer means no limit.
093         *
094         * @return The JSON object.
095         *
096         * @throws ParseException If the string cannot be parsed to a valid JSON
097         *                        object.
098         */
099        public static JSONObject parse(final String s, final int sizeLimit)
100                throws ParseException {
101
102                if (sizeLimit >= 0 && s.length() > sizeLimit) {
103                        throw new ParseException("The parsed string is longer than the max accepted size of " + sizeLimit + " characters", 0);
104                }
105                
106                Object o;
107
108                try {
109                        o = new JSONParser(JSONParser.USE_HI_PRECISION_FLOAT | JSONParser.ACCEPT_TAILLING_SPACE).parse(s);
110
111                } catch (net.minidev.json.parser.ParseException e) {
112
113                        throw new ParseException("Invalid JSON: " + e.getMessage(), 0);
114                } catch (Exception e) {
115                        throw new ParseException("Unexpected exception: " + e.getMessage(), 0);
116                } catch (StackOverflowError e) {
117                        throw new ParseException("Excessive JSON object and / or array nesting", 0);
118                }
119
120                if (o instanceof JSONObject) {
121                        return (JSONObject)o;
122                } else {
123                        throw new ParseException("JSON entity is not an object", 0);
124                }
125        }
126
127
128        /**
129         * Use {@link #parse(String)} instead.
130         *
131         * @param s The JSON object string to parse. Must not be {@code null}.
132         *
133         * @return The JSON object.
134         *
135         * @throws ParseException If the string cannot be parsed to a valid JSON
136         *                        object.
137         */
138        @Deprecated
139        public static JSONObject parseJSONObject(final String s)
140                throws ParseException {
141
142                return parse(s);
143        }
144
145
146        /**
147         * Gets a generic member of a JSON object.
148         *
149         * @param o     The JSON object. Must not be {@code null}.
150         * @param key   The JSON object member key. Must not be {@code null}.
151         * @param clazz The expected class of the JSON object member value. Must
152         *              not be {@code null}.
153         *
154         * @return The JSON object member value, may be {@code null}.
155         *
156         * @throws ParseException If the value is not of the expected type.
157         */
158        @SuppressWarnings("unchecked")
159        private static <T> T getGeneric(final JSONObject o, final String key, final Class<T> clazz)
160                throws ParseException {
161
162                if (o.get(key) == null) {
163                        return null;
164                }
165
166                Object value = o.get(key);
167
168                if (! clazz.isAssignableFrom(value.getClass())) {
169                        throw new ParseException("Unexpected type of JSON object member with key \"" + key + "\"", 0);
170                }
171
172                return (T)value;
173        }
174
175
176        /**
177         * Gets a boolean member of a JSON object.
178         *
179         * @param o   The JSON object. Must not be {@code null}.
180         * @param key The JSON object member key. Must not be {@code null}.
181         *
182         * @return The JSON object member value.
183         *
184         * @throws ParseException If the member is missing, the value is
185         *                        {@code null} or not of the expected type.
186         */
187        public static boolean getBoolean(final JSONObject o, final String key)
188                throws ParseException {
189
190                Boolean value = getGeneric(o, key, Boolean.class);
191                
192                if (value == null) {
193                        throw new ParseException("JSON object member with key \"" + key + "\" is missing or null", 0);
194                }
195                
196                return value;
197        }
198
199
200        /**
201         * Gets an number member of a JSON object as {@code int}.
202         *
203         * @param o   The JSON object. Must not be {@code null}.
204         * @param key The JSON object member key. Must not be {@code null}.
205         *
206         * @return The JSON object member value.
207         *
208         * @throws ParseException If the member is missing, the value is
209         *                        {@code null} or not of the expected type.
210         */
211        public static int getInt(final JSONObject o, final String key)
212                throws ParseException {
213
214                Number value = getGeneric(o, key, Number.class);
215                
216                if (value == null) {
217                        throw new ParseException("JSON object member with key \"" + key + "\" is missing or null", 0);
218                }
219                
220                return value.intValue();
221        }
222
223
224        /**
225         * Gets a number member of a JSON object as {@code long}.
226         *
227         * @param o   The JSON object. Must not be {@code null}.
228         * @param key The JSON object member key. Must not be {@code null}.
229         *
230         * @return The JSON object member value.
231         *
232         * @throws ParseException If the member is missing, the value is
233         *                        {@code null} or not of the expected type.
234         */
235        public static long getLong(final JSONObject o, final String key)
236                throws ParseException {
237
238                Number value = getGeneric(o, key, Number.class);
239                
240                if (value == null) {
241                        throw new ParseException("JSON object member with key \"" + key + "\" is missing or null", 0);
242                }
243                
244                return value.longValue();
245        }
246
247
248        /**
249         * Gets a number member of a JSON object {@code float}.
250         *
251         * @param o   The JSON object. Must not be {@code null}.
252         * @param key The JSON object member key. Must not be {@code null}.
253         *
254         * @return The JSON object member value, may be {@code null}.
255         *
256         * @throws ParseException If the member is missing, the value is
257         *                        {@code null} or not of the expected type.
258         */
259        public static float getFloat(final JSONObject o, final String key)
260                throws ParseException {
261
262                Number value = getGeneric(o, key, Number.class);
263                
264                if (value == null) {
265                        throw new ParseException("JSON object member with key \"" + key + "\" is missing or null", 0);
266                }
267                
268                return value.floatValue();
269        }
270
271
272        /**
273         * Gets a number member of a JSON object as {@code double}.
274         *
275         * @param o   The JSON object. Must not be {@code null}.
276         * @param key The JSON object member key. Must not be {@code null}.
277         *
278         * @return The JSON object member value, may be {@code null}.
279         *
280         * @throws ParseException If the member is missing, the value is
281         *                        {@code null} or not of the expected type.
282         */
283        public static double getDouble(final JSONObject o, final String key)
284                throws ParseException {
285
286                Number value = getGeneric(o, key, Number.class);
287                
288                if (value == null) {
289                        throw new ParseException("JSON object member with key \"" + key + "\" is missing or null", 0);
290                }
291                
292                return value.doubleValue();
293        }
294
295
296        /**
297         * Gets a string member of a JSON object.
298         *
299         * @param o   The JSON object. Must not be {@code null}.
300         * @param key The JSON object member key. Must not be {@code null}.
301         *
302         * @return The JSON object member value, may be {@code null}.
303         *
304         * @throws ParseException If the value is not of the expected type.
305         */
306        public static String getString(final JSONObject o, final String key)
307                throws ParseException {
308
309                return getGeneric(o, key, String.class);
310        }
311
312
313        /**
314         * Gets a string member of a JSON object as {@code java.net.URI}.
315         *
316         * @param o   The JSON object. Must not be {@code null}.
317         * @param key The JSON object member key. Must not be {@code null}.
318         *
319         * @return The JSON object member value, may be {@code null}.
320         *
321         * @throws ParseException If the value is not of the expected type.
322         */
323        public static URI getURI(final JSONObject o, final String key)
324                        throws ParseException {
325
326                String value = getString(o, key);
327                
328                if (value == null) {
329                        return null;
330                }
331                
332                try {
333                        return new URI(value);
334
335                } catch (URISyntaxException e) {
336
337                        throw new ParseException(e.getMessage(), 0);
338                }
339        }
340
341
342        /**
343         * Gets a JSON array member of a JSON object.
344         *
345         * @param o   The JSON object. Must not be {@code null}.
346         * @param key The JSON object member key. Must not be {@code null}.
347         *
348         * @return The JSON object member value, may be {@code null}.
349         *
350         * @throws ParseException If the value is not of the expected type.
351         */
352        public static JSONArray getJSONArray(final JSONObject o, final String key)
353                        throws ParseException {
354
355                return getGeneric(o, key, JSONArray.class);
356        }
357
358
359        /**
360         * Gets a string array member of a JSON object.
361         *
362         * @param o   The JSON object. Must not be {@code null}.
363         * @param key The JSON object member key. Must not be {@code null}.
364         *
365         * @return The JSON object member value, may be {@code null}.
366         *
367         * @throws ParseException If the value is not of the expected type.
368         */
369        public static String[] getStringArray(final JSONObject o, final String key)
370                        throws ParseException {
371
372                JSONArray jsonArray = getJSONArray(o, key);
373                
374                if (jsonArray == null) {
375                        return null;
376                }
377
378                try {
379                        return jsonArray.toArray(new String[0]);
380
381                } catch (ArrayStoreException e) {
382
383                        throw new ParseException("JSON object member with key \"" + key + "\" is not an array of strings", 0);
384                }
385        }
386
387        
388        /**
389         * Gets a string list member of a JSON object
390         * 
391         * @param o   The JSON object. Must not be {@code null}.
392         * @param key The JSON object member key. Must not be {@code null}.
393         *
394         * @return The JSON object member value, may be {@code null}.
395         *
396         * @throws ParseException If the value is not of the expected type.
397         */
398        public static List<String> getStringList(final JSONObject o, final String key) throws ParseException {
399
400                String[] array = getStringArray(o, key);
401                
402                if (array == null) {
403                        return null;
404                }
405
406                return Arrays.asList(array);
407        }
408        
409
410        /**
411         * Gets a JSON object member of a JSON object.
412         *
413         * @param o   The JSON object. Must not be {@code null}.
414         * @param key The JSON object member key. Must not be {@code null}.
415         *
416         * @return The JSON object member value, may be {@code null}.
417         *
418         * @throws ParseException If the value is not of the expected type.
419         */
420        public static JSONObject getJSONObject(final JSONObject o, final String key)
421                        throws ParseException {
422
423                return getGeneric(o, key, JSONObject.class);
424        }
425        
426        
427        /**
428         * Gets a string member of a JSON object as {@link Base64URL}.
429         *
430         * @param o   The JSON object. Must not be {@code null}.
431         * @param key The JSON object member key. Must not be {@code null}.
432         *
433         * @return The JSON object member value, may be {@code null}.
434         *
435         * @throws ParseException If the value is not of the expected type.
436         */
437        public static Base64URL getBase64URL(final JSONObject o, final String key)
438                throws ParseException {
439                
440                String value = getString(o, key);
441                
442                if (value == null) {
443                        return null;
444                }
445                
446                return new Base64URL(value);
447        }
448
449
450        /**
451         * Prevents public instantiation.
452         */
453        private JSONObjectUtils() { }
454}
455