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.internal.*; 019import org.apache.juneau.serializer.*; 020 021/** 022 * Serializes POJO models to JSON. 023 * 024 * <h5 class='topic'>Media types</h5> 025 * 026 * Handles <code>Accept</code> types: <code><b>application/json, text/json</b></code> 027 * <p> 028 * Produces <code>Content-Type</code> types: <code><b>application/json</b></code> 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 */ 090public class JsonSerializer extends WriterSerializer { 091 092 //------------------------------------------------------------------------------------------------------------------- 093 // Configurable properties 094 //------------------------------------------------------------------------------------------------------------------- 095 096 private static final String PREFIX = "JsonSerializer."; 097 098 /** 099 * Configuration property: Add <js>"_type"</js> properties when needed. 100 * 101 * <h5 class='section'>Property:</h5> 102 * <ul> 103 * <li><b>Name:</b> <js>"JsonSerializer.addBeanTypes.b"</js> 104 * <li><b>Data type:</b> <code>Boolean</code> 105 * <li><b>Default:</b> <jk>false</jk> 106 * <li><b>Session property:</b> <jk>false</jk> 107 * <li><b>Methods:</b> 108 * <ul> 109 * <li class='jm'>{@link JsonSerializerBuilder#addBeanTypes(boolean)} 110 * </ul> 111 * </ul> 112 * 113 * <h5 class='section'>Description:</h5> 114 * <p> 115 * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred 116 * through reflection. 117 * 118 * <p> 119 * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is 120 * provided to customize the behavior of specific serializers in a {@link SerializerGroup}. 121 */ 122 public static final String JSON_addBeanTypes = PREFIX + "addBeanTypes.b"; 123 124 /** 125 * Configuration property: Prefix solidus <js>'/'</js> characters with escapes. 126 * 127 * <h5 class='section'>Property:</h5> 128 * <ul> 129 * <li><b>Name:</b> <js>"JsonSerializer.escapeSolidus.b"</js> 130 * <li><b>Data type:</b> <code>Boolean</code> 131 * <li><b>Default:</b> <jk>false</jk> 132 * <li><b>Session property:</b> <jk>false</jk> 133 * <li><b>Methods:</b> 134 * <ul> 135 * <li class='jm'>{@link JsonSerializerBuilder#escapeSolidus(boolean)} 136 * <li class='jm'>{@link JsonSerializerBuilder#escapeSolidus()} 137 * </ul> 138 * </ul> 139 * 140 * <h5 class='section'>Description:</h5> 141 * <p> 142 * If <jk>true</jk>, solidus (e.g. slash) characters should be escaped. 143 * The JSON specification allows for either format. 144 * <br>However, if you're embedding JSON in an HTML script tag, this setting prevents confusion when trying to serialize 145 * <xt><\/script></xt>. 146 * 147 * <h5 class='section'>Example:</h5> 148 * <p class='bcode w800'> 149 * <jc>// Create a JSON serializer that escapes solidus characters.</jc> 150 * WriterSerializer s = JsonSerializer 151 * .<jsm>create</jsm>() 152 * .simple() 153 * .escapeSolidus() 154 * .build(); 155 * 156 * <jc>// Same, but use property.</jc> 157 * WriterSerializer s = JsonSerializer 158 * .<jsm>create</jsm>() 159 * .simple() 160 * .set(<jsf>JSON_escapeSolidus</jsf>, <jk>true</jk>) 161 * .build(); 162 * 163 * <jc>// Produces: "{foo:'<\/bar>'"</jc> 164 * String json = s.serialize(<jk>new</jk> ObjectMap().append(<js>"foo"</js>, <js>"</bar>"</js>); 165 * </p> 166 */ 167 public static final String JSON_escapeSolidus = PREFIX + "escapeSolidus.b"; 168 169 /** 170 * Configuration property: Simple JSON mode. 171 * 172 * <h5 class='section'>Property:</h5> 173 * <ul> 174 * <li><b>Name:</b> <js>"JsonSerializer.simpleMode.b"</js> 175 * <li><b>Data type:</b> <code>Boolean</code> 176 * <li><b>Default:</b> <jk>false</jk> 177 * <li><b>Session property:</b> <jk>false</jk> 178 * <li><b>Methods:</b> 179 * <ul> 180 * <li class='jm'>{@link JsonSerializerBuilder#simple(boolean)} 181 * <li class='jm'>{@link JsonSerializerBuilder#simple()} 182 * <li class='jm'>{@link JsonSerializerBuilder#ssq()} 183 * </ul> 184 * </ul> 185 * 186 * <h5 class='section'>Description:</h5> 187 * <p> 188 * If <jk>true</jk>, JSON attribute names will only be quoted when necessary. 189 * <br>Otherwise, they are always quoted. 190 * 191 * <p> 192 * Attributes do not need to be quoted when they conform to the following: 193 * <ol class='spaced-list'> 194 * <li>They start with an ASCII character or <js>'_'</js>. 195 * <li>They contain only ASCII characters or numbers or <js>'_'</js>. 196 * <li>They are not one of the following reserved words: 197 * <p class='bcode w800'> 198 * arguments, break, case, catch, class, const, continue, debugger, default, 199 * delete, do, else, enum, eval, export, extends, false, finally, for, function, 200 * if, implements, import, in, instanceof, interface, let, new, null, package, 201 * private, protected, public, return, static, super, switch, this, throw, 202 * true, try, typeof, var, void, while, with, undefined, yield 203 * </p> 204 * </ol> 205 * 206 * <h5 class='section'>Example:</h5> 207 * <p class='bcode w800'> 208 * <jc>// Create a JSON serializer in normal mode.</jc> 209 * WriterSerializer s1 = JsonSerializer 210 * .<jsm>create</jsm>() 211 * .build(); 212 * 213 * <jc>// Create a JSON serializer in simple mode.</jc> 214 * WriterSerializer s2 = JsonSerializer 215 * .<jsm>create</jsm>() 216 * .simple() 217 * .build(); 218 * 219 * ObjectMap m = <jk>new</jk> ObjectMap() 220 * .append(<js>"foo"</js>, <js>"x1"</js>) 221 * .append(<js>"_bar"</js>, <js>"x2"</js>) 222 * .append(<js>" baz "</js>, <js>"x3"</js>) 223 * .append(<js>"123"</js>, <js>"x4"</js>) 224 * .append(<js>"return"</js>, <js>"x5"</js>); 225 * .append(<js>""</js>, <js>"x6"</js>); 226 * 227 * <jc>// Produces:</jc> 228 * <jc>// {</jc> 229 * <jc>// "foo": "x1"</jc> 230 * <jc>// "_bar": "x2"</jc> 231 * <jc>// " baz ": "x3"</jc> 232 * <jc>// "123": "x4"</jc> 233 * <jc>// "return": "x5"</jc> 234 * <jc>// "": "x6"</jc> 235 * <jc>// }</jc> 236 * String json1 = s1.serialize(m); 237 * 238 * <jc>// Produces:</jc> 239 * <jc>// {</jc> 240 * <jc>// foo: "x1"</jc> 241 * <jc>// _bar: "x2"</jc> 242 * <jc>// " baz ": "x3"</jc> 243 * <jc>// "123": "x4"</jc> 244 * <jc>// "return": "x5"</jc> 245 * <jc>// "": "x6"</jc> 246 * <jc>// }</jc> 247 * String json2 = s2.serialize(m); 248 * </p> 249 */ 250 public static final String JSON_simpleMode = PREFIX + "simpleMode.b"; 251 252 //------------------------------------------------------------------------------------------------------------------- 253 // Predefined instances 254 //------------------------------------------------------------------------------------------------------------------- 255 256 /** Default serializer, all default settings.*/ 257 public static final JsonSerializer DEFAULT = new JsonSerializer(PropertyStore.DEFAULT); 258 259 /** Default serializer, all default settings.*/ 260 public static final JsonSerializer DEFAULT_READABLE = new Readable(PropertyStore.DEFAULT); 261 262 /** @deprecated Use {@link SimpleJsonSerializer#DEFAULT} */ 263 @Deprecated 264 public static final JsonSerializer DEFAULT_LAX = new SimpleJsonSerializer(PropertyStore.DEFAULT); 265 266 /** @deprecated Use {@link SimpleJsonSerializer#DEFAULT_READABLE} */ 267 @Deprecated 268 public static final JsonSerializer DEFAULT_LAX_READABLE = new SimpleJsonSerializer.Readable(PropertyStore.DEFAULT); 269 270 /** @deprecated Use {@link SimpleJsonSerializer#DEFAULT_READABLE} */ 271 @Deprecated 272 public static final JsonSerializer DEFAULT_LAX_READABLE_SAFE = new SimpleJsonSerializer.ReadableSafe(PropertyStore.DEFAULT); 273 274 275 //------------------------------------------------------------------------------------------------------------------- 276 // Predefined subclasses 277 //------------------------------------------------------------------------------------------------------------------- 278 279 /** Default serializer, with whitespace. */ 280 public static class Readable extends JsonSerializer { 281 282 /** 283 * Constructor. 284 * 285 * @param ps The property store containing all the settings for this object. 286 */ 287 public Readable(PropertyStore ps) { 288 super( 289 ps.builder().set(SERIALIZER_useWhitespace, true).build() 290 ); 291 } 292 } 293 294 /** @deprecated Use {@link SimpleJsonSerializer} */ 295 @Deprecated 296 public static class Simple extends JsonSerializer { 297 298 /** 299 * Constructor. 300 * 301 * @param ps The property store containing all the settings for this object. 302 */ 303 public Simple(PropertyStore ps) { 304 super( 305 ps.builder() 306 .set(JSON_simpleMode, true) 307 .set(WSERIALIZER_quoteChar, '\'') 308 .build(), 309 "application/json", 310 "application/json+simple,text/json+simple" 311 ); 312 } 313 } 314 315 /** @deprecated Use {@link SimpleJsonSerializer.Readable} */ 316 @Deprecated 317 public static class SimpleReadable extends JsonSerializer { 318 319 /** 320 * Constructor. 321 * 322 * @param ps The property store containing all the settings for this object. 323 */ 324 public SimpleReadable(PropertyStore ps) { 325 super( 326 ps.builder() 327 .set(JSON_simpleMode, true) 328 .set(WSERIALIZER_quoteChar, '\'') 329 .set(SERIALIZER_useWhitespace, true) 330 .build() 331 ); 332 } 333 } 334 335 /** @deprecated Use {@link SimpleJsonSerializer.Readable} */ 336 @Deprecated 337 public static class SimpleReadableSafe extends JsonSerializer { 338 339 /** 340 * Constructor. 341 * 342 * @param ps The property store containing all the settings for this object. 343 */ 344 public SimpleReadableSafe(PropertyStore ps) { 345 super( 346 ps.builder() 347 .set(JSON_simpleMode, true) 348 .set(WSERIALIZER_quoteChar, '\'') 349 .set(SERIALIZER_useWhitespace, true) 350 .set(BEANTRAVERSE_detectRecursions, true) 351 .build() 352 ); 353 } 354 } 355 356 /** 357 * Default serializer, single quotes, simple mode, with whitespace and recursion detection. 358 * Note that recursion detection introduces a small performance penalty. 359 */ 360 public static class ReadableSafe extends JsonSerializer { 361 362 /** 363 * Constructor. 364 * 365 * @param ps The property store containing all the settings for this object. 366 */ 367 public ReadableSafe(PropertyStore ps) { 368 super( 369 ps.builder() 370 .set(JSON_simpleMode, true) 371 .set(WSERIALIZER_quoteChar, '\'') 372 .set(SERIALIZER_useWhitespace, true) 373 .set(BEANTRAVERSE_detectRecursions, true) 374 .build() 375 ); 376 } 377 } 378 379 380 //------------------------------------------------------------------------------------------------------------------- 381 // Instance 382 //------------------------------------------------------------------------------------------------------------------- 383 384 private final boolean 385 simpleMode, 386 escapeSolidus, 387 addBeanTypes; 388 389 private volatile JsonSchemaSerializer schemaSerializer; 390 391 /** 392 * Constructor. 393 * 394 * @param ps 395 * The property store containing all the settings for this object. 396 */ 397 public JsonSerializer(PropertyStore ps) { 398 this(ps, "application/json", "application/json,text/json"); 399 } 400 401 /** 402 * Constructor. 403 * 404 * @param ps 405 * The property store containing all the settings for this object. 406 * @param produces 407 * The media type that this serializer produces. 408 * @param accept 409 * The accept media types that the serializer can handle. 410 * <p> 411 * Can contain meta-characters per the <code>media-type</code> specification of {@doc RFC2616.section14.1} 412 * <p> 413 * If empty, then assumes the only media type supported is <code>produces</code>. 414 * <p> 415 * For example, if this serializer produces <js>"application/json"</js> but should handle media types of 416 * <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be: 417 * <p class='bcode w800'> 418 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>); 419 * </p> 420 * <br>...or... 421 * <p class='bcode w800'> 422 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*​/json"</js>); 423 * </p> 424 * <p> 425 * The accept value can also contain q-values. 426 */ 427 public JsonSerializer(PropertyStore ps, String produces, String accept) { 428 super(ps, produces, accept); 429 430 simpleMode = getBooleanProperty(JSON_simpleMode, false); 431 escapeSolidus = getBooleanProperty(JSON_escapeSolidus, false); 432 addBeanTypes = getBooleanProperty(JSON_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false)); 433 } 434 435 @Override /* Context */ 436 public JsonSerializerBuilder builder() { 437 return new JsonSerializerBuilder(getPropertyStore()); 438 } 439 440 /** 441 * Instantiates a new clean-slate {@link JsonSerializerBuilder} object. 442 * 443 * <p> 444 * This is equivalent to simply calling <code><jk>new</jk> JsonSerializerBuilder()</code>. 445 * 446 * @return A new {@link JsonSerializerBuilder} object. 447 */ 448 public static JsonSerializerBuilder create() { 449 return new JsonSerializerBuilder(); 450 } 451 452 /** 453 * Returns the schema serializer based on the settings of this serializer. 454 * 455 * <p> 456 * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 457 * the settings of the object called on. 458 * 459 * @return The schema serializer. 460 */ 461 public JsonSchemaSerializer getSchemaSerializer() { 462 if (schemaSerializer == null) 463 schemaSerializer = builder().build(JsonSchemaSerializer.class); 464 return schemaSerializer; 465 } 466 467 //----------------------------------------------------------------------------------------------------------------- 468 // Entry point methods 469 //----------------------------------------------------------------------------------------------------------------- 470 471 @Override /* Serializer */ 472 public JsonSerializerSession createSession(SerializerSessionArgs args) { 473 return new JsonSerializerSession(this, args); 474 } 475 476 //----------------------------------------------------------------------------------------------------------------- 477 // Properties 478 //----------------------------------------------------------------------------------------------------------------- 479 480 /** 481 * Configuration property: Simple JSON mode. 482 * 483 * @see #JSON_simpleMode 484 * @return 485 * <jk>true</jk> if JSON attribute names will only be quoted when necessary. 486 * <br>Otherwise, they are always quoted. 487 */ 488 protected final boolean isSimpleMode() { 489 return simpleMode; 490 } 491 492 /** 493 * Configuration property: Prefix solidus <js>'/'</js> characters with escapes. 494 * 495 * @see #JSON_escapeSolidus 496 * @return 497 * <jk>true</jk> if solidus (e.g. slash) characters should be escaped. 498 */ 499 protected final boolean isEscapeSolidus() { 500 return escapeSolidus; 501 } 502 503 /** 504 * Configuration property: Add <js>"_type"</js> properties when needed. 505 * 506 * @see #JSON_addBeanTypes 507 * @return 508 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 509 * through reflection. 510 */ 511 @Override 512 protected final boolean isAddBeanTypes() { 513 return addBeanTypes; 514 } 515 516 @Override /* Context */ 517 public ObjectMap asMap() { 518 return super.asMap() 519 .append("JsonSerializer", new ObjectMap() 520 .append("simpleMode", simpleMode) 521 .append("escapeSolidus", escapeSolidus) 522 .append("addBeanTypes", addBeanTypes) 523 ); 524 } 525 526 /** 527 * @deprecated Use {@link #JsonSerializer(PropertyStore, String, String)} 528 */ 529 @SuppressWarnings("javadoc") 530 @Deprecated 531 public JsonSerializer(PropertyStore ps, String produces, String...accept) { 532 this(ps, produces, StringUtils.join(accept, ',')); 533 } 534 535 /** 536 * @deprecated Use {@link #JSON_addBeanTypes} 537 */ 538 @Deprecated 539 public static final String JSON_addBeanTypeProperties = JSON_addBeanTypes; 540 541}