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