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