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