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.*;
026
027import net.minidev.json.JSONArray;
028import net.minidev.json.JSONObject;
029
030import com.nimbusds.jwt.JWTClaimsSet;
031import com.nimbusds.oauth2.sdk.ParseException;
032
033
034/**
035 * JSON object helper methods for parsing and typed retrieval of member values.
036 */
037public final class JSONObjectUtils {
038        
039        
040        /**
041         * Returns {@code true} if the JSON object is defined and contains the 
042         * specified key.
043         *
044         * @param jsonObject The JSON object to check. May be {@code null}.
045         * @param key        The key to check. Must not be {@code null}.
046         *
047         * @return {@code true} if the JSON object is defined and contains the
048         *         specified key, else {@code false}.
049         */
050        public static boolean containsKey(final JSONObject jsonObject, final String key) {
051
052                return jsonObject != null && jsonObject.containsKey(key);
053        }
054        
055        
056        /**
057         * Parses a JSON object.
058         *
059         * <p>Specific JSON to Java entity mapping (as per JSON Simple):
060         *
061         * <ul>
062         *     <li>JSON numbers mapped to {@code java.lang.Number}.
063         *     <li>JSON integer numbers mapped to {@code long}.
064         *     <li>JSON fraction numbers mapped to {@code double}.
065         * </ul>
066         *
067         * @param s The JSON object string to parse. Must not be {@code null}.
068         *
069         * @return The JSON object.
070         *
071         * @throws ParseException If the string cannot be parsed to a JSON 
072         *                        object.
073         */
074        public static JSONObject parse(final String s)
075                throws ParseException {
076                
077                Object o = JSONUtils.parseJSON(s);
078                
079                if (o instanceof JSONObject)
080                        return (JSONObject)o;
081                else
082                        throw new ParseException("The JSON entity is not an object");
083        }
084        
085        
086        /**
087         * Parses a JSON object while keeping the order of JSON object members.
088         *
089         * <p>Specific JSON to Java entity mapping (as per JSON Simple):
090         *
091         * <ul>
092         *     <li>JSON numbers mapped to {@code java.lang.Number}.
093         *     <li>JSON integer numbers mapped to {@code long}.
094         *     <li>JSON fraction numbers mapped to {@code double}.
095         * </ul>
096         *
097         * @param s The JSON object string to parse. Must not be {@code null}.
098         *
099         * @return The JSON object as linked hash map.
100         *
101         * @throws ParseException If the string cannot be parsed to a JSON
102         *                        object.
103         */
104        public static LinkedHashMap<String,Object> parseKeepingOrder(final String s)
105                throws ParseException {
106                
107                Object o = JSONUtils.parseJSONKeepingOrder(s);
108                
109                if (o instanceof LinkedHashMap)
110                        return (LinkedHashMap<String,Object>)o;
111                else
112                        throw new ParseException("The JSON entity is not an object");
113        }
114
115
116        /**
117         * Use {@link #parse(String)} instead.
118         *
119         * @param s The JSON object string to parse. Must not be {@code null}.
120         *
121         * @return The JSON object.
122         *
123         * @throws ParseException If the string cannot be parsed to a JSON
124         *                        object.
125         */
126        @Deprecated
127        public static JSONObject parseJSONObject(final String s)
128                throws ParseException {
129
130                return parse(s);
131        }
132        
133        
134        /**
135         * Gets a generic 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         * @param clazz The expected class of the JSON object member value. Must
140         *              not be {@code null}.
141         *
142         * @return The JSON object member value.
143         *
144         * @throws ParseException If the value is missing, {@code null} or not
145         *                        of the expected type.
146         */
147        public static <T> T getGeneric(final JSONObject o, final String key, final Class<T> clazz)
148                throws ParseException {
149        
150                if (! o.containsKey(key))
151                        throw new ParseException("Missing JSON object member with key " + key + "");
152                
153                Object value = o.get(key);
154                
155                if (value == null) {
156                        throw new ParseException("JSON object member with key " + key + " has null value");
157                }
158                
159                try {
160                        return JSONUtils.to(value, clazz);
161                } catch (ParseException e) {
162                        throw new ParseException("Unexpected type of JSON object member with key " + key + "", e);
163                }
164        }
165
166
167        /**
168         * Gets a boolean member of a JSON object.
169         *
170         * @param o   The JSON object. Must not be {@code null}.
171         * @param key The JSON object member key. Must not be {@code null}.
172         *
173         * @return The member value.
174         *
175         * @throws ParseException If the value is missing, {@code null} or not
176         *                        of the expected type.
177         */
178        public static boolean getBoolean(final JSONObject o, final String key)
179                throws ParseException {
180                
181                return getGeneric(o, key, Boolean.class);
182        }
183
184
185        /**
186         * Gets a boolean member of a JSON object.
187         *
188         * @param o   The JSON object. Must not be {@code null}.
189         * @param key The JSON object member key. Must not be {@code null}.
190         * @param def The default value to return if the key is not present or.
191         *            the value is {@code null}. May be {@code null}.
192         *
193         * @return The member value.
194         *
195         * @throws ParseException If the value is not of the expected type.
196         */
197        public static boolean getBoolean(final JSONObject o, final String key, final boolean def)
198                throws ParseException {
199                
200                if (o.get(key) != null) {
201                        return getBoolean(o, key);
202                }
203                
204                return def;
205        }
206        
207        
208        /**
209         * Gets an number member of a JSON object as {@code int}.
210         *
211         * @param o   The JSON object. Must not be {@code null}.
212         * @param key The JSON object member key. Must not be {@code null}.
213         *
214         * @return The member value.
215         *
216         * @throws ParseException If the value is missing, {@code null} or not
217         *                        of the expected type.
218         */
219        public static int getInt(final JSONObject o, final String key)
220                throws ParseException {
221                
222                return getGeneric(o, key, Number.class).intValue();     
223        }
224        
225        
226        /**
227         * Gets an number member of a JSON object as {@code int}.
228         *
229         * @param o   The JSON object. Must not be {@code null}.
230         * @param key The JSON object member key. Must not be {@code null}.
231         * @param def The default value to return if the key is not present or
232         *            the value is {@code null}.
233         *
234         * @return The member value.
235         *
236         * @throws ParseException If the value is not of the expected type.
237         */
238        public static int getInt(final JSONObject o, final String key, final int def)
239                throws ParseException {
240                
241                if (o.get(key) != null) {
242                        return getInt(o, key);
243                }
244                
245                return def;
246        }
247        
248        
249        /**
250         * Gets a number member of a JSON object as {@code long}.
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 long getLong(final JSONObject o, final String key)
261                throws ParseException {
262                
263                return getGeneric(o, key, Number.class).longValue();
264        }
265        
266        
267        /**
268         * Gets a number member of a JSON object as {@code long}.
269         *
270         * @param o   The JSON object. Must not be {@code null}.
271         * @param key The JSON object member key. Must not be {@code null}.
272         * @param def The default value to return if the key is not present or
273         *            the value is {@code null}.
274         *
275         * @return The member value.
276         *
277         * @throws ParseException If the value is not of the expected type.
278         */
279        public static long getLong(final JSONObject o, final String key, final long def)
280                throws ParseException {
281                
282                if (o.get(key) != null) {
283                        return getLong(o, key);
284                }
285                
286                return def;
287        }
288        
289        
290        /**
291         * Gets a number member of a JSON object {@code float}.
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 float getFloat(final JSONObject o, final String key)
302                throws ParseException {
303                
304                return getGeneric(o, key, Number.class).floatValue();
305        }
306        
307        
308        /**
309         * Gets a number member of a JSON object {@code float}.
310         *
311         * @param o   The JSON object. Must not be {@code null}.
312         * @param key The JSON object member key. Must not be {@code null}.
313         * @param def The default value to return if the key is not present or
314         *            the value is {@code null}.
315         *
316         * @return The member value.
317         *
318         * @throws ParseException If the value is not of the expected type.
319         */
320        public static float getFloat(final JSONObject o, final String key, final float def)
321                throws ParseException {
322                
323                if (o.get(key) != null) {
324                        return getFloat(o, key);
325                }
326                
327                return def;
328        }
329        
330        
331        /**
332         * Gets a number member of a JSON object as {@code double}.
333         *
334         * @param o   The JSON object. Must not be {@code null}.
335         * @param key The JSON object member key. Must not be {@code null}.
336         *
337         * @return The member value.
338         *
339         * @throws ParseException If the value is missing, {@code null} or not
340         *                        of the expected type.
341         */
342        public static double getDouble(final JSONObject o, final String key)
343                throws ParseException {
344                
345                return getGeneric(o, key, Number.class).doubleValue();
346        }
347        
348        
349        /**
350         * Gets a number member of a JSON object as {@code double}.
351         *
352         * @param o   The JSON object. Must not be {@code null}.
353         * @param key The JSON object member key. Must not be {@code null}.
354         * @param def The default value to return if the key is not present or
355         *            the value is {@code null}.
356         *
357         * @return The member value.
358         *
359         * @throws ParseException If the value is not of the expected type.
360         */
361        public static double getDouble(final JSONObject o, final String key, final double def)
362                throws ParseException {
363                
364                if (o.get(key) != null) {
365                        return getDouble(o, key);
366                }
367                
368                return def;
369        }
370
371
372        /**
373         * Gets a number member of a JSON object as {@code java.lang.Number}.
374         *
375         * @param o   The JSON object. Must not be {@code null}.
376         * @param key The JSON object member key. Must not be {@code null}.
377         *
378         * @return The member value.
379         *
380         * @throws ParseException If the value is missing, {@code null} or not
381         *                        of the expected type.
382         */
383        public static Number getNumber(final JSONObject o, final String key)
384                throws ParseException {
385
386                return getGeneric(o, key, Number.class);
387        }
388
389
390        /**
391         * Gets a number member of a JSON object as {@code java.lang.Number}.
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         * @param def The default value to return if the key is not present or
396         *            the value is {@code null}. May be {@code null}.
397         *
398         * @return The member value.
399         *
400         * @throws ParseException If the value is not of the expected type.
401         */
402        public static Number getNumber(final JSONObject o, final String key, final Number def)
403                throws ParseException {
404
405                if (o.get(key) != null) {
406                        return getNumber(o, key);
407                }
408                
409                return def;
410        }
411        
412        
413        /**
414         * Gets a string member of a JSON object.
415         *
416         * @param o   The JSON object. Must not be {@code null}.
417         * @param key The JSON object member key. Must not be {@code null}.
418         *
419         * @return The member value.
420         *
421         * @throws ParseException If the value is missing, {@code null} or not
422         *                        of the expected type.
423         */
424        public static String getString(final JSONObject o, final String key)
425                throws ParseException {
426                
427                return getGeneric(o, key, String.class);
428        }
429        
430        
431        /**
432         * Gets a string member of a JSON object.
433         *
434         * @param o   The JSON object. Must not be {@code null}.
435         * @param key The JSON object member key. Must not be {@code null}.
436         * @param def The default value to return if the key is not present or
437         *            the value is {@code null}. May be {@code null}.
438         *
439         * @return The member value.
440         *
441         * @throws ParseException If the value is not of the expected type.
442         */
443        public static String getString(final JSONObject o, final String key, final String def)
444                throws ParseException {
445                
446                if (o.get(key) != null) {
447                        return getString(o, key);
448                }
449                
450                return def;
451        }
452
453
454        /**
455         * Gets a string member of a JSON object as an enumerated object.
456         *
457         * @param o         The JSON object. Must not be {@code null}.
458         * @param key       The JSON object member key. Must not be
459         *                  {@code null}.
460         * @param enumClass The enumeration class. Must not be {@code null}.
461         *
462         * @return The member value.
463         *
464         * @throws ParseException If the value is missing, {@code null} or not
465         *                        of the expected type.
466         */
467        public static <T extends Enum<T>> T getEnum(final JSONObject o, 
468                                                    final String key,
469                                                    final Class<T> enumClass)
470                throws ParseException {
471
472                String value = getString(o, key);
473
474                for (T en: enumClass.getEnumConstants()) {
475                               
476                        if (en.toString().equalsIgnoreCase(value))
477                                return en;
478                }
479
480                throw new ParseException("Unexpected value of JSON object member with key " + key + "");
481        }
482
483
484        /**
485         * Gets a string member of a JSON object as an enumerated object.
486         *
487         * @param o         The JSON object. Must not be {@code null}.
488         * @param key       The JSON object member key. Must not be
489         *                  {@code null}.
490         * @param enumClass The enumeration class. Must not be {@code null}.
491         * @param def       The default value to return if the key is not
492         *                  present or the value is {@code null}. May be
493         *                  {@code null}.
494         *
495         * @return The member value.
496         *
497         * @throws ParseException If the value is not of the expected type.
498         */
499        public static <T extends Enum<T>> T getEnum(final JSONObject o,
500                                                    final String key,
501                                                    final Class<T> enumClass,
502                                                    final T def)
503                throws ParseException {
504
505                if (o.get(key) != null) {
506                        return getEnum(o, key, enumClass);
507                }
508                
509                return def;
510        }
511
512
513        /**
514         * Gets a string member of a JSON object as {@code java.net.URI}.
515         *
516         * @param o   The JSON object. Must not be {@code null}.
517         * @param key The JSON object member key. Must not be {@code null}.
518         *
519         * @return The member value.
520         *
521         * @throws ParseException If the value is missing, {@code null} or not
522         *                        of the expected type.
523         */
524        public static URI getURI(final JSONObject o, final String key)
525                throws ParseException {
526
527                try {
528                        return new URI(getGeneric(o, key, String.class));
529
530                } catch (URISyntaxException e) {
531
532                        throw new ParseException(e.getMessage(), e);
533                }
534        }
535
536
537        /**
538         * Gets a string member of a JSON object as {@code java.net.URI}.
539         *
540         * @param o   The JSON object. Must not be {@code null}.
541         * @param key The JSON object member key. Must not be {@code null}.
542         * @param def The default value to return if the key is not present or
543         *            the value is {@code null}. May be {@code null}.
544         *
545         * @return The member value.
546         *
547         * @throws ParseException If the value is not of the expected type.
548         */
549        public static URI getURI(final JSONObject o, final String key, final URI def)
550                throws ParseException {
551
552                if (o.get(key) != null) {
553                        return getURI(o, key);
554                }
555                
556                return def;
557        }
558        
559        
560        /**
561         * Gets a string member of a JSON object as {@code java.net.URL}.
562         *
563         * @param o   The JSON object. Must not be {@code null}.
564         * @param key The JSON object member key. Must not be {@code null}.
565         *
566         * @return The member value.
567         *
568         * @throws ParseException If the value is missing, {@code null} or not
569         *                        of the expected type.
570         */
571        public static URL getURL(final JSONObject o, final String key)
572                throws ParseException {
573                
574                try {
575                        return new URL(getGeneric(o, key, String.class));
576                        
577                } catch (MalformedURLException e) {
578                
579                        throw new ParseException(e.getMessage(), e);
580                }
581        }
582        
583        
584        /**
585         * Gets a JSON array member of a JSON object.
586         *
587         * @param o   The JSON object. Must not be {@code null}.
588         * @param key The JSON object member key. Must not be {@code null}.
589         *
590         * @return The member value.
591         *
592         * @throws ParseException If the value is missing, {@code null} or not
593         *                        of the expected type.
594         */
595        public static JSONArray getJSONArray(final JSONObject o, final String key)
596                throws ParseException {
597                
598                return getGeneric(o, key, JSONArray.class);
599        }
600        
601        
602        /**
603         * Gets a JSON array member of a JSON object.
604         *
605         * @param o   The JSON object. Must not be {@code null}.
606         * @param key The JSON object member key. Must not be {@code null}.
607         * @param def The default value to return if the key is not present or
608         *            the value is {@code null}. May be {@code null}.
609         *
610         * @return The member value.
611         *
612         * @throws ParseException If the value is not of the expected type.
613         */
614        public static JSONArray getJSONArray(final JSONObject o, final String key, final JSONArray def)
615                throws ParseException {
616                
617                if (o.get(key) != null) {
618                        return getJSONArray(o, key);
619                }
620                
621                return def;
622        }
623
624
625        /**
626         * Gets a list member of a JSON object.
627         *
628         * @param o   The JSON object. Must not be {@code null}.
629         * @param key The JSON object member key. Must not be {@code null}.
630         *
631         * @return The member value.
632         *
633         * @throws ParseException If the value is missing, {@code null} or not
634         *                        of the expected type.
635         */
636        @SuppressWarnings("unchecked")
637        public static List<Object> getList(final JSONObject o, final String key)
638                throws ParseException {
639                
640                return getGeneric(o, key, List.class);
641        }
642
643
644        /**
645         * Gets a list member of a JSON object.
646         *
647         * @param o   The JSON object. Must not be {@code null}.
648         * @param key The JSON object member key. Must not be {@code null}.
649         * @param def The default value to return if the key is not present or
650         *            the value is {@code null}. May be {@code null}.
651         *
652         * @return The member value.
653         *
654         * @throws ParseException If the value is not of the expected type.
655         */
656        public static List<Object> getList(final JSONObject o, final String key, final List<Object> def)
657                throws ParseException {
658                
659                if (o.get(key) != null) {
660                        return getList(o, key);
661                }
662                
663                return def;
664        }
665
666
667        /**
668         * Gets a string array member of a JSON object.
669         *
670         * @param o   The JSON object. Must not be {@code null}.
671         * @param key The JSON object member key. Must not be {@code null}.
672         *
673         * @return The member value.
674         *
675         * @throws ParseException If the value is missing, {@code null} or not
676         *                        of the expected type.
677         */
678        public static String[] getStringArray(final JSONObject o, final String key)
679                throws ParseException {
680
681                List<Object> list = getList(o, key);
682
683                try {
684                        return list.toArray(new String[0]);
685
686                } catch (ArrayStoreException e) {
687
688                        throw new ParseException("JSON object member with key " + key + " is not an array of strings");
689                }
690        }
691
692
693        /**
694         * Gets a string array member of a JSON object.
695         *
696         * @param o   The JSON object. Must not be {@code null}.
697         * @param key The JSON object member key. Must not be {@code null}.
698         * @param def The default value to return if the key is not present or
699         *            the value is {@code null}. May be {@code null}.
700         *
701         * @return The member value.
702         *
703         * @throws ParseException If the value is not of the expected type.
704         */
705        public static String[] getStringArray(final JSONObject o, final String key, final String[] def)
706                throws ParseException {
707
708                if (o.get(key) != null) {
709                        return getStringArray(o, key);
710                }
711                
712                return def;
713        }
714
715
716        /**
717         * Gets a string list member of a JSON object.
718         *
719         * @param o   The JSON object. Must not be {@code null}.
720         * @param key The JSON object member key. Must not be {@code null}.
721         *
722         * @return The member value.
723         *
724         * @throws ParseException If the value is missing, {@code null} or not
725         *                        of the expected type.
726         */
727        public static List<String> getStringList(final JSONObject o, final String key)
728                throws ParseException {
729
730                return Arrays.asList(getStringArray(o, key));
731        }
732
733
734        /**
735         * Gets a string list member of a JSON object.
736         *
737         * @param o   The JSON object. Must not be {@code null}.
738         * @param key The JSON object member key. Must not be {@code null}.
739         * @param def The default value to return if the key is not present or
740         *            the value is {@code null}. May be {@code null}.
741         *
742         * @return The member value.
743         *
744         * @throws ParseException If the value is not of the expected type.
745         */
746        public static List<String> getStringList(final JSONObject o, final String key, final List<String> def)
747                throws ParseException {
748
749                if (o.get(key) != null) {
750                        return getStringList(o, key);
751                }
752                
753                return def;
754        }
755
756
757        /**
758         * Gets a string array member of a JSON object as a string set.
759         *
760         * @param o   The JSON object. Must not be {@code null}.
761         * @param key The JSON object member key. Must not be {@code null}.
762         *
763         * @return The member value.
764         *
765         * @throws ParseException If the value is missing, {@code null} or not
766         *                        of the expected type.
767         */
768        public static Set<String> getStringSet(final JSONObject o, final String key)
769                throws ParseException {
770
771                List<Object> list = getList(o, key);
772
773                Set<String> set = new HashSet<>();
774
775                for (Object item: list) {
776
777                        try {
778                                set.add((String)item);
779
780                        } catch (Exception e) {
781
782                                throw new ParseException("JSON object member with key " + key + " is not an array of strings");
783                        }
784
785                }
786
787                return set;
788        }
789
790
791        /**
792         * Gets a string array member of a JSON object as a string set.
793         *
794         * @param o   The JSON object. Must not be {@code null}.
795         * @param key The JSON object member key. Must not be {@code null}.
796         * @param def The default value to return if the key is not present or
797         *            the value is {@code null}. May be {@code null}.
798         *
799         * @return The member value.
800         *
801         * @throws ParseException If the value is not of the expected type.
802         */
803        public static Set<String> getStringSet(final JSONObject o, final String key, final Set<String> def)
804                throws ParseException {
805
806                if (o.get(key) != null) {
807                        return getStringSet(o, key);
808                }
809                
810                return def;
811        }
812        
813        
814        /**
815         * Gets a JSON object member of a JSON object.
816         *
817         * @param o   The JSON object. Must not be {@code null}.
818         * @param key The JSON object member key. Must not be {@code null}.
819         *
820         * @return The member value.
821         *
822         * @throws ParseException If the value is missing, {@code null} or not
823         *                        of the expected type.
824         */
825        public static JSONObject getJSONObject(final JSONObject o, final String key)
826                throws ParseException {
827                
828                return getGeneric(o, key, JSONObject.class);
829        }
830        
831        
832        /**
833         * Gets a JSON object member of a JSON object.
834         *
835         * @param o   The JSON object. Must not be {@code null}.
836         * @param key The JSON object member key. Must not be {@code null}.
837         * @param def The default value to return if the key is not present or
838         *            the value is {@code null}. May be {@code null}.
839         *
840         * @return The member value.
841         *
842         * @throws ParseException If the value is not of the expected type.
843         */
844        public static JSONObject getJSONObject(final JSONObject o, final String key, final JSONObject def)
845                throws ParseException {
846                
847                if (o.get(key) != null) {
848                        return getJSONObject(o, key);
849                }
850                
851                return def;
852        }
853        
854        
855        /**
856         * Returns the JSON object representation of the specified JWT claims
857         * set.
858         *
859         * @param jwtClaimsSet The JWT claims set, {@code null} if not
860         *                     specified.
861         *
862         * @return The JSON object, {@code null} if not specified.
863         */
864        public static JSONObject toJSONObject(final JWTClaimsSet jwtClaimsSet) {
865                
866                if (jwtClaimsSet == null) {
867                        return null;
868                }
869                
870                if (jwtClaimsSet.getClaims().isEmpty()) {
871                        return new JSONObject();
872                }
873                
874                // Serialise and parse is the safest method
875                final String json = jwtClaimsSet.toString();
876                
877                try {
878                        return parse(json);
879                } catch (ParseException e) {
880                        // Should never happen
881                        return null;
882                }
883        }
884        
885
886        /**
887         * Prevents public instantiation.
888         */
889        private JSONObjectUtils() {}
890}
891