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