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