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   // Predefined subclasses
263   //-------------------------------------------------------------------------------------------------------------------
264
265   /** Default serializer, with whitespace. */
266   public static class Readable extends JsonSerializer {
267
268      /**
269       * Constructor.
270       *
271       * @param ps The property store containing all the settings for this object.
272       */
273      public Readable(PropertyStore ps) {
274         super(
275            ps.builder().set(SERIALIZER_useWhitespace, true).build()
276         );
277      }
278   }
279
280   /**
281    * Default serializer, single quotes, simple mode, with whitespace and recursion detection.
282    * Note that recursion detection introduces a small performance penalty.
283    */
284   public static class ReadableSafe extends JsonSerializer {
285
286      /**
287       * Constructor.
288       *
289       * @param ps The property store containing all the settings for this object.
290       */
291      public ReadableSafe(PropertyStore ps) {
292         super(
293            ps.builder()
294               .set(JSON_simpleMode, true)
295               .set(WSERIALIZER_quoteChar, '\'')
296               .set(SERIALIZER_useWhitespace, true)
297               .set(BEANTRAVERSE_detectRecursions, true)
298               .build()
299         );
300      }
301   }
302
303
304   //-------------------------------------------------------------------------------------------------------------------
305   // Instance
306   //-------------------------------------------------------------------------------------------------------------------
307
308   private final boolean
309      simpleMode,
310      escapeSolidus,
311      addBeanTypes;
312
313   private volatile JsonSchemaSerializer schemaSerializer;
314
315   /**
316    * Constructor.
317    *
318    * @param ps
319    *    The property store containing all the settings for this object.
320    */
321   public JsonSerializer(PropertyStore ps) {
322      this(ps, "application/json", "application/json,text/json");
323   }
324
325   /**
326    * Constructor.
327    *
328    * @param ps
329    *    The property store containing all the settings for this object.
330    * @param produces
331    *    The media type that this serializer produces.
332    * @param accept
333    *    The accept media types that the serializer can handle.
334    *    <p>
335    *    Can contain meta-characters per the <code>media-type</code> specification of {@doc RFC2616.section14.1}
336    *    <p>
337    *    If empty, then assumes the only media type supported is <code>produces</code>.
338    *    <p>
339    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
340    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
341    *    <p class='bcode w800'>
342    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>);
343    *    </p>
344    *    <br>...or...
345    *    <p class='bcode w800'>
346    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
347    *    </p>
348    * <p>
349    * The accept value can also contain q-values.
350    */
351   public JsonSerializer(PropertyStore ps, String produces, String accept) {
352      super(ps, produces, accept);
353
354      simpleMode = getBooleanProperty(JSON_simpleMode, false);
355      escapeSolidus = getBooleanProperty(JSON_escapeSolidus, false);
356      addBeanTypes = getBooleanProperty(JSON_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false));
357   }
358
359   @Override /* Context */
360   public JsonSerializerBuilder builder() {
361      return new JsonSerializerBuilder(getPropertyStore());
362   }
363
364   /**
365    * Instantiates a new clean-slate {@link JsonSerializerBuilder} object.
366    *
367    * <p>
368    * This is equivalent to simply calling <code><jk>new</jk> JsonSerializerBuilder()</code>.
369    *
370    * @return A new {@link JsonSerializerBuilder} object.
371    */
372   public static JsonSerializerBuilder create() {
373      return new JsonSerializerBuilder();
374   }
375
376   /**
377    * Returns the schema serializer based on the settings of this serializer.
378    *
379    * <p>
380    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
381    * the settings of the object called on.
382    *
383    * @return The schema serializer.
384    */
385   public JsonSchemaSerializer getSchemaSerializer() {
386      if (schemaSerializer == null)
387         schemaSerializer = builder().build(JsonSchemaSerializer.class);
388      return schemaSerializer;
389   }
390
391   //-----------------------------------------------------------------------------------------------------------------
392   // Entry point methods
393   //-----------------------------------------------------------------------------------------------------------------
394
395   @Override /* Serializer */
396   public JsonSerializerSession createSession(SerializerSessionArgs args) {
397      return new JsonSerializerSession(this, args);
398   }
399
400   //-----------------------------------------------------------------------------------------------------------------
401   // Properties
402   //-----------------------------------------------------------------------------------------------------------------
403
404   /**
405    * Configuration property:  Simple JSON mode.
406    *
407    * @see #JSON_simpleMode
408    * @return
409    *    <jk>true</jk> if JSON attribute names will only be quoted when necessary.
410    *    <br>Otherwise, they are always quoted.
411    */
412   protected final boolean isSimpleMode() {
413      return simpleMode;
414   }
415
416   /**
417    * Configuration property:  Prefix solidus <js>'/'</js> characters with escapes.
418    *
419    * @see #JSON_escapeSolidus
420    * @return
421    *    <jk>true</jk> if solidus (e.g. slash) characters should be escaped.
422    */
423   protected final boolean isEscapeSolidus() {
424      return escapeSolidus;
425   }
426
427   /**
428    * Configuration property:  Add <js>"_type"</js> properties when needed.
429    *
430    * @see #JSON_addBeanTypes
431    * @return
432    *    <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
433    *    through reflection.
434    */
435   @Override
436   protected final boolean isAddBeanTypes() {
437      return addBeanTypes;
438   }
439
440   @Override /* Context */
441   public ObjectMap asMap() {
442      return super.asMap()
443         .append("JsonSerializer", new ObjectMap()
444            .append("simpleMode", simpleMode)
445            .append("escapeSolidus", escapeSolidus)
446            .append("addBeanTypes", addBeanTypes)
447         );
448   }
449}