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