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