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.collections;
018
019import static java.util.Collections.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.io.*;
023import java.util.*;
024import java.util.function.*;
025
026import org.apache.juneau.marshall.*;
027import org.apache.juneau.marshall.json.*;
028import org.apache.juneau.marshall.json5.*;
029import org.apache.juneau.marshall.marshaller.*;
030import org.apache.juneau.marshall.marshaller.Json;
031import org.apache.juneau.marshall.parser.*;
032import org.apache.juneau.marshall.serializer.*;
033
034/**
035 * Java implementation of a JSON object.
036 *
037 * <p>
038 * This class is a subclass of {@link MarshalledMap} that carries strict-JSON-specific surface, including
039 * strict-JSON {@link #toString()} (via {@link Json#of(Object)}), {@link JsonParser}-defaulting
040 * {@link CharSequence} / {@link Reader} constructors, and convenience methods for serializing to the
041 * various JSON dialects (JSON5, JSON Lines, canonical JSON, HJSON).
042 *
043 * <p>
044 * <b>v10.0 behavioral break:</b> {@code toString()} now produces strict (RFC 8259) JSON (was JSON5),
045 * and the {@link CharSequence} / {@link Reader} constructors now default to {@link JsonParser#DEFAULT}
046 * (was {@link Json5Parser#DEFAULT}). For the previous JSON5 behavior use {@link Json5Map}.
047 *
048 * <p>
049 * Note that the use of this class is optional for generating JSON. The serializers will accept any objects that implement the
050 * {@link Map} interface.  But this class provides some useful additional functionality when working with
051 * JSON models constructed from Java Collections Framework objects.  For example, a constructor is provided for
052 * converting a JSON object string directly into a {@link Map}.  It also contains accessor methods for to avoid common
053 * typecasting when accessing elements in a list.
054 *
055 * <h5 class='section'>Example:</h5>
056 * <p class='bjava'>
057 *    <jc>// Construct an empty Map</jc>
058 *    JsonMap <jv>map</jv> = JsonMap.<jsm>of</jsm>();
059 *
060 *    <jc>// Construct a Map from JSON</jc>
061 *    <jv>map</jv> = JsonMap.<jsm>ofString</jsm>(<js>"{a:'A',b:{c:'C',d:123}}"</js>);
062 *
063 *    <jc>// Construct a Map using the append method</jc>
064 *    <jv>map</jv> = JsonMap.<jsm>of</jsm>().a(<js>"foo"</js>,<js>"x"</js>).a(<js>"bar"</js>,123).a(<js>"baz"</js>,<jk>true</jk>);
065 *
066 *    <jc>// Construct a Map from XML generated by XmlSerializer</jc>
067 *    String <jv>xml</jv> = <js>"&lt;object&gt;&lt;a type='string'&gt;A&lt;/a&gt;&lt;b type='object'&gt;&lt;c type='string'&gt;C&lt;/c&gt;&lt;d type='number'&gt;123&lt;/d&gt;&lt;/b&gt;&lt;/object&gt;"</js>;
068 *    <jv>map</jv> = JsonMap.<jsm>of</jsm>(<jv>xml</jv>, XmlParser.<jsf>DEFAULT</jsf>);
069 *
070 *    <jc>// Construct a Map from a URL GET parameter string generated by UrlEncodingParser</jc>
071 *    String <jv>urlParams</jv> = <js>"?a='A'&amp;b={c:'C',d:123}"</js>;
072 *    <jv>map</jv> = JsonMap.<jsm>of</jsm>(<jv>urlParams</jv>, UrlEncodingParser.<jsf>DEFAULT</jsf>);
073 *
074 *    <jc>// Construct JSON from JsonMap</jc>
075 *    <jv>map</jv> = JsonMap.<jsm>ofString</jsm>(<js>"{foo:'bar'},{baz:[123,true]}"</js>);
076 *    String <jv>json</jv> = <jv>map</jv>.toString();  <jc>// Produces "{foo:'bar'},{baz:[123,true]}"</jc>
077 *    <jv>json</jv> = <jv>map</jv>.toString(JsonSerializer.<jsf>DEFAULT</jsf>);  <jc>// Equivalent</jc>
078 *    <jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.write(<jv>map</jv>);  <jc>// Equivalent</jc>
079 *
080 *    <jc>// Get a map entry as an Integer</jc>
081 *    <jv>map</jv> = JsonMap.<jsm>ofString</jsm>(<js>"{foo:123}"</js>);
082 *    Integer <jv>integer</jv> = <jv>map</jv>.getInt(<js>"foo"</js>);
083 *    <jv>integer</jv> = <jv>map</jv>.get(Integer.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
084 *
085 *    <jc>// Add an inner map</jc>
086 *    JsonMap <jv>map1</jv> = JsonMap.<jsm>ofString</jsm>(<js>"{a:1}"</js>);
087 *    JsonMap <jv>map2</jv> = JsonMap.<jsm>ofString</jsm>(<js>"{b:2}"</js>).setInner(<jv>map1</jv>);
088 *    <jk>int</jk> <jv>_int</jv> = <jv>map2</jv>.getInt(<js>"a"</js>);  <jc>// a == 1 </jc>
089 * </p>
090 *
091 * <h5 class='section'>Notes:</h5><ul>
092 *    <li class='warn'>This class is not thread safe.
093 * </ul>
094 */
095@SuppressWarnings({
096   "java:S110", // JsonMap -> MarshalledMap -> LinkedHashMap -> HashMap -> AbstractMap: depth comes from the JDK map hierarchy
097   "java:S2160", // equals() / hashCode() inherited from MarshalledMap; map equality is element-based
098   "java:S9149" // Per-type static factories intentionally shadow the parent's.
099})
100public class JsonMap extends MarshalledMap {
101
102   private static class Unmodifiable extends JsonMap {
103      private static final long serialVersionUID = 1L;
104
105      @SuppressWarnings({
106         "synthetic-access" // Access to outer class members is intentional
107      })
108      Unmodifiable(JsonMap contents) {
109         if (nn(contents))
110            contents.forEach(super::put);
111      }
112
113      @Override
114      public void clear() {
115         throw uoroex();
116      }
117
118      @Override
119      public Object compute(String key, BiFunction<? super String,? super Object,?> remappingFunction) {
120         throw uoroex();
121      }
122
123      @Override
124      public Object computeIfAbsent(String key, Function<? super String,?> mappingFunction) {
125         throw uoroex();
126      }
127
128      @Override
129      public Object computeIfPresent(String key, BiFunction<? super String,? super Object,?> remappingFunction) {
130         throw uoroex();
131      }
132
133      @Override
134      public Set<Map.Entry<String,Object>> entrySet() {
135         return u(super.entrySet());
136      }
137
138      @Override
139      public boolean isUnmodifiable() { return true; }
140
141      @Override
142      public Set<String> keySet() {
143         return u(super.keySet());
144      }
145
146      @Override
147      public Object merge(String key, Object value, BiFunction<? super Object,? super Object,?> remappingFunction) {
148         throw uoroex();
149      }
150
151      @Override
152      public Object put(String key, Object val) {
153         throw uoroex();
154      }
155
156      @Override
157      public void putAll(Map<? extends String,?> m) {
158         throw uoroex();
159      }
160
161      @Override
162      public Object putIfAbsent(String key, Object value) {
163         throw uoroex();
164      }
165
166      @Override
167      public Object remove(Object key) {
168         throw uoroex();
169      }
170
171      @Override
172      public boolean remove(Object key, Object value) {
173         throw uoroex();
174      }
175
176      @Override
177      public Object replace(String key, Object value) {
178         throw uoroex();
179      }
180
181      @Override
182      public boolean replace(String key, Object oldValue, Object newValue) {
183         throw uoroex();
184      }
185
186      @Override
187      public void replaceAll(BiFunction<? super String,? super Object,?> function) {
188         throw uoroex();
189      }
190
191      @Override
192      public Collection<Object> values() {
193         return Collections.unmodifiableCollection(super.values());
194      }
195   }
196
197   private static final long serialVersionUID = 1L;
198
199   /**
200    * An empty read-only JsonMap.
201    *
202    * @serial exclude
203    */
204   @SuppressWarnings({
205      "java:S2386" // Public static final field accessed externally, cannot be protected
206   })
207   public static final JsonMap EMPTY_MAP = new JsonMap() {
208
209      private static final long serialVersionUID = 1L;
210
211      @Override /* Overridden from Map */
212      public Set<Map.Entry<String,Object>> entrySet() {
213         return Collections.<String,Object>emptyMap().entrySet();
214      }
215
216      @Override /* Overridden from Map */
217      public Set<String> keySet() {
218         return Collections.<String,Object>emptyMap().keySet();
219      }
220
221      @Override /* Overridden from Map */
222      public Object put(String key, Object value) {
223         throw uoroex();
224      }
225
226      @Override /* Overridden from Map */
227      public Object remove(Object key) {
228         throw uoroex();
229      }
230
231      @Override /* Overridden from Map */
232      public Collection<Object> values() {
233         return emptyMap().values();
234      }
235   };
236
237   /**
238    * Construct an empty map.
239    *
240    * @return An empty map.
241    */
242   public static JsonMap create() {
243      return new JsonMap();
244   }
245
246   /**
247    * Construct an empty filtered map.
248    *
249    * @return An empty map.
250    */
251   public static JsonMap filteredMap() {
252      return create().filtered();
253   }
254
255   /**
256    * Construct a filtered map initialized with the specified key/value pairs.
257    *
258    * <p>
259    * Same as {@link #of(Object...)} but calls {@link #filtered()} on the created map.
260    *
261    * @param keyValuePairs A list of key/value pairs to add to this map.
262    * @return A new map, never <jk>null</jk>.
263    */
264   public static JsonMap filteredMap(Object...keyValuePairs) {
265      return new JsonMap(keyValuePairs).filtered();
266   }
267
268   /**
269    * Construct a map initialized with the specified map.
270    *
271    * @param values
272    *    The map to copy.
273    *    <br>Can be <jk>null</jk> (returns <jk>null</jk>).
274    *    <br>Keys will be converted to strings using {@link Object#toString()}.
275    * @return A new map or <jk>null</jk> if the map was <jk>null</jk>.
276    */
277   public static JsonMap of(Map<?,?> values) {
278      return values == null ? null : new JsonMap(values);
279   }
280
281   /**
282    * Construct a map initialized with the specified key/value pairs.
283    *
284    * <h5 class='section'>Examples:</h5>
285    * <p class='bjava'>
286    *    JsonMap <jv>map</jv> = <jk>new</jk> JsonMap(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>);
287    * </p>
288    *
289    * @param keyValuePairs A list of key/value pairs to add to this map.
290    * @return A new map, never <jk>null</jk>.
291    */
292   public static JsonMap of(Object...keyValuePairs) {
293      return new JsonMap(keyValuePairs);
294   }
295
296   /**
297    * Construct a map initialized with the specified JSON string.
298    *
299    * @param json
300    *    The JSON text to parse.
301    *    <br>Can be normal or simplified JSON.
302    * @return A new map (empty if the input was <jk>null</jk>), never <jk>null</jk>.
303    * @throws ParseException Malformed input encountered.
304    */
305   public static JsonMap ofString(CharSequence json) throws ParseException {
306      return json == null ? new JsonMap() : new JsonMap(json);
307   }
308
309   /**
310    * Construct a map initialized with the specified reader containing JSON.
311    *
312    * @param json
313    *    The reader containing JSON text to parse.
314    *    <br>Can contain normal or simplified JSON.
315    * @return A new map (empty if the input was <jk>null</jk>), never <jk>null</jk>.
316    * @throws ParseException Malformed input encountered.
317    */
318   public static JsonMap ofString(Reader json) throws ParseException {
319      return json == null ? new JsonMap() : new JsonMap(json);
320   }
321
322   /**
323    * Construct a map initialized with the specified string.
324    *
325    * @param in The input being parsed.
326    * @param p
327    *    The parser to use to parse the input.
328    *    <br>If <jk>null</jk>, uses {@link JsonParser}.
329    * @return A new map (empty if the input was <jk>null</jk>), never <jk>null</jk>.
330    * @throws ParseException Malformed input encountered.
331    */
332   public static JsonMap ofString(CharSequence in, Parser p) throws ParseException {
333      return in == null ? new JsonMap() : new JsonMap(in, p);
334   }
335
336   /**
337    * Construct a map initialized with the specified string.
338    *
339    * @param in
340    *    The reader containing the input being parsed.
341    *    <br>Can contain normal or simplified JSON.
342    * @param p
343    *    The parser to use to parse the input.
344    *    <br>If <jk>null</jk>, uses {@link JsonParser}.
345    * @return A new map (empty if the input was <jk>null</jk>), never <jk>null</jk>.
346    * @throws ParseException Malformed input encountered.
347    */
348   public static JsonMap ofString(Reader in, Parser p) throws ParseException {
349      return in == null ? new JsonMap() : new JsonMap(in, p);
350   }
351
352   /**
353    * Construct an empty map.
354    */
355   public JsonMap() {}
356
357   /**
358    * Construct an empty map with the specified bean context.
359    *
360    * @param session The bean session to use for creating beans.
361    */
362   public JsonMap(MarshallingSession session) {
363      super(session);
364   }
365
366   /**
367    * Construct a map initialized with the specified JSON.
368    *
369    * @param json
370    *    The JSON text to parse.
371    *    <br>Must be strict (RFC 8259) JSON; for JSON5 input use {@link Json5Map} or pass
372    *    {@link Json5Parser#DEFAULT} explicitly.
373    * @throws ParseException Malformed input encountered.
374    */
375   public JsonMap(CharSequence json) throws ParseException {
376      this(json, JsonParser.DEFAULT);
377   }
378
379   /**
380    * Construct a map initialized with the specified string.
381    *
382    * @param in
383    *    The input being parsed.
384    *    <br>Can be <jk>null</jk> or empty (results in an empty map; no parsing occurs).
385    * @param p
386    *    The parser to use to parse the input.
387    *    <br>If <jk>null</jk>, uses {@link JsonParser}.
388    * @throws ParseException Malformed input encountered.
389    */
390   public JsonMap(CharSequence in, Parser p) throws ParseException {
391      super(in, p == null ? JsonParser.DEFAULT : p);
392   }
393
394   /**
395    * Construct a map initialized with the specified map.
396    *
397    * @param in
398    *    The map to copy.
399    *    <br>Can be <jk>null</jk> (results in an empty map).
400    *    <br>Keys will be converted to strings using {@link Object#toString()}.
401    */
402   public JsonMap(Map<?,?> in) {
403      super(in);
404   }
405
406   /**
407    * Construct a map initialized with the specified key/value pairs.
408    *
409    * <h5 class='section'>Examples:</h5>
410    * <p class='bjava'>
411    *    JsonMap <jv>map</jv> = <jk>new</jk> JsonMap(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>);
412    * </p>
413    *
414    * @param keyValuePairs A list of key/value pairs to add to this map.
415    */
416   public JsonMap(Object...keyValuePairs) {
417      super(keyValuePairs);
418   }
419
420   /**
421    * Construct a map initialized with the specified reader containing JSON.
422    *
423    * @param json
424    *    The reader containing JSON text to parse.
425    *    <br>Must be strict (RFC 8259) JSON; for JSON5 input use {@link Json5Map} or pass
426    *    {@link Json5Parser#DEFAULT} explicitly.
427    * @throws ParseException Malformed input encountered.
428    */
429   public JsonMap(Reader json) throws ParseException {
430      this(json, JsonParser.DEFAULT);
431   }
432
433   /**
434    * Construct a map initialized with the specified string.
435    *
436    * @param in
437    *    The reader containing the input being parsed.
438    *    <br>Must be strict (RFC 8259) JSON.
439    * @param p
440    *    The parser to use to parse the input.
441    *    <br>If <jk>null</jk>, uses {@link JsonParser}.
442    * @throws ParseException Malformed input encountered.
443    */
444   public JsonMap(Reader in, Parser p) throws ParseException {
445      super(in, p == null ? JsonParser.DEFAULT : p);
446   }
447
448   @Override /* Overridden from MarshalledMap */
449   public JsonMap append(Map<String,Object> values) {
450      super.append(values);
451      return this;
452   }
453
454   @Override /* Overridden from MarshalledMap */
455   public JsonMap append(String key, Object value) {
456      super.append(key, value);
457      return this;
458   }
459
460   @Override /* Overridden from MarshalledMap */
461   @SuppressWarnings({
462      "unchecked", // Generic varargs is safe here: values are only iterated, not stored into a typed array.
463      "varargs" // values is only forwarded to super.appendFirst(), never stored or exposed, so heap pollution cannot occur
464   })
465   public <T> JsonMap appendFirst(Predicate<T> test, String key, T...values) {
466      super.appendFirst(test, key, values);
467      return this;
468   }
469
470   @Override /* Overridden from MarshalledMap */
471   public JsonMap appendIf(boolean flag, String key, Object value) {
472      super.appendIf(flag, key, value);
473      return this;
474   }
475
476   @Override /* Overridden from MarshalledMap */
477   public <T> JsonMap appendIf(Predicate<T> test, String key, T value) {
478      super.appendIf(test, key, value);
479      return this;
480   }
481
482   @Override /* Overridden from MarshalledMap */
483   public JsonMap appendIfAbsent(String key, Object value) {
484      super.appendIfAbsent(key, value);
485      return this;
486   }
487
488   @Override /* Overridden from MarshalledMap */
489   public <T> JsonMap appendIfAbsentIf(Predicate<T> predicate, String key, T value) {
490      super.appendIfAbsentIf(predicate, key, value);
491      return this;
492   }
493
494   /**
495    * Serializes this map to a standard JSON string.
496    *
497    * @return This object as a standard JSON string.
498    */
499   public String toJson() {
500      return Json.of(this);
501   }
502
503   /**
504    * Serializes this map to a JSON5 string.
505    *
506    * <p>
507    * A synonym for {@link #toString()}.
508    *
509    * @return This object as a JSON5 string.
510    */
511   public String toJson5() {
512      return Json5.of(this);
513   }
514
515   /**
516    * Serializes this map to a JSON Lines string.
517    *
518    * @return This object as a JSON Lines string.
519    */
520   public String toJsonl() {
521      return Jsonl.of(this);
522   }
523
524   /**
525    * Serializes this map to a canonical JSON string (RFC 8785).
526    *
527    * @return This object as a canonical JSON string.
528    */
529   public String toJcs() {
530      return Jcs.of(this);
531   }
532
533   /**
534    * Serializes this map to an HJSON string.
535    *
536    * @return This object as an HJSON string.
537    */
538   public String toHjson() {
539      return Hjson.of(this);
540   }
541
542   /**
543    * Serializes this map to a readable (indented) JSON5 string using {@link Json5Serializer#DEFAULT_READABLE}.
544    *
545    * @return This object serialized as a readable JSON5 string.
546    */
547   public String toReadableJson5() {
548      if (Json5Serializer.DEFAULT_READABLE == null)
549         return s(this);
550      return Json5Serializer.DEFAULT_READABLE.toString(this);
551   }
552
553   @Override /* Overridden from MarshalledMap */
554   public JsonMap exclude(String...keys) {
555      var m2 = new JsonMap();
556      this.forEach((k, v) -> {
557         var exclude = false;
558         for (var kk : keys)
559            if (kk.equals(k))
560               exclude = true;
561         if (! exclude)
562            m2.put(k, v);
563      });
564      return m2;
565   }
566
567   @Override /* Overridden from MarshalledMap */
568   public JsonMap filtered() {
569      super.filtered();
570      return this;
571   }
572
573   @Override /* Overridden from MarshalledMap */
574   public JsonMap filtered(Predicate<Object> value) {
575      super.filtered(value);
576      return this;
577   }
578
579   @Override /* Overridden from MarshalledMap */
580   public JsonList findList(String...keys) {
581      return find(JsonList.class, keys);
582   }
583
584   @Override /* Overridden from MarshalledMap */
585   public JsonMap findMap(String...keys) {
586      return find(JsonMap.class, keys);
587   }
588
589   @Override /* Overridden from MarshalledMap */
590   public JsonList getList(String key) {
591      return get(key, JsonList.class);
592   }
593
594   @Override /* Overridden from MarshalledMap */
595   public JsonList getList(String key, boolean createIfNotExists) {
596      JsonList m = getWithDefault(key, null, JsonList.class);
597      if (m == null && createIfNotExists) {
598         m = new JsonList();
599         put(key, m);
600      }
601      return m;
602   }
603
604   /**
605    * Returns the specified entry value converted to a {@link JsonList}.
606    *
607    * @param key The key.
608    * @param defVal The default value if the map doesn't contain the specified mapping.
609    * @return The converted value, or the default value if the map contains no mapping for this key.
610    * @throws InvalidDataConversionException If value cannot be converted.
611    */
612   public JsonList getList(String key, JsonList defVal) {
613      return getWithDefault(key, defVal, JsonList.class);
614   }
615
616   @Override /* Overridden from MarshalledMap */
617   public JsonMap getMap(String key) {
618      return get(key, JsonMap.class);
619   }
620
621   @Override /* Overridden from MarshalledMap */
622   public JsonMap getMap(String key, boolean createIfNotExists) {
623      var m = getWithDefault(key, null, JsonMap.class);
624      if (m == null && createIfNotExists) {
625         m = new JsonMap();
626         put(key, m);
627      }
628      return m;
629   }
630
631   /**
632    * Returns the specified entry value converted to a {@link JsonMap}.
633    *
634    * @param key The key.
635    * @param defVal The default value if the map doesn't contain the specified mapping.
636    * @return The converted value, or the default value if the map contains no mapping for this key.
637    * @throws InvalidDataConversionException If value cannot be converted.
638    */
639   public JsonMap getMap(String key, JsonMap defVal) {
640      return getWithDefault(key, defVal, JsonMap.class);
641   }
642
643   @Override /* Overridden from MarshalledMap */
644   public JsonMap include(String...keys) {
645      var m2 = new JsonMap();
646      this.forEach((k, v) -> {
647         for (var kk : keys)
648            if (kk.equals(k))
649               m2.put(kk, v);
650      });
651      return m2;
652   }
653
654   @Override /* Overridden from MarshalledMap */
655   public JsonMap inner(Map<String,Object> inner) {
656      super.inner(inner);
657      return this;
658   }
659
660   @Override /* Overridden from MarshalledMap */
661   public JsonMap keepAll(String...keys) {
662      super.keepAll(keys);
663      return this;
664   }
665
666   @Override /* Overridden from MarshalledMap */
667   public JsonMap modifiable() {
668      if (isUnmodifiable())
669         return new JsonMap(this);
670      return this;
671   }
672
673   @Override /* Overridden from MarshalledMap */
674   protected JsonMap newMarshalledMap(MarshallingSession bs) {
675      return new JsonMap(bs);
676   }
677
678   /**
679    * Convenience method for inserting JSON directly into an attribute on this object.
680    *
681    * <p>
682    * The JSON text can be an object (i.e. <js>"{...}"</js>) or an array (i.e. <js>"[...]"</js>).
683    *
684    * <p>
685    * Input must be strict (RFC 8259) JSON; for JSON5 use {@link Json5Map#putJson5(String,String)}.
686    *
687    * @param key The key.
688    * @param json The JSON text that will be parsed into an Object and then inserted into this map.
689    * @throws ParseException Malformed input encountered.
690    */
691   public void putJson(String key, String json) throws ParseException {
692      this.put(key, Json.to(json, Object.class));
693   }
694
695   @Override /* Overridden from MarshalledMap */
696   public JsonMap session(MarshallingSession session) {
697      super.session(session);
698      return this;
699   }
700
701   @Override /* Overridden from MarshalledMap */
702   public JsonMap setBeanSession(MarshallingSession value) {
703      super.setBeanSession(value);
704      return this;
705   }
706
707   @Override /* Overridden from Object */
708   public String toString() {
709      return Json.of(this);
710   }
711
712   @Override /* Overridden from MarshalledMap */
713   public JsonMap unmodifiable() {
714      if (this instanceof Unmodifiable this2)
715         return this2;
716      return new Unmodifiable(this);
717   }
718
719   /**
720    * Convenience method for serializing this map to the specified <c>Writer</c> using the
721    * {@link Json#DEFAULT} marshaller.
722    *
723    * @param w The writer to serialize this object to.
724    * @return This object.
725    * @throws IOException If a problem occurred trying to write to the writer.
726    * @throws SerializeException If a problem occurred trying to convert the output.
727    */
728   public JsonMap writeTo(Writer w) throws IOException, SerializeException {
729      Json.DEFAULT.write(this, w);
730      return this;
731   }
732}