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