001    package com.nimbusds.oauth2.sdk.util;
002    
003    
004    import java.net.MalformedURLException;
005    import java.net.URL;
006    import java.util.List;
007    
008    import javax.mail.internet.AddressException;
009    import javax.mail.internet.InternetAddress;
010    
011    import net.minidev.json.JSONArray;
012    import net.minidev.json.JSONObject;
013    import net.minidev.json.parser.JSONParser;
014    
015    import 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     */
023    public 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