001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.json;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.SystemUtils.*;
021
022import java.io.*;
023import java.util.*;
024import java.util.concurrent.*;
025import java.util.concurrent.atomic.*;
026
027import org.apache.juneau.commons.bean.*;
028import org.apache.juneau.commons.collections.*;
029import org.apache.juneau.marshall.*;
030import org.apache.juneau.marshall.json5.*;
031import org.apache.juneau.marshall.serializer.*;
032import org.apache.juneau.marshall.stream.*;
033import org.apache.juneau.marshall.swap.spi.*;
034
035/**
036 * Serializes POJO models to RFC 8259 JSON.
037 *
038 * <p>
039 * Produces strict JSON with double-quoted strings by default. For JSON5-style output (single quotes,
040 * etc.), use {@link Json5Serializer}.
041 *
042 * <h5 class='topic'>Media types</h5>
043 * <p>
044 * Handles <c>Accept</c> types:  <bc>application/json, text/json</bc>
045 * <p>
046 * Produces <c>Content-Type</c> types:  <bc>application/json</bc>
047 *
048 * <h5 class='topic'>Description</h5>
049 * <p>
050 * The conversion is as follows...
051 * <ul class='spaced-list'>
052 *    <li>
053 *       Maps (e.g. {@link HashMap HashMaps}, {@link TreeMap TreeMaps}) are converted to JSON objects.
054 *    <li>
055 *       Collections (e.g. {@link HashSet HashSets}, {@link LinkedList LinkedLists}) and Java arrays are converted to
056 *       JSON arrays.
057 *    <li>
058 *       {@link String Strings} are converted to JSON strings.
059 *    <li>
060 *       {@link Number Numbers} (e.g. {@link Integer}, {@link Long}, {@link Double}) are converted to JSON numbers.
061 *    <li>
062 *       {@link Boolean Booleans} are converted to JSON booleans.
063 *    <li>
064 *       {@code nulls} are converted to JSON nulls.
065 *    <li>
066 *       {@code arrays} are converted to JSON arrays.
067 *    <li>
068 *       {@code beans} are converted to JSON objects.
069 * </ul>
070 *
071 * <p>
072 * The types above are considered "JSON-primitive" object types.
073 * Any non-JSON-primitive object types are transformed into JSON-primitive object types through
074 * {@link ObjectSwap ObjectSwaps} associated through the
075 * {@link org.apache.juneau.marshall.MarshallingContext.Builder#swaps(Class...) MarshallingContext.Builder#swaps(Class...)} method.
076 * Several default transforms are provided for transforming Dates, Enums, Iterators, etc...
077 *
078 * <p>
079 * This serializer provides several serialization options.
080 * Typically, one of the predefined DEFAULT serializers will be sufficient.
081 * However, custom serializers can be constructed to fine-tune behavior.
082 *
083 * <h5 class='topic'>Behavior-specific subclasses</h5>
084 * <p>
085 * The following direct subclasses are provided for convenience:
086 * <ul class='spaced-list'>
087 *    <li>
088 *       {@link Json5Serializer} - Default serializer, single quotes, simple mode.
089 *    <li>
090 *       {@link org.apache.juneau.marshall.json5.Json5Serializer.Readable Json5Serializer.Readable} - Default serializer, single quotes, simple mode, with whitespace.
091 * </ul>
092 *
093 * <h5 class='section'>Example:</h5>
094 * <p class='bjava'>
095 *    <jc>// Use one of the default serializers to serialize a POJO</jc>
096 *    String <jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.write(<jv>someObject</jv>);
097 *
098 *    <jc>// Create a custom serializer for lax syntax using single quote characters</jc>
099 *    JsonSerializer <jv>serializer</jv> = Json5Serializer.<jsm>create</jsm>().build();
100 *
101 *    <jc>// Clone an existing serializer and modify it to use single-quotes</jc>
102 *    <jv>serializer</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.copy().sq().build();
103 *
104 *    <jc>// Serialize a POJO to JSON</jc>
105 *    String <jv>json</jv> = <jv>serializer</jv>.write(<jv>someObject</jv>);
106 * </p>
107 *
108 * <h5 class='figure'>Example output (Map of name/age):</h5>
109 * <p class='bjson'>
110 *    {<js>"name"</js>:<js>"Alice"</js>,<js>"age"</js>:30}
111 * </p>
112 *
113 * <h5 class='figure'>Complex (nested object + array):</h5>
114 * <p class='bjson'>
115 *    {<js>"name"</js>:<js>"Alice"</js>,<js>"age"</js>:30,<js>"address"</js>:{<js>"street"</js>:<js>"123 Main St"</js>,<js>"city"</js>:<js>"Boston"</js>,<js>"state"</js>:<js>"MA"</js>},<js>"tags"</js>:[<js>"a"</js>,<js>"b"</js>,<js>"c"</js>]}
116 * </p>
117 *
118 * <h5 class='section'>Notes:</h5><ul>
119 *    <li class='note'>This class is thread safe and reusable.
120 * </ul>
121 *
122 * <h5 class='section'>See Also:</h5><ul>
123 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JsonSupport">JSON</a>
124 *    <li class='link'>{@link Json5Serializer} - For JSON5-style output (single quotes, etc.)
125 * </ul>
126 */
127@SuppressWarnings({
128   "java:S110", // Inheritance depth acceptable for this class hierarchy
129   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
130   "java:S9149", // Per-format static factories intentionally shadow the parent's.
131   "resource" // writeTokens(...) returns a Closeable owned by the caller; Eclipse JDT @Owning warning is by design.
132})
133public class JsonSerializer extends WriterSerializer implements JsonMetaProvider, TokenWritable, ArrayRecordWritable {
134
135   /**
136    * Builder class.
137    */
138   @SuppressWarnings({
139      "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
140   })
141   public abstract static class Builder<SELF extends Builder<SELF>> extends WriterSerializer.Builder<SELF> {
142
143      private static final Cache<HashKey,JsonSerializer> CACHE = Cache.of(HashKey.class, JsonSerializer.class).build();
144
145      private boolean addBeanTypesJson;
146      private boolean escapeSolidus;
147
148      /**
149       * Constructor, default settings.
150       */
151      protected Builder() {
152         produces("application/json");
153         accept("application/json,text/json");
154         addBeanTypesJson = env("JsonSerializer.addBeanTypes", false);
155         escapeSolidus = env("JsonSerializer.escapeSolidus", false);
156      }
157
158      /**
159       * Copy constructor.
160       *
161       * @param copyFrom The builder to copy from.
162       *    <br>Cannot be <jk>null</jk>.
163       */
164      protected Builder(Builder<?> copyFrom) {
165         super(reqnn("copyFrom", copyFrom));
166         addBeanTypesJson = copyFrom.addBeanTypesJson;
167         escapeSolidus = copyFrom.escapeSolidus;
168      }
169
170      /**
171       * Copy constructor.
172       *
173       * @param copyFrom The bean to copy from.
174       *    <br>Cannot be <jk>null</jk>.
175       */
176      protected Builder(JsonSerializer copyFrom) {
177         super(reqnn("copyFrom", copyFrom));
178         addBeanTypesJson = copyFrom.addBeanTypesJson;
179         escapeSolidus = copyFrom.escapeSolidus;
180      }
181
182      /**
183       * Add <js>"_type"</js> properties when needed.
184       *
185       * <p>
186       * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred
187       * through reflection.
188       *
189       * <p>
190       * When present, this value overrides the {@link org.apache.juneau.marshall.serializer.Serializer.Builder#addBeanTypes() Serializer.Builder#addBeanTypes()} setting and is
191       * provided to customize the behavior of specific serializers in a {@link SerializerSet}.
192       *
193       * <p>
194       * <b>Token streams:</b> the cursor's raw structural methods do not emit <c>"_type"</c>;
195       * however this setting IS honored by
196       * {@link TokenWriter#object(Object) TokenWriter.object(Object)}
197       * when walking a bean.
198       *
199       * @return This object.
200       */
201      public SELF addBeanTypesJson() {
202         return addBeanTypesJson(true);
203      }
204
205      /**
206       * Same as {@link #addBeanTypesJson()} but allows you to explicitly specify the value.
207       *
208       * @param value The value for this setting.
209       * @return This object.
210       */
211      public SELF addBeanTypesJson(boolean value) {
212         addBeanTypesJson = value;
213         return self();
214      }
215
216      @Override /* Overridden from Context.Builder<?> */
217      public JsonSerializer build() {
218         return cache(CACHE).build(JsonSerializer.class);
219      }
220
221      @Override /* Overridden from Context.Builder<?> */
222      public abstract SELF copy();
223
224      /**
225       * Prefix solidus <js>'/'</js> characters with escapes.
226       *
227       * <p>
228       * If enabled, solidus (e.g. slash) characters should be escaped.
229       *
230       * <p>
231       * The JSON specification allows for either format.
232       * <br>However, if you're embedding JSON in an HTML script tag, this setting prevents confusion when trying to serialize
233       * <xt>&lt;\/script&gt;</xt>.
234       *
235       * <h5 class='section'>Example:</h5>
236       * <p class='bjava'>
237       *    <jc>// Create a JSON serializer that escapes solidus characters.</jc>
238       *    WriterSerializer <jv>serializer</jv> = Json5Serializer
239       *       .<jsm>create</jsm>()
240       *       .escapeSolidus()
241       *       .build();
242       *
243       *    <jc>// Produces: "{foo:'&lt;\/bar&gt;'"</jc>
244       *    String <jv>json</jv> = <jv>serializer</jv>.write(JsonMap.<jsm>of</jsm>(<js>"foo"</js>, <js>"&lt;/bar&gt;"</js>);
245       * </p>
246       *
247       * @return This object.
248       */
249      public SELF escapeSolidus() {
250         return escapeSolidus(true);
251      }
252
253      /**
254       * Same as {@link #escapeSolidus()} but allows you to explicitly specify the value.
255       *
256       * @param value The value for this setting.
257       * @return This object.
258       */
259      public SELF escapeSolidus(boolean value) {
260         escapeSolidus = value;
261         return self();
262      }
263
264      @Override /* Overridden from Context.Builder<?> */
265      public HashKey hashKey() {
266         // @formatter:off
267         return HashKey.of(
268            super.hashKey(),
269            addBeanTypesJson,
270            escapeSolidus
271         );
272         // @formatter:on
273      }
274
275
276   }
277
278   /**
279    * Concrete default builder leaf for the non-subclassed {@link JsonSerializer#create()} / {@link JsonSerializer#copy()} path.
280    */
281   public static final class DefaultBuilder extends Builder<DefaultBuilder> {
282
283      DefaultBuilder() {}
284
285      DefaultBuilder(JsonSerializer copyFrom) {
286         super(copyFrom);
287      }
288
289      DefaultBuilder(Builder<?> copyFrom) {
290         super(copyFrom);
291      }
292
293      @Override /* Overridden from Context.Builder<?> */
294      public DefaultBuilder copy() {
295         return new DefaultBuilder(this);
296      }
297   }
298
299   /** Default serializer, with whitespace. */
300   public static class Readable extends JsonSerializer {
301
302      /**
303       * Constructor.
304       *
305       * @param builder The builder for this object.
306       */
307      public Readable(Builder<?> builder) {
308         super(builder.useWhitespace());
309      }
310   }
311
312   /** Default serializer, all default settings.*/
313   public static final JsonSerializer DEFAULT = new JsonSerializer(create());
314
315   /** Default serializer, all default settings.*/
316   public static final JsonSerializer DEFAULT_READABLE = new Readable(create());
317
318   /**
319    * Creates a new builder for this object.
320    *
321    * @return A new builder.
322    */
323   @SuppressWarnings({
324      "java:S1452" // Builder<?> wildcard return intentional; callers chain via fluent API without needing the concrete type
325   })
326   public static Builder<?> create() {
327      return new DefaultBuilder();
328   }
329
330   protected final boolean addBeanTypesJson;
331   protected final boolean escapeSolidus;
332
333   private final boolean addBeanTypes2;
334   private final Map<BeanPropertyMeta,JsonBeanPropertyMeta> jsonBeanPropertyMetas = new ConcurrentHashMap<>();
335   private final Map<ClassMeta<?>,JsonClassMeta> jsonClassMetas = new ConcurrentHashMap<>();
336
337   private final AtomicReference<JsonSchemaSerializer> schemaSerializer = new AtomicReference<>();
338
339   /**
340    * Constructor.
341    *
342    * @param builder The builder for this object.
343    */
344   public JsonSerializer(Builder<?> builder) {
345      super(builder);
346      addBeanTypesJson = builder.addBeanTypesJson;
347      escapeSolidus = builder.escapeSolidus;
348
349      addBeanTypes2 = addBeanTypesJson || super.isAddBeanTypes();
350   }
351
352   @Override /* Overridden from Context */
353   public Builder<?> copy() {
354      return new DefaultBuilder(this);
355   }
356
357   @Override /* Overridden from Context */
358   public JsonSerializerSession.Builder<?> createSession() {
359      return JsonSerializerSession.create(this);
360   }
361
362   @Override /* Overridden from JsonMetaProvider */
363   public JsonBeanPropertyMeta getJsonBeanPropertyMeta(BeanPropertyMeta bpm) {
364      if (bpm == null)
365         return JsonBeanPropertyMeta.DEFAULT;
366      return jsonBeanPropertyMetas.computeIfAbsent(bpm, k -> new JsonBeanPropertyMeta(k.getDelegateFor(), this));
367   }
368
369   @Override /* Overridden from JsonMetaProvider */
370   public JsonClassMeta getJsonClassMeta(ClassMeta<?> cm) {
371      return jsonClassMetas.computeIfAbsent(cm, k -> new JsonClassMeta(k, this));
372   }
373
374   /**
375    * Returns the schema serializer based on the settings of this serializer.
376    *
377    * <p>
378    * Note that this method creates a builder initialized to all default settings, whereas {@link #copy()} copies
379    * the settings of the object called on.
380    *
381    * @return The schema serializer.
382    */
383   public JsonSchemaSerializer getSchemaSerializer() {
384      JsonSchemaSerializer result = schemaSerializer.get();
385      if (result == null) {
386         result = JsonSchemaSerializer.create().marshallingContext(getMarshallingContext()).build();
387         if (! schemaSerializer.compareAndSet(null, result)) {
388            result = schemaSerializer.get();
389         }
390      }
391      return result;
392   }
393
394   @Override /* Overridden from Context */
395   public JsonSerializerSession getSession() { return createSession().build(); }
396
397   /**
398    * Convenience delegator that opens a {@link JsonTokenWriter} over the output using
399    * <b>default session arguments</b> (mirrors {@link #write(Object, Object)}).
400    *
401    * <p>
402    * The real implementation lives on {@link JsonSerializerSession#writeTokens(Object)}.
403    * Callers that need request-derived configuration should call {@link #createSession()} and
404    * invoke {@link JsonSerializerSession#writeTokens(Object)} on the built session instead.
405    *
406    * @param output The output.  Accepts {@link Writer}, {@link OutputStream}, {@link File}, or
407    *    {@link StringBuilder}.
408    * @return A new {@link JsonTokenWriter}.
409    * @throws IOException If the output type is not supported or could not be opened.
410    */
411   @Override /* TokenWritable */
412   public TokenWriter writeTokens(Object output) throws IOException {
413      return getSession().writeTokens(output);
414   }
415
416   /**
417    * Add <js>"_type"</js> properties when needed.
418    *
419    * @see Builder#addBeanTypesJson()
420    * @return
421    *    <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
422    *    through reflection.
423    */
424   @Override
425   protected final boolean isAddBeanTypes() { return addBeanTypes2; }
426
427   /**
428    * Prefix solidus <js>'/'</js> characters with escapes.
429    *
430    * @see Builder#escapeSolidus()
431    * @return
432    *    <jk>true</jk> if solidus (e.g. slash) characters should be escaped.
433    */
434   protected final boolean isEscapeSolidus() { return escapeSolidus; }
435
436   @Override /* Overridden from WriterSerializer */
437   protected FluentMap<String,Object> properties() {
438      return super.properties()
439         .a("addBeanTypesJson", addBeanTypesJson)
440         .a("escapeSolidus", escapeSolidus);
441   }
442
443   /**
444    * Convenience delegator for the streaming array-element {@link RecordWriter} (uses default
445    * session args; see {@link #writeTokens(Object)}).  Real impl on
446    * {@link JsonSerializerSession#writeArrayRecords(Object)}.
447    *
448    * @param output The output.
449    * @return A new element-streamed {@link RecordWriter}.
450    * @throws IOException If a problem occurred opening the underlying output.
451    */
452   @Override /* ArrayRecordWritable */
453   public RecordWriter writeArrayRecords(Object output) throws IOException {
454      return getSession().writeArrayRecords(output);
455   }
456}