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