001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.json; 014 015import java.util.*; 016 017import org.apache.juneau.*; 018import org.apache.juneau.annotation.*; 019import org.apache.juneau.serializer.*; 020 021/** 022 * Serializes POJO models to JSON. 023 * 024 * <h5 class='topic'>Media types</h5> 025 * 026 * Handles <c>Accept</c> types: <bc>application/json, text/json</bc> 027 * <p> 028 * Produces <c>Content-Type</c> types: <bc>application/json</bc> 029 * 030 * <h5 class='topic'>Description</h5> 031 * 032 * The conversion is as follows... 033 * <ul class='spaced-list'> 034 * <li> 035 * Maps (e.g. {@link HashMap HashMaps}, {@link TreeMap TreeMaps}) are converted to JSON objects. 036 * <li> 037 * Collections (e.g. {@link HashSet HashSets}, {@link LinkedList LinkedLists}) and Java arrays are converted to 038 * JSON arrays. 039 * <li> 040 * {@link String Strings} are converted to JSON strings. 041 * <li> 042 * {@link Number Numbers} (e.g. {@link Integer}, {@link Long}, {@link Double}) are converted to JSON numbers. 043 * <li> 044 * {@link Boolean Booleans} are converted to JSON booleans. 045 * <li> 046 * {@code nulls} are converted to JSON nulls. 047 * <li> 048 * {@code arrays} are converted to JSON arrays. 049 * <li> 050 * {@code beans} are converted to JSON objects. 051 * </ul> 052 * 053 * <p> 054 * The types above are considered "JSON-primitive" object types. 055 * Any non-JSON-primitive object types are transformed into JSON-primitive object types through 056 * {@link org.apache.juneau.transform.PojoSwap PojoSwaps} associated through the 057 * {@link BeanContextBuilder#pojoSwaps(Class...)} method. 058 * Several default transforms are provided for transforming Dates, Enums, Iterators, etc... 059 * 060 * <p> 061 * This serializer provides several serialization options. 062 * Typically, one of the predefined DEFAULT serializers will be sufficient. 063 * However, custom serializers can be constructed to fine-tune behavior. 064 * 065 * <h5 class='topic'>Behavior-specific subclasses</h5> 066 * 067 * The following direct subclasses are provided for convenience: 068 * <ul class='spaced-list'> 069 * <li> 070 * {@link SimpleJsonSerializer} - Default serializer, single quotes, simple mode. 071 * <li> 072 * {@link SimpleJsonSerializer.Readable} - Default serializer, single quotes, simple mode, with whitespace. 073 * </ul> 074 * 075 * <h5 class='section'>Example:</h5> 076 * <p class='bcode w800'> 077 * <jc>// Use one of the default serializers to serialize a POJO</jc> 078 * String json = JsonSerializer.<jsf>DEFAULT</jsf>.serialize(someObject); 079 * 080 * <jc>// Create a custom serializer for lax syntax using single quote characters</jc> 081 * JsonSerializer serializer = JsonSerializer.<jsm>create</jsm>().simple().sq().build(); 082 * 083 * <jc>// Clone an existing serializer and modify it to use single-quotes</jc> 084 * JsonSerializer serializer = JsonSerializer.<jsf>DEFAULT</jsf>.builder().sq().build(); 085 * 086 * <jc>// Serialize a POJO to JSON</jc> 087 * String json = serializer.serialize(someObject); 088 * </p> 089 */ 090@ConfigurableContext 091public class JsonSerializer extends WriterSerializer { 092 093 //------------------------------------------------------------------------------------------------------------------- 094 // Configurable properties 095 //------------------------------------------------------------------------------------------------------------------- 096 097 static final String PREFIX = "JsonSerializer"; 098 099 /** 100 * Configuration property: Add <js>"_type"</js> properties when needed. 101 * 102 * <h5 class='section'>Property:</h5> 103 * <ul> 104 * <li><b>Name:</b> <js>"JsonSerializer.addBeanTypes.b"</js> 105 * <li><b>Data type:</b> <c>Boolean</c> 106 * <li><b>Default:</b> <jk>false</jk> 107 * <li><b>Session property:</b> <jk>false</jk> 108 * <li><b>Methods:</b> 109 * <ul> 110 * <li class='jm'>{@link JsonSerializerBuilder#addBeanTypes(boolean)} 111 * </ul> 112 * </ul> 113 * 114 * <h5 class='section'>Description:</h5> 115 * <p> 116 * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred 117 * through reflection. 118 * 119 * <p> 120 * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is 121 * provided to customize the behavior of specific serializers in a {@link SerializerGroup}. 122 */ 123 public static final String JSON_addBeanTypes = PREFIX + ".addBeanTypes.b"; 124 125 /** 126 * Configuration property: Prefix solidus <js>'/'</js> characters with escapes. 127 * 128 * <h5 class='section'>Property:</h5> 129 * <ul> 130 * <li><b>Name:</b> <js>"JsonSerializer.escapeSolidus.b"</js> 131 * <li><b>Data type:</b> <c>Boolean</c> 132 * <li><b>Default:</b> <jk>false</jk> 133 * <li><b>Session property:</b> <jk>false</jk> 134 * <li><b>Methods:</b> 135 * <ul> 136 * <li class='jm'>{@link JsonSerializerBuilder#escapeSolidus(boolean)} 137 * <li class='jm'>{@link JsonSerializerBuilder#escapeSolidus()} 138 * </ul> 139 * </ul> 140 * 141 * <h5 class='section'>Description:</h5> 142 * <p> 143 * If <jk>true</jk>, solidus (e.g. slash) characters should be escaped. 144 * The JSON specification allows for either format. 145 * <br>However, if you're embedding JSON in an HTML script tag, this setting prevents confusion when trying to serialize 146 * <xt><\/script></xt>. 147 * 148 * <h5 class='section'>Example:</h5> 149 * <p class='bcode w800'> 150 * <jc>// Create a JSON serializer that escapes solidus characters.</jc> 151 * WriterSerializer s = JsonSerializer 152 * .<jsm>create</jsm>() 153 * .simple() 154 * .escapeSolidus() 155 * .build(); 156 * 157 * <jc>// Same, but use property.</jc> 158 * WriterSerializer s = JsonSerializer 159 * .<jsm>create</jsm>() 160 * .simple() 161 * .set(<jsf>JSON_escapeSolidus</jsf>, <jk>true</jk>) 162 * .build(); 163 * 164 * <jc>// Produces: "{foo:'<\/bar>'"</jc> 165 * String json = s.serialize(<jk>new</jk> ObjectMap().append(<js>"foo"</js>, <js>"</bar>"</js>); 166 * </p> 167 */ 168 public static final String JSON_escapeSolidus = PREFIX + ".escapeSolidus.b"; 169 170 /** 171 * Configuration property: Simple JSON mode. 172 * 173 * <h5 class='section'>Property:</h5> 174 * <ul> 175 * <li><b>Name:</b> <js>"JsonSerializer.simpleMode.b"</js> 176 * <li><b>Data type:</b> <c>Boolean</c> 177 * <li><b>Default:</b> <jk>false</jk> 178 * <li><b>Session property:</b> <jk>false</jk> 179 * <li><b>Methods:</b> 180 * <ul> 181 * <li class='jm'>{@link JsonSerializerBuilder#simple(boolean)} 182 * <li class='jm'>{@link JsonSerializerBuilder#simple()} 183 * <li class='jm'>{@link JsonSerializerBuilder#ssq()} 184 * </ul> 185 * </ul> 186 * 187 * <h5 class='section'>Description:</h5> 188 * <p> 189 * If <jk>true</jk>, JSON attribute names will only be quoted when necessary. 190 * <br>Otherwise, they are always quoted. 191 * 192 * <p> 193 * Attributes do not need to be quoted when they conform to the following: 194 * <ol class='spaced-list'> 195 * <li>They start with an ASCII character or <js>'_'</js>. 196 * <li>They contain only ASCII characters or numbers or <js>'_'</js>. 197 * <li>They are not one of the following reserved words: 198 * <p class='bcode w800'> 199 * arguments, break, case, catch, class, const, continue, debugger, default, 200 * delete, do, else, enum, eval, export, extends, false, finally, for, function, 201 * if, implements, import, in, instanceof, interface, let, new, null, package, 202 * private, protected, public, return, static, super, switch, this, throw, 203 * true, try, typeof, var, void, while, with, undefined, yield 204 * </p> 205 * </ol> 206 * 207 * <h5 class='section'>Example:</h5> 208 * <p class='bcode w800'> 209 * <jc>// Create a JSON serializer in normal mode.</jc> 210 * WriterSerializer s1 = JsonSerializer 211 * .<jsm>create</jsm>() 212 * .build(); 213 * 214 * <jc>// Create a JSON serializer in simple mode.</jc> 215 * WriterSerializer s2 = JsonSerializer 216 * .<jsm>create</jsm>() 217 * .simple() 218 * .build(); 219 * 220 * ObjectMap m = <jk>new</jk> ObjectMap() 221 * .append(<js>"foo"</js>, <js>"x1"</js>) 222 * .append(<js>"_bar"</js>, <js>"x2"</js>) 223 * .append(<js>" baz "</js>, <js>"x3"</js>) 224 * .append(<js>"123"</js>, <js>"x4"</js>) 225 * .append(<js>"return"</js>, <js>"x5"</js>); 226 * .append(<js>""</js>, <js>"x6"</js>); 227 * 228 * <jc>// Produces:</jc> 229 * <jc>// {</jc> 230 * <jc>// "foo": "x1"</jc> 231 * <jc>// "_bar": "x2"</jc> 232 * <jc>// " baz ": "x3"</jc> 233 * <jc>// "123": "x4"</jc> 234 * <jc>// "return": "x5"</jc> 235 * <jc>// "": "x6"</jc> 236 * <jc>// }</jc> 237 * String json1 = s1.serialize(m); 238 * 239 * <jc>// Produces:</jc> 240 * <jc>// {</jc> 241 * <jc>// foo: "x1"</jc> 242 * <jc>// _bar: "x2"</jc> 243 * <jc>// " baz ": "x3"</jc> 244 * <jc>// "123": "x4"</jc> 245 * <jc>// "return": "x5"</jc> 246 * <jc>// "": "x6"</jc> 247 * <jc>// }</jc> 248 * String json2 = s2.serialize(m); 249 * </p> 250 */ 251 public static final String JSON_simpleMode = PREFIX + ".simpleMode.b"; 252 253 //------------------------------------------------------------------------------------------------------------------- 254 // Predefined instances 255 //------------------------------------------------------------------------------------------------------------------- 256 257 /** Default serializer, all default settings.*/ 258 public static final JsonSerializer DEFAULT = new JsonSerializer(PropertyStore.DEFAULT); 259 260 /** Default serializer, all default settings.*/ 261 public static final JsonSerializer DEFAULT_READABLE = new Readable(PropertyStore.DEFAULT); 262 263 264 //------------------------------------------------------------------------------------------------------------------- 265 // Predefined subclasses 266 //------------------------------------------------------------------------------------------------------------------- 267 268 /** Default serializer, with whitespace. */ 269 public static class Readable extends JsonSerializer { 270 271 /** 272 * Constructor. 273 * 274 * @param ps The property store containing all the settings for this object. 275 */ 276 public Readable(PropertyStore ps) { 277 super( 278 ps.builder().set(WSERIALIZER_useWhitespace, true).build() 279 ); 280 } 281 } 282 283 /** 284 * Default serializer, single quotes, simple mode, with whitespace and recursion detection. 285 * Note that recursion detection introduces a small performance penalty. 286 */ 287 public static class ReadableSafe extends JsonSerializer { 288 289 /** 290 * Constructor. 291 * 292 * @param ps The property store containing all the settings for this object. 293 */ 294 public ReadableSafe(PropertyStore ps) { 295 super( 296 ps.builder() 297 .set(JSON_simpleMode, true) 298 .set(WSERIALIZER_quoteChar, '\'') 299 .set(WSERIALIZER_useWhitespace, true) 300 .set(BEANTRAVERSE_detectRecursions, true) 301 .build() 302 ); 303 } 304 } 305 306 307 //------------------------------------------------------------------------------------------------------------------- 308 // Instance 309 //------------------------------------------------------------------------------------------------------------------- 310 311 private final boolean 312 simpleMode, 313 escapeSolidus, 314 addBeanTypes; 315 316 private volatile JsonSchemaSerializer schemaSerializer; 317 318 /** 319 * Constructor. 320 * 321 * @param ps 322 * The property store containing all the settings for this object. 323 */ 324 public JsonSerializer(PropertyStore ps) { 325 this(ps, "application/json", "application/json,text/json"); 326 } 327 328 /** 329 * Constructor. 330 * 331 * @param ps 332 * The property store containing all the settings for this object. 333 * @param produces 334 * The media type that this serializer produces. 335 * @param accept 336 * The accept media types that the serializer can handle. 337 * <p> 338 * Can contain meta-characters per the <c>media-type</c> specification of {@doc RFC2616.section14.1} 339 * <p> 340 * If empty, then assumes the only media type supported is <c>produces</c>. 341 * <p> 342 * For example, if this serializer produces <js>"application/json"</js> but should handle media types of 343 * <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be: 344 * <p class='bcode w800'> 345 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>); 346 * </p> 347 * <br>...or... 348 * <p class='bcode w800'> 349 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*​/json"</js>); 350 * </p> 351 * <p> 352 * The accept value can also contain q-values. 353 */ 354 public JsonSerializer(PropertyStore ps, String produces, String accept) { 355 super(ps, produces, accept); 356 357 simpleMode = getBooleanProperty(JSON_simpleMode, false); 358 escapeSolidus = getBooleanProperty(JSON_escapeSolidus, false); 359 addBeanTypes = getBooleanProperty(JSON_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false)); 360 } 361 362 @Override /* Context */ 363 public JsonSerializerBuilder builder() { 364 return new JsonSerializerBuilder(getPropertyStore()); 365 } 366 367 /** 368 * Instantiates a new clean-slate {@link JsonSerializerBuilder} object. 369 * 370 * <p> 371 * This is equivalent to simply calling <code><jk>new</jk> JsonSerializerBuilder()</code>. 372 * 373 * @return A new {@link JsonSerializerBuilder} object. 374 */ 375 public static JsonSerializerBuilder create() { 376 return new JsonSerializerBuilder(); 377 } 378 379 /** 380 * Returns the schema serializer based on the settings of this serializer. 381 * 382 * <p> 383 * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 384 * the settings of the object called on. 385 * 386 * @return The schema serializer. 387 */ 388 public JsonSchemaSerializer getSchemaSerializer() { 389 if (schemaSerializer == null) 390 schemaSerializer = builder().build(JsonSchemaSerializer.class); 391 return schemaSerializer; 392 } 393 394 //----------------------------------------------------------------------------------------------------------------- 395 // Entry point methods 396 //----------------------------------------------------------------------------------------------------------------- 397 398 @Override /* Context */ 399 public JsonSerializerSession createSession() { 400 return createSession(createDefaultSessionArgs()); 401 } 402 403 @Override /* Serializer */ 404 public JsonSerializerSession createSession(SerializerSessionArgs args) { 405 return new JsonSerializerSession(this, args); 406 } 407 408 //----------------------------------------------------------------------------------------------------------------- 409 // Properties 410 //----------------------------------------------------------------------------------------------------------------- 411 412 /** 413 * Configuration property: Add <js>"_type"</js> properties when needed. 414 * 415 * @see #JSON_addBeanTypes 416 * @return 417 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 418 * through reflection. 419 */ 420 @Override 421 protected final boolean isAddBeanTypes() { 422 return addBeanTypes; 423 } 424 425 /** 426 * Configuration property: Prefix solidus <js>'/'</js> characters with escapes. 427 * 428 * @see #JSON_escapeSolidus 429 * @return 430 * <jk>true</jk> if solidus (e.g. slash) characters should be escaped. 431 */ 432 protected final boolean isEscapeSolidus() { 433 return escapeSolidus; 434 } 435 436 /** 437 * Configuration property: Simple JSON mode. 438 * 439 * @see #JSON_simpleMode 440 * @return 441 * <jk>true</jk> if JSON attribute names will only be quoted when necessary. 442 * <br>Otherwise, they are always quoted. 443 */ 444 protected final boolean isSimpleMode() { 445 return simpleMode; 446 } 447 448 //----------------------------------------------------------------------------------------------------------------- 449 // Other methods 450 //----------------------------------------------------------------------------------------------------------------- 451 452 @Override /* Context */ 453 public ObjectMap toMap() { 454 return super.toMap() 455 .append("JsonSerializer", new DefaultFilteringObjectMap() 456 .append("simpleMode", simpleMode) 457 .append("escapeSolidus", escapeSolidus) 458 .append("addBeanTypes", addBeanTypes) 459 ); 460 } 461}