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