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         * Gets a generic member of a JSON object.
076         *
077         * @param o     The JSON object. Must not be {@code null}.
078         * @param key   The JSON object member key. Must not be {@code null}.
079         * @param clazz The expected class of the JSON object member value. Must
080         *              not be {@code null}.
081         *
082         * @return The JSON object member value.
083         *
084         * @throws ParseException If the value is missing, {@code null} or not
085         *                        of the expected type.
086         */
087        @SuppressWarnings("unchecked")
088        public static <T> T getGeneric(final JSONObject o, final String key, final Class<T> clazz)
089                throws ParseException {
090        
091                if (! o.containsKey(key))
092                        throw new ParseException("Missing JSON object member with key \"" + key + "\"");
093                
094                if (o.get(key) == null)
095                        throw new ParseException("JSON object member with key \"" + key + "\" has null value");
096                
097                Object value = o.get(key);
098                
099                if (! clazz.isAssignableFrom(value.getClass()))
100                        throw new ParseException("Unexpected type of JSON object member with key \"" + key + "\"");
101                
102                return (T)value;
103        }
104
105
106        /**
107         * Gets a boolean member of a JSON object.
108         *
109         * @param o   The JSON object. Must not be {@code null}.
110         * @param key The JSON object member key. Must not be {@code null}.
111         *
112         * @return The member value.
113         *
114         * @throws ParseException If the value is missing, {@code null} or not
115         *                        of the expected type.
116         */
117        public static boolean getBoolean(final JSONObject o, final String key)
118                throws ParseException {
119                
120                return getGeneric(o, key, Boolean.class);
121        }
122        
123        
124        /**
125         * Gets an number member of a JSON object as {@code int}.
126         *
127         * @param o   The JSON object. Must not be {@code null}.
128         * @param key The JSON object member key. Must not be {@code null}.
129         *
130         * @return The member value.
131         *
132         * @throws ParseException If the value is missing, {@code null} or not
133         *                        of the expected type.
134         */
135        public static int getInt(final JSONObject o, final String key)
136                throws ParseException {
137                
138                return getGeneric(o, key, Number.class).intValue();     
139        }
140        
141        
142        /**
143         * Gets a number member of a JSON object as {@code long}.
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         *
148         * @return The member value.
149         *
150         * @throws ParseException If the value is missing, {@code null} or not
151         *                        of the expected type.
152         */
153        public static long getLong(final JSONObject o, final String key)
154                throws ParseException {
155                
156                return getGeneric(o, key, Number.class).longValue();
157        }
158        
159        
160        /**
161         * Gets a number member of a JSON object {@code float}.
162         *
163         * @param o   The JSON object. Must not be {@code null}.
164         * @param key The JSON object member key. Must not be {@code null}.
165         *
166         * @return The member value.
167         *
168         * @throws ParseException If the value is missing, {@code null} or not
169         *                        of the expected type.
170         */
171        public static float getFloat(final JSONObject o, final String key)
172                throws ParseException {
173                
174                return getGeneric(o, key, Number.class).floatValue();
175        }
176        
177        
178        /**
179         * Gets a number member of a JSON object as {@code double}.
180         *
181         * @param o   The JSON object. Must not be {@code null}.
182         * @param key The JSON object member key. Must not be {@code null}.
183         *
184         * @return The member value.
185         *
186         * @throws ParseException If the value is missing, {@code null} or not
187         *                        of the expected type.
188         */
189        public static double getDouble(final JSONObject o, final String key)
190                throws ParseException {
191                
192                return getGeneric(o, key, Number.class).doubleValue();
193        }
194
195
196        /**
197         * Gets a number member of a JSON object as {@code java.lang.Number}.
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 member value.
203         *
204         * @throws ParseException If the value is missing, {@code null} or not
205         *                        of the expected type.
206         */
207        public static Number getNumber(final JSONObject o, final String key)
208                throws ParseException {
209
210                return getGeneric(o, key, Number.class);
211        }
212        
213        
214        /**
215         * Gets a string member of a JSON object.
216         *
217         * @param o   The JSON object. Must not be {@code null}.
218         * @param key The JSON object member key. Must not be {@code null}.
219         *
220         * @return The member value.
221         *
222         * @throws ParseException If the value is missing, {@code null} or not
223         *                        of the expected type.
224         */
225        public static String getString(final JSONObject o, final String key)
226                throws ParseException {
227                
228                return getGeneric(o, key, String.class);
229        }
230
231
232        /**
233         * Gets a string member of a JSON object as an enumerated object.
234         *
235         * @param o         The JSON object. Must not be {@code null}.
236         * @param key       The JSON object member key. Must not be
237         *                  {@code null}.
238         * @param enumClass The enumeration class. Must not be {@code null}.
239         *
240         * @return The member value.
241         *
242         * @throws ParseException If the value is missing, {@code null} or not
243         *                        of the expected type.
244         */
245        public static <T extends Enum<T>> T getEnum(final JSONObject o, 
246                                                    final String key,
247                                                    final Class<T> enumClass)
248                throws ParseException {
249
250                String value = getString(o, key);
251
252                for (T en: enumClass.getEnumConstants()) {
253                               
254                        if (en.toString().equalsIgnoreCase(value))
255                                return en;
256                }
257
258                throw new ParseException("Unexpected value of JSON object member with key \"" + key + "\"");
259        }
260
261
262        /**
263         * Gets a string member of a JSON object as {@code java.net.URI}.
264         *
265         * @param o   The JSON object. Must not be {@code null}.
266         * @param key The JSON object member key. Must not be {@code null}.
267         *
268         * @return The member value.
269         *
270         * @throws ParseException If the value is missing, {@code null} or not
271         *                        of the expected type.
272         */
273        public static URI getURI(final JSONObject o, final String key)
274                throws ParseException {
275
276                try {
277                        return new URI(getGeneric(o, key, String.class));
278
279                } catch (URISyntaxException e) {
280
281                        throw new ParseException(e.getMessage(), e);
282                }
283        }
284        
285        
286        /**
287         * Gets a string member of a JSON object as {@code java.net.URL}.
288         *
289         * @param o   The JSON object. Must not be {@code null}.
290         * @param key The JSON object member key. Must not be {@code null}.
291         *
292         * @return The member value.
293         *
294         * @throws ParseException If the value is missing, {@code null} or not
295         *                        of the expected type.
296         */
297        public static URL getURL(final JSONObject o, final String key)
298                throws ParseException {
299                
300                try {
301                        return new URL(getGeneric(o, key, String.class));
302                        
303                } catch (MalformedURLException e) {
304                
305                        throw new ParseException(e.getMessage(), e);
306                }
307        }
308        
309        
310        /**
311         * Gets a string member of a JSON object as 
312         * {@code javax.mail.internet.InternetAddress}.
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 member value.
318         *
319         * @throws ParseException If the value is missing, {@code null} or not
320         *                        of the expected type.
321         */
322        public static InternetAddress getEmail(final JSONObject o, final String key)
323                throws ParseException {
324                
325                try {
326                        final boolean strict = true;
327                        
328                        return new InternetAddress(getGeneric(o, key, String.class), strict);
329                        
330                } catch (AddressException e) {
331                
332                        throw new ParseException(e.getMessage(), e);
333                }
334        }
335        
336        
337        /**
338         * Gets a JSON array member of a JSON object.
339         *
340         * @param o   The JSON object. Must not be {@code null}.
341         * @param key The JSON object member key. Must not be {@code null}.
342         *
343         * @return The member value.
344         *
345         * @throws ParseException If the value is missing, {@code null} or not
346         *                        of the expected type.
347         */
348        public static JSONArray getJSONArray(final JSONObject o, final String key)
349                throws ParseException {
350                
351                return getGeneric(o, key, JSONArray.class);
352        }
353
354
355        /**
356         * Gets a list 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 member value.
362         *
363         * @throws ParseException If the value is missing, {@code null} or not
364         *                        of the expected type.
365         */
366        @SuppressWarnings("unchecked")
367        public static List<Object> getList(final JSONObject o, final String key)
368                throws ParseException {
369                
370                return getGeneric(o, key, List.class);
371        }
372
373
374        /**
375         * Gets a string array member of a JSON object.
376         *
377         * @param o   The JSON object. Must not be {@code null}.
378         * @param key The JSON object member key. Must not be {@code null}.
379         *
380         * @return The member value.
381         *
382         * @throws ParseException If the value is missing, {@code null} or not
383         *                        of the expected type.
384         */
385        public static String[] getStringArray(final JSONObject o, final String key)
386                throws ParseException {
387
388                List<Object> list = getList(o, key);
389
390                try {
391                        return list.toArray(new String[0]);
392
393                } catch (ArrayStoreException e) {
394
395                        throw new ParseException("JSON object member with key \"" + key + "\" is not an array of strings");
396                }
397        }
398
399
400        /**
401         * Gets a string list member of a JSON object.
402         *
403         * @param o   The JSON object. Must not be {@code null}.
404         * @param key The JSON object member key. Must not be {@code null}.
405         *
406         * @return The member value.
407         *
408         * @throws ParseException If the value is missing, {@code null} or not
409         *                        of the expected type.
410         */
411        public static List<String> getStringList(final JSONObject o, final String key)
412                throws ParseException {
413
414                return Arrays.asList(getStringArray(o, key));
415        }
416
417
418        /**
419         * Gets a string array member of a JSON object as a string set.
420         *
421         * @param o   The JSON object. Must not be {@code null}.
422         * @param key The JSON object member key. Must not be {@code null}.
423         *
424         * @return The member value.
425         *
426         * @throws ParseException If the value is missing, {@code null} or not
427         *                        of the expected type.
428         */
429        public static Set<String> getStringSet(final JSONObject o, final String key)
430                throws ParseException {
431
432                List<Object> list = getList(o, key);
433
434                Set<String> set = new HashSet<>();
435
436                for (Object item: list) {
437
438                        try {
439                                set.add((String)item);
440
441                        } catch (Exception e) {
442
443                                throw new ParseException("JSON object member wit key \"" + key + "\" is not an array of strings");
444                        }
445
446                }
447
448                return set;
449        }
450        
451        
452        /**
453         * Gets a JSON object member of a JSON object.
454         *
455         * @param o   The JSON object. Must not be {@code null}.
456         * @param key The JSON object member key. Must not be {@code null}.
457         *
458         * @return The member value.
459         *
460         * @throws ParseException If the value is missing, {@code null} or not
461         *                        of the expected type.
462         */
463        public static JSONObject getJSONObject(final JSONObject o, final String key)
464                throws ParseException {
465                
466                return getGeneric(o, key, JSONObject.class);
467        }
468        
469
470        /**
471         * Prevents instantiation.
472         */
473        private JSONObjectUtils() {
474        
475                // Nothing to do
476        }
477}
478