All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.collections.JsonList Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.collections;

import static org.apache.juneau.common.internal.ThrowableUtils.*;
import static org.apache.juneau.internal.ConsumerUtils.*;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;

import org.apache.juneau.*;
import org.apache.juneau.common.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.marshaller.*;
import org.apache.juneau.objecttools.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.serializer.*;

/**
 * Java implementation of a JSON array.
 *
 * 

* An extension of {@link LinkedList}, so all methods available to in that class are also available to this class. * *

* Note that the use of this class is optional for generating JSON. The serializers will accept any objects that implement the * {@link Collection} interface. But this class provides some useful additional functionality when working with JSON * models constructed from Java Collections Framework objects. For example, a constructor is provided for converting a * JSON array string directly into a {@link List}. It also contains accessor methods for to avoid common typecasting * when accessing elements in a list. * *

Example:
*

* // Construct an empty List * JsonList list = JsonList.of(); * * // Construct a list of objects using various methods * list = JsonList.of().a("foo").a(123).a(true); * list = JsonList.of().a("foo", 123, true); // Equivalent * list = JsonList.of("foo", 123, true); // Equivalent * * // Construct a list of integers from JSON * list = JsonList.ofJson("[1,2,3]"); * * // Construct a list of generic JsonMap objects from JSON * list = JsonList.ofJson("[{foo:'bar'},{baz:'bing'}]"); * * // Construct a list of integers from XML * String xml = "<array><number>1</number><number>2</number><number>3</number></array>"; * list = JsonList.of(xml, XmlParser.DEFAULT); * list = (List)XmlParser.DEFAULT.parse(xml); // Equivalent * list = (List)XmlParser.DEFAULT.parse(Object.class, xml); // Equivalent * list = XmlParser.DEFAULT.parse(List.class, xml); // Equivalent * list = XmlParser.DEFAULT.parse(JsonList.class, xml); // Equivalent * * // Construct JSON from JsonList * list = JsonList.ofJson("[{foo:'bar'},{baz:'bing'}]"); * String json = list.toString(); // Produces "[{foo:'bar'},{baz:'bing'}]" * json = list.toString(JsonSerializer.DEFAULT); // Equivalent * json = JsonSerializer.DEFAULT.serialize(list); // Equivalent * * // Get one of the entries in the list as an Integer * list = JsonList.ofJson("[1,2,3]"); * Integer integer = list.getInt(1); * list = list.get(Integer.class, 1); // Equivalent * * // Get one of the entries in the list as an Float * list = JsonList.ofJson("[1,2,3]"); * Float _float = list.getFloat(1); // Returns 2f * _float = list.get(Float.class, 1); // Equivalent * * // Same as above, except converted to a String * list = JsonList.ofJson("[1,2,3]"); * String string = list.getString(1); // Returns "2" * string = list.get(String.class, 1); // Equivalent * * // Get one of the entries in the list as a bean (converted to a bean if it isn't already one) * list = JsonList.ofJson("[{name:'John Smith',age:45}]"); * Person person = list.get(Person.class, 0); * * // Iterate over a list of beans using the elements() method * list = JsonList.ofJson("[{name:'John Smith',age:45}]"); * for (Person person : list.elements(Person.class) { * // Do something with p * } *

* *
Notes:
    *
  • This class is not thread safe. *
* *
See Also:
    *
* * @serial exclude */ public class JsonList extends LinkedList { //----------------------------------------------------------------------------------------------------------------- // Static //----------------------------------------------------------------------------------------------------------------- private static final long serialVersionUID = 1L; /** * Parses a string that can consist of either a JSON array or comma-delimited list. * *

* The type of string is auto-detected. * * @param s The string to parse. * @return The parsed string. * @throws ParseException Malformed input encountered. */ public static JsonList ofJsonOrCdl(String s) throws ParseException { if (StringUtils.isEmpty(s)) return null; if (! StringUtils.isJsonArray(s, true)) return new JsonList((Object[])StringUtils.split(s.trim(), ',')); return new JsonList(s); } //----------------------------------------------------------------------------------------------------------------- // Instance //----------------------------------------------------------------------------------------------------------------- transient BeanSession session = null; private transient ObjectRest objectRest; /** * An empty read-only JsonList. * * @serial exclude */ public static final JsonList EMPTY_LIST = new JsonList() { private static final long serialVersionUID = 1L; @Override /* List */ public void add(int location, Object object) { throw new UnsupportedOperationException("Not supported on read-only object."); } @Override /* List */ public ListIterator listIterator(final int location) { return Collections.emptyList().listIterator(location); } @Override /* List */ public Object remove(int location) { throw new UnsupportedOperationException("Not supported on read-only object."); } @Override /* List */ public Object set(int location, Object object) { throw new UnsupportedOperationException("Not supported on read-only object."); } @Override /* List */ public List subList(int start, int end) { return Collections.emptyList().subList(start, end); } }; //------------------------------------------------------------------------------------------------------------------ // Constructors //------------------------------------------------------------------------------------------------------------------ /** * Construct an empty list. */ public JsonList() {} /** * Construct an empty list with the specified bean context. * * @param session The bean session to use for creating beans. */ public JsonList(BeanSession session) { this.session = session; } /** * Construct a list initialized with the specified list. * * @param copyFrom * The list to copy. *
Can be null. */ public JsonList(Collection copyFrom) { super(copyFrom); } /** * Construct a list initialized with the specified JSON. * * @param json * The JSON text to parse. *
Can be normal or simplified JSON. * @throws ParseException Malformed input encountered. */ public JsonList(CharSequence json) throws ParseException { this(json, JsonParser.DEFAULT); } /** * Construct a list initialized with the specified string. * * @param in * The input being parsed. *
Can be null. * @param p * The parser to use to parse the input. *
If null, uses {@link JsonParser}. * @throws ParseException Malformed input encountered. */ public JsonList(CharSequence in, Parser p) throws ParseException { this(p == null ? BeanContext.DEFAULT_SESSION : p.getBeanContext().getSession()); if (p == null) p = JsonParser.DEFAULT; if (in != null) p.parseIntoCollection(in, this, bs().object()); } /** * Construct a list initialized with the specified reader containing JSON. * * @param json * The reader containing JSON text to parse. *
Can contain normal or simplified JSON. * @throws ParseException Malformed input encountered. */ public JsonList(Reader json) throws ParseException { parse(json, JsonParser.DEFAULT); } /** * Construct a list initialized with the specified string. * * @param in * The reader containing the input being parsed. *
Can contain normal or simplified JSON. * @param p * The parser to use to parse the input. *
If null, uses {@link JsonParser}. * @throws ParseException Malformed input encountered. */ public JsonList(Reader in, Parser p) throws ParseException { this(p == null ? BeanContext.DEFAULT_SESSION : p.getBeanContext().getSession()); parse(in, p); } /** * Construct a list initialized with the contents. * * @param entries The entries to add to this list. */ public JsonList(Object... entries) { Collections.addAll(this, entries); } //------------------------------------------------------------------------------------------------------------------ // Creators //------------------------------------------------------------------------------------------------------------------ /** * Construct an empty list. * * @return An empty list. */ public static JsonList create() { return new JsonList(); } /** * Construct a list initialized with the specified list. * * @param values * The list to copy. *
Can be null. * @return A new list or null if the list was null. */ public static JsonList of(Collection values) { return values == null ? null : new JsonList(values); } /** * Convenience method for creating a list of collection objects. * * @param values The initial values. * @return A new list. */ public static JsonList ofCollections(Collection...values) { JsonList l = new JsonList(); for (Collection v : values) l.add(v); return l; } /** * Convenience method for creating a list of array objects. * * @param values The initial values. * @return A new list. */ public static JsonList ofArrays(Object[]...values) { JsonList l = new JsonList(); for (Object[] v : values) l.add(v); return l; } /** * Construct a list initialized with the specified JSON string. * * @param json * The JSON text to parse. *
Can be normal or simplified JSON. * @return A new list or null if the string was null. * @throws ParseException Malformed input encountered. */ public static JsonList ofJson(CharSequence json) throws ParseException { return json == null ? null : new JsonList(json); } /** * Construct a list initialized with the specified string. * * @param in * The input being parsed. *
Can be null. * @param p * The parser to use to parse the input. *
If null, uses {@link JsonParser}. * @return A new list or null if the input was null. * @throws ParseException Malformed input encountered. */ public static JsonList ofText(CharSequence in, Parser p) throws ParseException { return in == null ? null : new JsonList(in, p); } /** * Construct a list initialized with the specified reader containing JSON. * * @param json * The reader containing JSON text to parse. *
Can contain normal or simplified JSON. * @return A new list or null if the input was null. * @throws ParseException Malformed input encountered. */ public static JsonList ofJson(Reader json) throws ParseException { return json == null ? null : new JsonList(json); } /** * Construct a list initialized with the specified string. * * @param in * The reader containing the input being parsed. *
Can contain normal or simplified JSON. * @param p * The parser to use to parse the input. *
If null, uses {@link JsonParser}. * @return A new list or null if the input was null. * @throws ParseException Malformed input encountered. */ public static JsonList ofText(Reader in, Parser p) throws ParseException { return in == null ? null : new JsonList(in); } /** * Construct a list initialized with the specified values. * * @param values The values to add to this list. * @return A new list, never null. */ public static JsonList of(Object... values) { return new JsonList(values); } //------------------------------------------------------------------------------------------------------------------ // Initializers //------------------------------------------------------------------------------------------------------------------ /** * Override the default bean session used for converting POJOs. * *

* Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases. * *

* Useful if you're serializing/parsing beans with transforms defined. * * @param session The new bean session. * @return This object. */ public JsonList session(BeanSession session) { this.session = session; return this; } //------------------------------------------------------------------------------------------------------------------ // Appenders //------------------------------------------------------------------------------------------------------------------ /** * Adds the value to this list. * * @param value The value to add to this list. * @return This object. */ public JsonList append(Object value) { add(value); return this; } /** * Adds all the values in the specified array to this list. * * @param values The values to add to this list. * @return This object. */ public JsonList append(Object...values) { Collections.addAll(this, values); return this; } /** * Adds all the values in the specified collection to this list. * * @param values The values to add to this list. * @return This object. */ public JsonList append(Collection values) { if (values != null) addAll(values); return this; } /** * Adds an entry to this list if the boolean flag is true. * * @param flag The boolean flag. * @param value The value to add. * @return This object. */ public JsonList appendIf(boolean flag, Object value) { if (flag) append(value); return this; } /** * Adds all the entries in the specified collection to this list in reverse order. * * @param values The collection to add to this list. * @return This object. */ public JsonList appendReverse(List values) { for (ListIterator i = values.listIterator(values.size()); i.hasPrevious();) add(i.previous()); return this; } /** * Adds the contents of the array to the list in reverse order. * *

* i.e. add values from the array from end-to-start order to the end of the list. * * @param values The collection to add to this list. * @return This object. */ public JsonList appendReverse(Object...values) { for (int i = values.length - 1; i >= 0; i--) add(values[i]); return this; } /** * Add if predicate matches. * * @param The type being tested. * @param test The predicate to match against. * @param value The value to add if the predicate matches. * @return This object. */ public JsonList appendIf(Predicate test, T value) { return appendIf(test(test, value), value); } //------------------------------------------------------------------------------------------------------------------ // Retrievers //------------------------------------------------------------------------------------------------------------------ /** * Get the entry at the specified index, converted to the specified type. * *

* This is the preferred get method for simple types. * *

Examples:
*

* JsonList list = JsonList.ofJson("..."); * * // Value converted to a string. * String string = list.get(1, String.class); * * // Value converted to a bean. * MyBean bean = list.get(2, MyBean.class); * * // Value converted to a bean array. * MyBean[] beanArray = list.get(3, MyBean[].class); * * // Value converted to a linked-list of objects. * List list2 = list.get(4, LinkedList.class); * * // Value converted to a map of object keys/values. * Map map = list.get(5, TreeMap.class); *

* *

* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. * * @param index The index into this list. * @param type The type of object to convert the entry to. * @param The type of object to convert the entry to. * @return The converted entry. */ public T get(int index, Class type) { return bs().convertToType(get(index), type); } /** * Get the entry at the specified index, converted to the specified type. * *

* The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). * *

Examples:
*

* JsonList list = JsonList.ofJson("..."); * * // Value converted to a linked-list of strings. * List<String> list1 = list.get(1, LinkedList.class, String.class); * * // Value converted to a linked-list of beans. * List<MyBean> list2 = list.get(2, LinkedList.class, MyBean.class); * * // Value converted to a linked-list of linked-lists of strings. * List<List<String>> list3 = list.get(3, LinkedList.class, LinkedList.class, String.class); * * // Value converted to a map of string keys/values. * Map<String,String> map1 = list.get(4, TreeMap.class, String.class, String.class); * * // Value converted to a map containing string keys and values of lists containing beans. * Map<String,List<MyBean>> map2 = list.get(5, TreeMap.class, String.class, List.class, MyBean.class); *

* *

* Collection classes are assumed to be followed by zero or one objects indicating the element type. * *

* Map classes are assumed to be followed by zero or two meta objects indicating the key and value types. * *

* The array can be arbitrarily long to indicate arbitrarily complex data structures. * *

* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. * * @param index The index into this list. * @param type The type of object to convert the entry to. * @param args The type arguments of the type to convert the entry to. * @param The type of object to convert the entry to. * @return The converted entry. */ public T get(int index, Type type, Type...args) { return bs().convertToType(get(index), type, args); } /** * Shortcut for calling get(index, String.class). * * @param index The index. * @return The converted value. */ public String getString(int index) { return get(index, String.class); } /** * Shortcut for calling get(index, Integer.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public Integer getInt(int index) { return get(index, Integer.class); } /** * Shortcut for calling get(index, Boolean.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public Boolean getBoolean(int index) { return get(index, Boolean.class); } /** * Shortcut for calling get(index, Long.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public Long getLong(int index) { return get(index, Long.class); } /** * Shortcut for calling get(index, JsonMap.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public JsonMap getMap(int index) { return get(index, JsonMap.class); } /** * Same as {@link #getMap(int)} except converts the keys and values to the specified types. * * @param The key type class. * @param The value type class. * @param index The index. * @param keyType The key type class. * @param valType The value type class. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public Map getMap(int index, Class keyType, Class valType) { return bs().convertToType(get(index), Map.class, keyType, valType); } /** * Shortcut for calling get(index, JsonList.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public JsonList getList(int index) { return get(index, JsonList.class); } /** * Same as {@link #getList(int)} except converts the elements to the specified types. * * @param The element type. * @param index The index. * @param elementType The element type class. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public List getList(int index, Class elementType) { return bs().convertToType(get(index), List.class, elementType); } //------------------------------------------------------------------------------------------------------------------ // POJO REST methods. //------------------------------------------------------------------------------------------------------------------ /** * Same as {@link #get(int,Class) get(int,Class)}, but the key is a slash-delimited path used to traverse entries in * this POJO. * *

* For example, the following code is equivalent: *

*

* JsonList list = JsonList.ofJson("..."); * * // Long way * long long1 = list.getMap("0").getLong("baz"); * * // Using this method * long long2 = list.getAt("0/baz", long.class); *

* *

* This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @param type The class type. * * @param The class type. * @return The value, or null if the entry doesn't exist. */ public T getAt(String path, Class type) { return getObjectRest().get(path, type); } /** * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections. * * @param path The path to the entry. * @param type The class type. * @param args The class parameter types. * * @param The class type. * @return The value, or null if the entry doesn't exist. */ public T getAt(String path, Type type, Type...args) { return getObjectRest().get(path, type, args); } /** * Same as {@link #set(int,Object) set(int,Object)}, but the key is a slash-delimited path used to traverse entries * in this POJO. * *

* For example, the following code is equivalent: *

*

* JsonList list = JsonList.ofJson("..."); * * // Long way * list.getMap("0").put("baz", 123); * * // Using this method * list.putAt("0/baz", 123); *

* *

* This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @param o The new value. * @return The previous value, or null if the entry doesn't exist. */ public Object putAt(String path, Object o) { return getObjectRest().put(path, o); } /** * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays. * *

* For example, the following code is equivalent: *

*

* JsonList list = JsonList.ofJson("..."); * * // Long way * list.getMap(0).getList("bar").append(123); * * // Using this method * list.postAt("0/bar", 123); *

* *

* This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @param o The new value. * @return The previous value, or null if the entry doesn't exist. */ public Object postAt(String path, Object o) { return getObjectRest().post(path, o); } /** * Similar to {@link #remove(int) remove(int)},but the key is a slash-delimited path used to traverse entries in * this POJO. * *

* For example, the following code is equivalent: *

*

* JsonList list = JsonList.ofJson("..."); * * // Long way * list.getMap(0).getList("bar").delete(0); * * // Using this method * list.deleteAt("0/bar/0"); *

* *

* This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @return The previous value, or null if the entry doesn't exist. */ public Object deleteAt(String path) { return getObjectRest().delete(path); } //------------------------------------------------------------------------------------------------------------------ // Other methods //------------------------------------------------------------------------------------------------------------------ /** * Returns the {@link BeanSession} currently associated with this list. * * @return The {@link BeanSession} currently associated with this list. */ public BeanSession getBeanSession() { return session; } /** * Sets the {@link BeanSession} currently associated with this list. * * @param value The {@link BeanSession} currently associated with this list. * @return This object. */ public JsonList setBeanSession(BeanSession value) { this.session = value; return this; } /** * Creates an {@link Iterable} with elements of the specified child type. * *

* Attempts to convert the child objects to the correct type if they aren't already the correct type. * *

* The next() method on the returned iterator may throw a {@link InvalidDataConversionException} if * the next element cannot be converted to the specified type. * *

* See {@link BeanSession#convertToType(Object, ClassMeta)} for a description of valid conversions. * *

Example:
*

* // Iterate over a list of JsonMaps. * JsonList list = JsonList.ofJson("[{foo:'bar'},{baz:123}]"); * for (JsonMap map : list.elements(JsonMap.class)) { * // Do something with map. * } * * // Iterate over a list of ints. * JsonList list = JsonList.ofJson("[1,2,3]"); * for (Integer i : list.elements(Integer.class)) { * // Do something with i. * } * * // Iterate over a list of beans. * // Automatically converts to beans. * JsonList list = JsonList.ofJson("[{name:'John Smith',age:45}]"); * for (Person p : list.elements(Person.class)) { * // Do something with p. * } *

* * @param The child object type. * @param childType The child object type. * @return A new Iterable object over this list. */ public Iterable elements(final Class childType) { final Iterator iterator = iterator(); return () -> new Iterator<>() { @Override /* Iterator */ public boolean hasNext() { return iterator.hasNext(); } @Override /* Iterator */ public E next() { return bs().convertToType(iterator.next(), childType); } @Override /* Iterator */ public void remove() { iterator.remove(); } }; } /** * Returns the {@link ClassMeta} of the class of the object at the specified index. * * @param index An index into this list, zero-based. * @return The data type of the object at the specified index, or null if the value is null. */ public ClassMeta getClassMeta(int index) { return bs().getClassMetaForObject(get(index)); } /** * Serialize this array to a string using the specified serializer. * * @param serializer The serializer to use to convert this object to a string. * @return This object as a serialized string. */ public String asString(WriterSerializer serializer) { return serializer.toString(this); } /** * Serialize this array to Simplified JSON. * * @return This object as a serialized string. */ public String asString() { return Json5Serializer.DEFAULT.toString(this); } /** * Returns true if this list is unmodifiable. * * @return true if this list is unmodifiable. */ public boolean isUnmodifiable() { return false; } /** * Returns a modifiable copy of this list if it's unmodifiable. * * @return A modifiable copy of this list if it's unmodifiable, or this list if it is already modifiable. */ public JsonList modifiable() { if (isUnmodifiable()) return new JsonList(this); return this; } /** * Returns an unmodifiable copy of this list if it's modifiable. * * @return An unmodifiable copy of this list if it's modifiable, or this list if it is already unmodifiable. */ public JsonList unmodifiable() { if (this instanceof UnmodifiableJsonList) return this; return new UnmodifiableJsonList(this); } /** * Convenience method for serializing this JsonList to the specified Writer using the JsonSerializer.DEFAULT * serializer. * * @param w The writer to send the serialized contents of this object. * @return This object. * @throws IOException If a problem occurred trying to write to the writer. * @throws SerializeException If a problem occurred trying to convert the output. */ public JsonList writeTo(Writer w) throws IOException, SerializeException { JsonSerializer.DEFAULT.serialize(this, w); return this; } /** * Converts this object into the specified class type. * *

* TODO - The current implementation is very inefficient. * * @param cm The class type to convert this object to. * @return A converted object. */ public Object cast(ClassMeta cm) { try { return JsonParser.DEFAULT.parse(Json5Serializer.DEFAULT.serialize(this), cm); } catch (ParseException | SerializeException e) { throw asRuntimeException(e); } } //------------------------------------------------------------------------------------------------------------------ // Utility methods //------------------------------------------------------------------------------------------------------------------ private void parse(Reader r, Parser p) throws ParseException { if (p == null) p = JsonParser.DEFAULT; p.parseIntoCollection(r, this, bs().object()); } private ObjectRest getObjectRest() { if (objectRest == null) objectRest = new ObjectRest(this); return objectRest; } BeanSession bs() { if (session == null) session = BeanContext.DEFAULT_SESSION; return session; } private static final class UnmodifiableJsonList extends JsonList { private static final long serialVersionUID = 1L; UnmodifiableJsonList(JsonList contents) { if (contents != null) this.forEach(super::add); } @Override /* List */ public void add(int location, Object object) { throw new UnsupportedOperationException("Not supported on read-only object."); } @Override /* List */ public Object remove(int location) { throw new UnsupportedOperationException("Not supported on read-only object."); } @Override /* List */ public Object set(int location, Object object) { throw new UnsupportedOperationException("Not supported on read-only object."); } @Override public boolean isUnmodifiable() { return true; } } //------------------------------------------------------------------------------------------------------------------ // Overridden methods. //------------------------------------------------------------------------------------------------------------------ /** * A synonym for {@link #toString()} * * @return This object as a JSON string. */ public String asJson() { return toString(); } @Override /* Object */ public String toString() { return Json5.of(this); } }