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