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 Simple} - Default serializer, single quotes, simple mode.
070 *    <li>
071 *       {@link SimpleReadable} - Default serializer, single quotes, simple mode, with whitespace.
072 * </ul>
073 * 
074 * <h5 class='section'>Example:</h5>
075 * <p class='bcode'>
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.addBeanTypeProperties.b"</js>
103    *    <li><b>Data type:</b>  <code>Boolean</code>
104    *    <li><b>Default:</b>  <jk>false</jk>
105    *    <li><b>Session-overridable:</b>  <jk>true</jk>
106    *    <li><b>Methods:</b> 
107    *       <ul>
108    *          <li class='jm'>{@link JsonSerializerBuilder#addBeanTypeProperties(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_addBeanTypeProperties} setting and is
119    * provided to customize the behavior of specific serializers in a {@link SerializerGroup}.
120    */
121   public static final String JSON_addBeanTypeProperties = PREFIX + "addBeanTypeProperties.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-overridable:</b>  <jk>true</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'>
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-overridable:</b>  <jk>true</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'>
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'>
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   //-------------------------------------------------------------------------------------------------------------------
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   /** Default serializer, single quotes, simple mode. */
263   public static final JsonSerializer DEFAULT_LAX = new Simple(PropertyStore.DEFAULT);
264
265   /** Default serializer, single quotes, simple mode, with whitespace. */
266   public static final JsonSerializer DEFAULT_LAX_READABLE = new SimpleReadable(PropertyStore.DEFAULT);
267
268   /**
269    * Default serializer, single quotes, simple mode, with whitespace and recursion detection.
270    * Note that recursion detection introduces a small performance penalty.
271    */
272   public static final JsonSerializer DEFAULT_LAX_READABLE_SAFE = new SimpleReadableSafe(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   /** Default serializer, single quotes, simple mode. */
295   public static class Simple extends JsonSerializer {
296
297      /**
298       * Constructor.
299       * 
300       * @param ps The property store containing all the settings for this object.
301       */
302      public Simple(PropertyStore ps) {
303         super(
304            ps.builder()
305               .set(JSON_simpleMode, true)
306               .set(SERIALIZER_quoteChar, '\'')
307               .build(),
308            "application/json",
309            "application/json+simple", "text/json+simple"
310         );
311      }
312   }
313
314   /** Default serializer, single quotes, simple mode, with whitespace. */
315   public static class SimpleReadable extends JsonSerializer {
316
317      /**
318       * Constructor.
319       * 
320       * @param ps The property store containing all the settings for this object.
321       */
322      public SimpleReadable(PropertyStore ps) {
323         super(
324            ps.builder()
325               .set(JSON_simpleMode, true)
326               .set(SERIALIZER_quoteChar, '\'')
327               .set(SERIALIZER_useWhitespace, true)
328               .build()
329         );
330      }
331   }
332
333   /**
334    * Default serializer, single quotes, simple mode, with whitespace and recursion detection.
335    * Note that recursion detection introduces a small performance penalty.
336    */
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(SERIALIZER_quoteChar, '\'')
349               .set(SERIALIZER_useWhitespace, true)
350               .set(SERIALIZER_detectRecursions, true)
351               .build()
352         );
353      }
354   }
355
356
357   //-------------------------------------------------------------------------------------------------------------------
358   // Instance
359   //-------------------------------------------------------------------------------------------------------------------
360
361   final boolean
362      simpleMode,
363      escapeSolidus,
364      addBeanTypeProperties;
365
366   private volatile JsonSchemaSerializer schemaSerializer;
367
368   /**
369    * Constructor.
370    * 
371    * @param ps
372    *    The property store containing all the settings for this object.
373    */
374   public JsonSerializer(PropertyStore ps) {
375      this(ps, "application/json", "application/json", "text/json");
376   }
377
378   /**
379    * Constructor.
380    * 
381    * @param ps
382    *    The property store containing all the settings for this object.
383    * @param produces
384    *    The media type that this serializer produces.
385    * @param accept
386    *    The accept media types that the serializer can handle.
387    *    <p>
388    *    Can contain meta-characters per the <code>media-type</code> specification of
389    *    <a class="doclink" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1">RFC2616/14.1</a>
390    *    <p>
391    *    If empty, then assumes the only media type supported is <code>produces</code>.
392    *    <p>
393    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
394    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
395    *    <p class='bcode'>
396    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json"</js>, <js>"text/json"</js>);
397    *    </p>
398    *    <br>...or...
399    *    <p class='bcode'>
400    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
401    *    </p>
402    */
403   public JsonSerializer(PropertyStore ps, String produces, String...accept) {
404      super(ps, produces, accept);
405      
406      simpleMode = getBooleanProperty(JSON_simpleMode, false);
407      escapeSolidus = getBooleanProperty(JSON_escapeSolidus, false);
408      addBeanTypeProperties = getBooleanProperty(JSON_addBeanTypeProperties, getBooleanProperty(SERIALIZER_addBeanTypeProperties, true));
409   }
410
411   @Override /* Context */
412   public JsonSerializerBuilder builder() {
413      return new JsonSerializerBuilder(getPropertyStore());
414   }
415   
416   /**
417    * Instantiates a new clean-slate {@link JsonSerializerBuilder} object.
418    * 
419    * <p>
420    * This is equivalent to simply calling <code><jk>new</jk> JsonSerializerBuilder()</code>.
421    * 
422    * @return A new {@link JsonSerializerBuilder} object.
423    */
424   public static JsonSerializerBuilder create() {
425      return new JsonSerializerBuilder();
426   }
427
428   /**
429    * Returns the schema serializer based on the settings of this serializer.
430    * 
431    * <p>
432    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 
433    * the settings of the object called on.
434    * 
435    * @return The schema serializer.
436    */
437   public JsonSchemaSerializer getSchemaSerializer() {
438      if (schemaSerializer == null)
439         schemaSerializer = builder().build(JsonSchemaSerializer.class);
440      return schemaSerializer;
441   }
442
443   //--------------------------------------------------------------------------------
444   // Entry point methods
445   //--------------------------------------------------------------------------------
446
447   @Override /* Serializer */
448   public WriterSerializerSession createSession(SerializerSessionArgs args) {
449      return new JsonSerializerSession(this, args);
450   }
451
452   @Override /* Context */
453   public ObjectMap asMap() {
454      return super.asMap()
455         .append("JsonSerializer", new ObjectMap()
456            .append("simpleMode", simpleMode)
457            .append("escapeSolidus", escapeSolidus)
458            .append("addBeanTypeProperties", addBeanTypeProperties)
459         );
460   }
461}