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