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