001package com.nimbusds.oauth2.sdk.util;
002
003
004import java.net.MalformedURLException;
005import java.net.URI;
006import java.net.URISyntaxException;
007import java.net.URL;
008import java.util.Arrays;
009import java.util.HashSet;
010import java.util.List;
011import java.util.Set;
012
013import javax.mail.internet.AddressException;
014import javax.mail.internet.InternetAddress;
015
016import net.minidev.json.JSONArray;
017import net.minidev.json.JSONObject;
018import net.minidev.json.parser.JSONParser;
019
020import com.nimbusds.oauth2.sdk.ParseException;
021
022
023/**
024 * JSON object helper methods for parsing and typed retrieval of member values.
025 */
026public class JSONObjectUtils {
027        
028        
029        /**
030         * Returns {@code true} if the JSON object is defined and contains the 
031         * specified key.
032         *
033         * @param jsonObject The JSON object to check. May be {@code null}.
034         * @param key        The key to check. Must not be {@code null}.
035         *
036         * @return {@code true} if the JSON object is defined and contains the
037         *         specified key, else {@code false}.
038         */
039        public static boolean containsKey(final JSONObject jsonObject, final String key) {
040
041                return jsonObject != null && jsonObject.containsKey(key);
042        }
043        
044        
045        /**
046         * Parses a JSON object.
047         *
048         * <p>Specific JSON to Java entity mapping (as per JSON Simple):
049         *
050         * <ul>
051         *     <li>JSON numbers mapped to {@code java.lang.Number}.
052         *     <li>JSON integer numbers mapped to {@code long}.
053         *     <li>JSON fraction numbers mapped to {@code double}.
054         * </ul>
055         *
056         * @param s The JSON object string to parse. Must not be {@code null}.
057         *
058         * @return The JSON object.
059         *
060         * @throws ParseException If the string cannot be parsed to a JSON 
061         *                        object.
062         */
063        public static JSONObject parseJSONObject(final String s) 
064                throws ParseException {
065                
066                Object o;
067                
068                try {
069                        o = new JSONParser(JSONParser.USE_HI_PRECISION_FLOAT).parse(s);
070                        
071                } catch (net.minidev.json.parser.ParseException e) {
072                        
073                        throw new ParseException("Invalid JSON: " + e.getMessage(), e);
074                }
075                
076                if (o instanceof JSONObject)
077                        return (JSONObject)o;
078                else
079                        throw new ParseException("JSON entity is not an object");
080        }
081        
082        
083        /**
084         * Gets a generic member of a JSON object.
085         *
086         * @param o     The JSON object. Must not be {@code null}.
087         * @param key   The JSON object member key. Must not be {@code null}.
088         * @param clazz The expected class of the JSON object member value. Must
089         *              not be {@code null}.
090         *
091         * @return The JSON object member value.
092         *
093         * @throws ParseException If the value is missing, {@code null} or not
094         *                        of the expected type.
095         */
096        @SuppressWarnings("unchecked")
097        public static <T> T getGeneric(final JSONObject o, final String key, final Class<T> clazz)
098                throws ParseException {
099        
100                if (! o.containsKey(key))
101                        throw new ParseException("Missing JSON object member with key \"" + key + "\"");
102                
103                if (o.get(key) == null)
104                        throw new ParseException("JSON object member with key \"" + key + "\" has null value");
105                
106                Object value = o.get(key);
107                
108                if (! clazz.isAssignableFrom(value.getClass()))
109                        throw new ParseException("Unexpected type of JSON object member with key \"" + key + "\"");
110                
111                return (T)value;
112        }
113
114
115        /**
116         * Gets a boolean member of a JSON object.
117         *
118         * @param o   The JSON object. Must not be {@code null}.
119         * @param key The JSON object member key. Must not be {@code null}.
120         *
121         * @return The member value.
122         *
123         * @throws ParseException If the value is missing, {@code null} or not
124         *                        of the expected type.
125         */
126        public static boolean getBoolean(final JSONObject o, final String key)
127                throws ParseException {
128                
129                return getGeneric(o, key, Boolean.class);
130        }
131        
132        
133        /**
134         * Gets an number member of a JSON object as {@code int}.
135         *
136         * @param o   The JSON object. Must not be {@code null}.
137         * @param key The JSON object member key. Must not be {@code null}.
138         *
139         * @return The member value.
140         *
141         * @throws ParseException If the value is missing, {@code null} or not
142         *                        of the expected type.
143         */
144        public static int getInt(final JSONObject o, final String key)
145                throws ParseException {
146                
147                return getGeneric(o, key, Number.class).intValue();     
148        }
149        
150        
151        /**
152         * Gets a number member of a JSON object as {@code long}.
153         *
154         * @param o   The JSON object. Must not be {@code null}.
155         * @param key The JSON object member key. Must not be {@code null}.
156         *
157         * @return The member value.
158         *
159         * @throws ParseException If the value is missing, {@code null} or not
160         *                        of the expected type.
161         */
162        public static long getLong(final JSONObject o, final String key)
163                throws ParseException {
164                
165                return getGeneric(o, key, Number.class).longValue();
166        }
167        
168        
169        /**
170         * Gets a number member of a JSON object {@code float}.
171         *
172         * @param o   The JSON object. Must not be {@code null}.
173         * @param key The JSON object member key. Must not be {@code null}.
174         *
175         * @return The member value.
176         *
177         * @throws ParseException If the value is missing, {@code null} or not
178         *                        of the expected type.
179         */
180        public static float getFloat(final JSONObject o, final String key)
181                throws ParseException {
182                
183                return getGeneric(o, key, Number.class).floatValue();
184        }
185        
186        
187        /**
188         * Gets a number member of a JSON object as {@code double}.
189         *
190         * @param o   The JSON object. Must not be {@code null}.
191         * @param key The JSON object member key. Must not be {@code null}.
192         *
193         * @return The member value.
194         *
195         * @throws ParseException If the value is missing, {@code null} or not
196         *                        of the expected type.
197         */
198        public static double getDouble(final JSONObject o, final String key)
199                throws ParseException {
200                
201                return getGeneric(o, key, Number.class).doubleValue();
202        }
203
204
205        /**
206         * Gets a number member of a JSON object as {@code java.lang.Number}.
207         *
208         * @param o   The JSON object. Must not be {@code null}.
209         * @param key The JSON object member key. Must not be {@code null}.
210         *
211         * @return The member value.
212         *
213         * @throws ParseException If the value is missing, {@code null} or not
214         *                        of the expected type.
215         */
216        public static Number getNumber(final JSONObject o, final String key)
217                throws ParseException {
218
219                return getGeneric(o, key, Number.class);
220        }
221        
222        
223        /**
224         * Gets a string member of a JSON object.
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 member value.
230         *
231         * @throws ParseException If the value is missing, {@code null} or not
232         *                        of the expected type.
233         */
234        public static String getString(final JSONObject o, final String key)
235                throws ParseException {
236                
237                return getGeneric(o, key, String.class);
238        }
239
240
241        /**
242         * Gets a string member of a JSON object as an enumerated object.
243         *
244         * @param o         The JSON object. Must not be {@code null}.
245         * @param key       The JSON object member key. Must not be
246         *                  {@code null}.
247         * @param enumClass The enumeration class. Must not be {@code null}.
248         *
249         * @return The member value.
250         *
251         * @throws ParseException If the value is missing, {@code null} or not
252         *                        of the expected type.
253         */
254        public static <T extends Enum<T>> T getEnum(final JSONObject o, 
255                                                    final String key,
256                                                    final Class<T> enumClass)
257                throws ParseException {
258
259                String value = getString(o, key);
260
261                for (T en: enumClass.getEnumConstants()) {
262                               
263                        if (en.toString().equalsIgnoreCase(value))
264                                return en;
265                }
266
267                throw new ParseException("Unexpected value of JSON object member with key \"" + key + "\"");
268        }
269
270
271        /**
272         * Gets a string member of a JSON object as {@code java.net.URI}.
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 member value.
278         *
279         * @throws ParseException If the value is missing, {@code null} or not
280         *                        of the expected type.
281         */
282        public static URI getURI(final JSONObject o, final String key)
283                throws ParseException {
284
285                try {
286                        return new URI(getGeneric(o, key, String.class));
287
288                } catch (URISyntaxException e) {
289
290                        throw new ParseException(e.getMessage(), e);
291                }
292        }
293        
294        
295        /**
296         * Gets a string member of a JSON object as {@code java.net.URL}.
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 member value.
302         *
303         * @throws ParseException If the value is missing, {@code null} or not
304         *                        of the expected type.
305         */
306        public static URL getURL(final JSONObject o, final String key)
307                throws ParseException {
308                
309                try {
310                        return new URL(getGeneric(o, key, String.class));
311                        
312                } catch (MalformedURLException e) {
313                
314                        throw new ParseException(e.getMessage(), e);
315                }
316        }
317        
318        
319        /**
320         * Gets a string member of a JSON object as 
321         * {@code javax.mail.internet.InternetAddress}.
322         *
323         * @param o   The JSON object. Must not be {@code null}.
324         * @param key The JSON object member key. Must not be {@code null}.
325         *
326         * @return The member value.
327         *
328         * @throws ParseException If the value is missing, {@code null} or not
329         *                        of the expected type.
330         */
331        public static InternetAddress getEmail(final JSONObject o, final String key)
332                throws ParseException {
333                
334                try {
335                        final boolean strict = true;
336                        
337                        return new InternetAddress(getGeneric(o, key, String.class), strict);
338                        
339                } catch (AddressException e) {
340                
341                        throw new ParseException(e.getMessage(), e);
342                }
343        }
344        
345        
346        /**
347         * Gets a JSON array member of a JSON object.
348         *
349         * @param o   The JSON object. Must not be {@code null}.
350         * @param key The JSON object member key. Must not be {@code null}.
351         *
352         * @return The member value.
353         *
354         * @throws ParseException If the value is missing, {@code null} or not
355         *                        of the expected type.
356         */
357        public static JSONArray getJSONArray(final JSONObject o, final String key)
358                throws ParseException {
359                
360                return getGeneric(o, key, JSONArray.class);
361        }
362
363
364        /**
365         * Gets a list member of a JSON object.
366         *
367         * @param o   The JSON object. Must not be {@code null}.
368         * @param key The JSON object member key. Must not be {@code null}.
369         *
370         * @return The member value.
371         *
372         * @throws ParseException If the value is missing, {@code null} or not
373         *                        of the expected type.
374         */
375        @SuppressWarnings("unchecked")
376        public static List<Object> getList(final JSONObject o, final String key)
377                throws ParseException {
378                
379                return getGeneric(o, key, List.class);
380        }
381
382
383        /**
384         * Gets a string array member of a JSON object.
385         *
386         * @param o   The JSON object. Must not be {@code null}.
387         * @param key The JSON object member key. Must not be {@code null}.
388         *
389         * @return The member value.
390         *
391         * @throws ParseException If the value is missing, {@code null} or not
392         *                        of the expected type.
393         */
394        public static String[] getStringArray(final JSONObject o, final String key)
395                throws ParseException {
396
397                List<Object> list = getList(o, key);
398
399                try {
400                        return list.toArray(new String[0]);
401
402                } catch (ArrayStoreException e) {
403
404                        throw new ParseException("JSON object member with key \"" + key + "\" is not an array of strings");
405                }
406        }
407
408
409        /**
410         * Gets a string list member of a JSON object.
411         *
412         * @param o   The JSON object. Must not be {@code null}.
413         * @param key The JSON object member key. Must not be {@code null}.
414         *
415         * @return The member value.
416         *
417         * @throws ParseException If the value is missing, {@code null} or not
418         *                        of the expected type.
419         */
420        public static List<String> getStringList(final JSONObject o, final String key)
421                throws ParseException {
422
423                return Arrays.asList(getStringArray(o, key));
424        }
425
426
427        /**
428         * Gets a string array member of a JSON object as a string set.
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 member value.
434         *
435         * @throws ParseException If the value is missing, {@code null} or not
436         *                        of the expected type.
437         */
438        public static Set<String> getStringSet(final JSONObject o, final String key)
439                throws ParseException {
440
441                List<Object> list = getList(o, key);
442
443                Set<String> set = new HashSet<>();
444
445                for (Object item: list) {
446
447                        try {
448                                set.add((String)item);
449
450                        } catch (Exception e) {
451
452                                throw new ParseException("JSON object member wit key \"" + key + "\" is not an array of strings");
453                        }
454
455                }
456
457                return set;
458        }
459        
460        
461        /**
462         * Gets a JSON object member of a JSON object.
463         *
464         * @param o   The JSON object. Must not be {@code null}.
465         * @param key The JSON object member key. Must not be {@code null}.
466         *
467         * @return The member value.
468         *
469         * @throws ParseException If the value is missing, {@code null} or not
470         *                        of the expected type.
471         */
472        public static JSONObject getJSONObject(final JSONObject o, final String key)
473                throws ParseException {
474                
475                return getGeneric(o, key, JSONObject.class);
476        }
477        
478
479        /**
480         * Prevents instantiation.
481         */
482        private JSONObjectUtils() {
483        
484                // Nothing to do
485        }
486}
487