001package Torello.Java.JSON; 002 003import javax.json.*; 004import java.lang.reflect.*; 005import java.math.*; 006 007import java.util.function.Function; 008 009import static javax.json.JsonValue.ValueType.*; 010import static Torello.Java.JSON.JFlag.*; 011import static Torello.Java.JSON.RJInternal.*; 012 013/** 014 * Builds on the J2EE Standard Release JSON Parsing Tools by providing additional 015 * help with converting JSON Data into Java Object-Data. 016 * 017 * <EMBED CLASS='external-html' DATA-FILE-ID=GLASS_FISH_NOTE> 018 * <EMBED CLASS='external-html' DATA-FILE-ID=READ_JSON> 019 * <EMBED CLASS='external-html' DATA-FILE-ID=JOS> 020 * 021 * @see Json 022 * @see JsonObject 023 * @see JsonArray 024 */ 025@Torello.JavaDoc.StaticFunctional 026@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JSON_JDHBI") 027public class ReadJSON 028{ 029 // This is a static class. Has no program state. 030 private ReadJSON() { } 031 032 033 // ******************************************************************************************** 034 // ******************************************************************************************** 035 // Object 036 // ******************************************************************************************** 037 // ******************************************************************************************** 038 039 040 /** 041 * Retrieve a {@link JsonArray} element, and transform it to a Java {@code Object} (POJO). 042 * <EMBED CLASS='external-HTML' DATA-FILE-ID=JR_PC_NOTE> 043 * <EMBED CLASS=defs DATA-JTYPE=JsonObject DATA-TYPE='Type Parameter T'> 044 * 045 * @param <T> <EMBED CLASS='external-html' DATA-FILE-ID=JR_TYPE_T> 046 * @param ja Any instance of {@link JsonArray} 047 * @param index <EMBED CLASS='external-html' DATA-FILE-ID=JR_INDEX_JA> 048 * @param c <EMBED CLASS='external-html' DATA-FILE-ID=JR_CLASS> 049 * @param throwOnNull <EMBED CLASS='external-html' DATA-FILE-ID=JR_TON_JA> 050 * @return <EMBED CLASS='external-html' DATA-FILE-ID=JR_RET_TON_JA> 051 * 052 * @throws IndexOutOfBoundsException If {@code 'index'} is out of bounds of {@code 'ja'} 053 * @throws JsonTypeArrException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JTAEX> 054 * @throws JsonNullArrException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JNAEX> 055 * @throws JsonException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JEX_REFL> 056 */ 057 public static <T> T getObject(JsonArray ja, int index, Class<T> c, boolean throwOnNull) 058 { 059 // This will throw an IndexOutOfBoundsException if the index is out of bounds. 060 JsonValue jv = ja.get(index); 061 062 switch (jv.getValueType()) 063 { 064 case NULL: 065 066 // This is simple-stuff (not rocket-science). "Type Mapping" Code has to worry 067 // about what the meaning of "null" should be. 068 069 if (throwOnNull) throw new JsonNullArrException(ja, index, OBJECT, c); 070 else return getObject(null, c); 071 072 case OBJECT: return getObject((JsonObject) jv, c); 073 074 // The JsonValue at the specified array-index does not contain a JsonObject. 075 default: throw new JsonTypeArrException(ja, index, OBJECT, jv, c); 076 } 077 } 078 079 /** 080 * Extract a {@link JsonObject} property, and transform it to a Java {@code Object} (POJO). 081 * <EMBED CLASS='external-HTML' DATA-FILE-ID=JR_PC_NOTE> 082 * <EMBED CLASS=defs DATA-TYPE='Type Parameter T' DATA-JTYPE=JsonObject> 083 * 084 * @param <T> <EMBED CLASS='external-html' DATA-FILE-ID=JR_TYPE_T> 085 * @param jo Any instance of {@link JsonObject}. 086 * @param propertyName <EMBED CLASS='external-html' DATA-FILE-ID=JR_PN_JO> 087 * @param isOptional <EMBED CLASS='external-html' DATA-FILE-ID=JR_ISOPT_JO> 088 * @param throwOnNull <EMBED CLASS='external-html' DATA-FILE-ID=JR_TON_JO> 089 * @return <EMBED CLASS='external-html' DATA-FILE-ID=JR_RET_TON_JO> 090 * 091 * @throws JsonPropMissingException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JPMEX> 092 * @throws JsonTypeObjException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JTOEX> 093 * @throws JsonNullObjException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JNOEX> 094 * @throws JsonException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JEX_REFL> 095 */ 096 public static <T> T getObject( 097 JsonObject jo, String propertyName, Class<T> c, boolean isOptional, 098 boolean throwOnNull 099 ) 100 { 101 if (! jo.containsKey(propertyName)) 102 { 103 if (isOptional) return null; 104 105 throw new JsonPropMissingException(jo, propertyName, OBJECT, c); 106 } 107 108 JsonValue jv = jo.get(propertyName); 109 110 switch (jv.getValueType()) 111 { 112 case NULL: 113 114 // This is simple-stuff (not rocket-science). "Type Mapping" Code has to 115 // worry about what the meaning of "null" should be. 116 117 if (throwOnNull) throw new JsonNullObjException(jo, propertyName, OBJECT, c); 118 119 return getObject(null, c); 120 121 case OBJECT: return getObject((JsonObject) jv, c); 122 123 // The JsonObject propertydoes not contain a JsonObject. 124 default: throw new JsonTypeObjException(jo, propertyName, OBJECT, jv, c); 125 } 126 } 127 128 /** 129 * This class contains a lot of the reason / impetus for writing {@code 'ReadJSON'}. This 130 * does converts a {@link JsonObject} into a Java-Object. The actual binding must be 131 * implemented by the programmer - because the class-type that is passed to this method 132 * (parameter {@code 'c'}) must have a constructor accepting this {@code JsonObject}. 133 * 134 * <EMBED CLASS='external-html' DATA-FILE-ID=JR_PC_NOTE> 135 * @param <T> <EMBED CLASS='external-html' DATA-FILE-ID=JR_TYPE_T> 136 * 137 * @param jo This may be any {@link JsonObject} which can be bound to {@code 'c'}. 138 * <BR /><BR /><B>NOTE:</B> This parameter may be null, and if it is, null is passed to the 139 * {@code 'c'} constructor. 140 * 141 * @param c <EMBED CLASS='external-html' DATA-FILE-ID=JR_CLASS> 142 * @return An instance of the class {@code 'T'}, which is specified by {@code 'c'} 143 * @throws JsonException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JEX_REFL> 144 */ 145 public static <T> T getObject(JsonObject jo, Class<T> c) 146 { 147 Constructor<T> ctor = null; 148 149 try 150 { 151 // This just gets a "Constructor" using Reflection. The main point is that the 152 // Constructor must have exactly one parameter - and that parameter must have a 153 // type "JsonObject" (basic java.lang.reflect stuff) 154 // 155 //System.out.println("c.getName:():" + c.getName()); 156 157 ctor = c.getDeclaredConstructor(JsonObject.class); 158 } 159 catch (Exception e) 160 { 161 if (c.getEnclosingClass() != null) 162 { 163 int modifiers = c.getModifiers(); 164 165 if ((! Modifier.isStatic(modifiers)) || (! Modifier.isPublic(modifiers))) 166 167 throw new JsonException( 168 "Unable to retrieve POJO Constructor for class: " + 169 "[" + c.getName() + "]\n" + 170 "Your class appears to be a Nested-Class, however it has not been " + 171 "declared public and static. There is no way to retrieve a " + 172 "1-Argument JsonObject Constructor for Nested-Type's unless the " + 173 "type has been declared BOTH static AND public.\n" + 174 "See Exception.getCause() for details.", 175 e 176 ); 177 } 178 179 else throw new JsonException( 180 "Unable to retrieve POJO Constructor for class: [" + c.getName() + "]\n" + 181 "Do you have a one-argument, public, constructor for this class?\n" + 182 "Does it accept a JsonObject in its parameter list?\n" + 183 "See Exception.getCause() for details.", 184 e 185 ); 186 } 187 188 // If the user has requested a class that doesn't have that kind of constructor, then 189 // there is no way to build the object. Throw an exception. 190 191 if (ctor == null) throw new JsonException( 192 "The class which was passed to parameter 'c' [" + c.getName() + "] does not " + 193 "appear to have a constructor with precisely one parameter of type JsonObject." 194 ); 195 196 // Call that constructor using the parameter 'jo', and return that instance / object 197 try 198 { return ctor.newInstance(jo); } 199 200 // NOTE: There are *MANY* possible Exception's that may be thrown by reflective 201 // operations like those above. Furthermore, they are *ALL* checked exceptions. 202 // The code below wraps those exceptions into an UN-CHECKED / RuntimeException 203 // (JsonException) 204 205 catch (Exception e) 206 { 207 throw new JsonException( 208 "Unable to instantiate class: [" + c.getName() + "] using provided " + 209 "Java-Object\n" + 210 "See Exception.getCause() for details.", 211 e 212 ); 213 } 214 } 215 216 217 // ******************************************************************************************** 218 // ******************************************************************************************** 219 // String 220 // ******************************************************************************************** 221 // ******************************************************************************************** 222 223 224 /** 225 * Retrieve a {@link JsonArray} element, and transform it to a {@code java.lang.String}. 226 * <EMBED CLASS=defs DATA-JTYPE=JsonString DATA-TYPE='java.lang.String'> 227 * 228 * @param ja Any instance of {@link JsonArray} 229 * @param index <EMBED CLASS='external-html' DATA-FILE-ID=JR_INDEX_JA> 230 * @param throwOnNull <EMBED CLASS='external-html' DATA-FILE-ID=JR_TON_JA> 231 * @return <EMBED CLASS='external-html' DATA-FILE-ID=JR_RET_TON_JA> 232 * 233 * @throws IndexOutOfBoundsException If {@code 'index'} is out of the bounds of {@code 'ja'} 234 * @throws JsonTypeArrException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JTAEX> 235 * @throws JsonNullArrException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JNAEX> 236 * 237 * @see JsonValue#getValueType() 238 * @see JsonValue.ValueType#STRING 239 */ 240 public static String getString(JsonArray ja, int index, boolean throwOnNull) 241 { 242 // This will throw an IndexOutOfBoundsException if the index is out of bounds. 243 JsonValue jv = ja.get(index); 244 245 switch (jv.getValueType()) 246 { 247 case NULL: 248 249 // This is simple-stuff (not rocket-science). "Type Mapping" Code has to worry 250 // about what the meaning of "null" should be. 251 252 if (throwOnNull) throw new JsonNullArrException(ja, index, STRING, String.class); 253 else return null; 254 255 case STRING: return ((JsonString) jv).getString(); 256 257 // The JsonValue at the specified array-index does not contain a JsonString. 258 default: throw new JsonTypeArrException(ja, index, STRING, jv, String.class); 259 } 260 } 261 262 /** 263 * Extract a {@link JsonObject} property, and transform it to a {@code java.lang.String}. 264 * <EMBED CLASS=defs DATA-TYPE='java.lang.String' DATA-JTYPE=JsonString> 265 * 266 * @param jo Any instance of {@link JsonObject}. 267 * @param propertyName <EMBED CLASS='external-html' DATA-FILE-ID=JR_PN_JO> 268 * @param isOptional <EMBED CLASS='external-html' DATA-FILE-ID=JR_ISOPT_JO> 269 * @param throwOnNull <EMBED CLASS='external-html' DATA-FILE-ID=JR_TON_JO> 270 * @return <EMBED CLASS='external-html' DATA-FILE-ID=JR_RET_TON_JO> 271 * 272 * @throws JsonPropMissingException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JPMEX> 273 * @throws JsonTypeObjException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JTOEX> 274 * @throws JsonNullObjException <EMBED CLASS='external-html' DATA-FILE-ID=JR_JNOEX> 275 * 276 * @see JsonValue#getValueType() 277 * @see JsonValue.ValueType#STRING 278 */ 279 public static String getString 280 (JsonObject jo, String propertyName, boolean isOptional, boolean throwOnNull) 281 { 282 if (! jo.containsKey(propertyName)) 283 { 284 if (isOptional) return null; 285 286 throw new JsonPropMissingException(jo, propertyName, STRING, String.class); 287 } 288 289 JsonValue jv = jo.get(propertyName); 290 291 switch (jv.getValueType()) 292 { 293 case NULL: 294 295 // This is simple-stuff (not rocket-science). "Type Mapping" Code has to worry 296 // about what the meaning of "null" should be. 297 298 if (throwOnNull) throw new JsonNullObjException 299 (jo, propertyName, STRING, String.class); 300 301 else return null; 302 303 case STRING: return ((JsonString) jv).getString(); 304 305 // The JsonObject propertydoes not contain a JsonString. 306 default: throw new JsonTypeObjException(jo, propertyName, STRING, jv, String.class); 307 } 308 } 309}