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

io.setl.json.CJObject Maven / Gradle / Ivy

Go to download

An implementation of the Canonical JSON format with support for javax.json and Jackson

The newest version!
package io.setl.json;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.AbstractMap.SimpleEntry;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.Spliterator;
import java.util.TreeMap;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import jakarta.json.JsonArray;
import jakarta.json.JsonNumber;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import io.setl.json.CJArray.MySpliterator;
import io.setl.json.exception.IncorrectTypeException;
import io.setl.json.exception.MissingItemException;
import io.setl.json.io.Generator;
import io.setl.json.jackson.JsonObjectSerializer;
import io.setl.json.primitive.CJFalse;
import io.setl.json.primitive.CJNull;
import io.setl.json.primitive.CJString;
import io.setl.json.primitive.CJTrue;
import io.setl.json.primitive.numbers.CJNumber;

/**
 * Representation of an object in JSON.
 *
 * 

No value in the object can be null. If you try to add one, it will be replaced by a Canonical instance holding a null. * *

As JSON objects can contain mixed content, this class provides type-checking accessors to the object members. There are multiple varieties of each * accessor which obey these contracts: * *

*
optType(key)
*
*
    *
  • If the key is not present, returns null.
  • *
  • If the entry is not the required type, returns null. *
  • Otherwise returns the entry *
*
* *
getType(key, default)
*
*
    *
  • If the key is not present, returns the default. *
  • If the entry is not the required type, returns the default. *
  • Otherwise returns the entry *
*
*
getType(key, function)
*
*
    *
  • If the key is not present, invokes the function to derive a suitable value. *
  • If the entry is not the required type, invokes the function to derive a suitable value. *
  • Otherwise returns the entry *
*
*
getType(key)
*
*
    *
  • If the key is not present, throws a {@link MissingItemException}. *
  • If the entry is not the required type, throws an {@link IncorrectTypeException}. *
  • Otherwise returns the entry *
*
*
* *

The numeric accessors follow the normal Java rules for primitive type conversions and consider any number to be the correct type. For example, if you * call {@link #getInt(String)} and the value is the Long value 1L<<50, then the call returns the value of Integer.MAX_VALUE, as would be expected * for a narrowing primitive conversion, rather than throwing a {@link IncorrectTypeException}. */ @JsonSerialize(using = JsonObjectSerializer.class) public class CJObject implements NavigableMap, JsonObject, Canonical { /** * Sort object keys into Unicode code point order. */ @SuppressWarnings("java:S127") // Allow incrementing loop counter inside loop as code points can be one or two characters. public static final Comparator CODE_POINT_ORDER = (s1, s2) -> { int len1 = s1.length(); int len2 = s2.length(); int lim = Math.min(len1, len2); for (int i = 0; i < lim; i++) { int cp1 = s1.codePointAt(i); int cp2 = s2.codePointAt(i); if (cp1 != cp2) { return cp1 - cp2; } if (cp1 > 0xffff) { i++; } } return len1 - len2; }; /** * Set which converts JsonValue to Canonicals. */ static class MyEntries implements Set> { private final Set> mySet; MyEntries(Set> mySet) { this.mySet = mySet; } @Override public boolean add(Entry entry) { throw new UnsupportedOperationException("Cannot add to the entry set of a Java Map."); } @Override public boolean addAll(@Nonnull Collection> c) { throw new UnsupportedOperationException("Cannot add to the entry set of a Java Map."); } @Override public void clear() { mySet.clear(); } @Override public boolean contains(Object o) { if (!(o instanceof Entry)) { return false; } Entry e = (Entry) o; if (!(e.getKey() instanceof String)) { return false; } Object v = e.getValue(); if (v instanceof Canonical) { return mySet.contains(e); } if (v != null && !(v instanceof JsonValue)) { return false; } return mySet.contains(new SimpleEntry<>(e.getKey(), Canonical.cast((JsonValue) e.getValue()))); } @Override public boolean containsAll(Collection c) { for (Object o : c) { if (!contains(o)) { return false; } } return true; } @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object o) { return mySet.equals(o); } @Override public int hashCode() { return mySet.hashCode(); } @Override public boolean isEmpty() { return mySet.isEmpty(); } @Override @Nonnull public Iterator> iterator() { final Iterator> myIterator = mySet.iterator(); return new Iterator<>() { @Override public boolean hasNext() { return myIterator.hasNext(); } @Override public Entry next() { return new MyEntry(myIterator.next()); } @Override public void remove() { myIterator.remove(); } }; } @Override public Stream> parallelStream() { return mySet.parallelStream().map(MyEntry::new); } @Override public boolean remove(Object o) { return mySet.remove(o); } @Override public boolean removeAll(@Nonnull Collection c) { return mySet.removeAll(c); } @Override public boolean removeIf(Predicate> filter) { return mySet.removeIf(e -> filter.test(new MyEntry(e))); } @Override public boolean retainAll(@Nonnull Collection c) { return mySet.retainAll(c); } @Override public int size() { return mySet.size(); } @Override public Spliterator> spliterator() { return new MyEntrySpliterator(mySet.spliterator()); } @Override public Stream> stream() { return mySet.stream().map(MyEntry::new); } @Override @Nonnull public Object[] toArray() { return mySet.toArray(); } @Override @Nonnull public T[] toArray(@Nonnull T[] a) { return mySet.toArray(a); } @Override @Nonnull public T[] toArray(@Nonnull IntFunction generator) { return mySet.toArray(generator); } @Override public String toString() { return mySet.toString(); } } static class MyEntry implements Entry { private final Entry me; public MyEntry(Entry me) { this.me = me; } @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object o) { if (this == o) { return true; } return me.equals(o); } @Override public String getKey() { return me.getKey(); } @Override public JsonValue getValue() { return me.getValue(); } @Override public int hashCode() { return me.hashCode(); } @Override public JsonValue setValue(JsonValue value) { return me.setValue(Canonical.cast(value)); } @Override public String toString() { return String.valueOf(me); } } /** * Spliterator over JsonValues instead of Canonical. If someone called setValue on the output, we have to convert to a Canonical. */ static class MyEntrySpliterator implements Spliterator> { private final Spliterator> me; MyEntrySpliterator(Spliterator> me) { this.me = me; } @Override public int characteristics() { return me.characteristics(); } @Override public long estimateSize() { return me.estimateSize(); } @Override public long getExactSizeIfKnown() { return me.getExactSizeIfKnown(); } @Override public boolean tryAdvance(Consumer> action) { return me.tryAdvance(e -> action.accept(new MyEntry(e))); } @Override public Spliterator> trySplit() { Spliterator> newSplit = me.trySplit(); if (newSplit != null) { return new MyEntrySpliterator(newSplit); } return null; } } private static class MyValues implements Collection { private final Collection me; MyValues(Collection me) { this.me = me; } public boolean add(JsonValue value) { throw new UnsupportedOperationException("Add is not supported on a Java Map's values collection"); } public boolean addAll(@Nonnull Collection c) { throw new UnsupportedOperationException("Add is not supported on a Java Map's values collection"); } @Override public void clear() { me.clear(); } @Override public boolean contains(Object o) { return me.contains(o); } @Override public boolean containsAll(@Nonnull Collection c) { return me.containsAll(c); } @Override public boolean isEmpty() { return me.isEmpty(); } @Override @Nonnull public Iterator iterator() { final Iterator myIterator = me.iterator(); return new Iterator<>() { @Override public boolean hasNext() { return myIterator.hasNext(); } @Override public JsonValue next() { return myIterator.next(); } @Override public void remove() { myIterator.remove(); } }; } @Override public Stream parallelStream() { return me.parallelStream().map(JsonValue.class::cast); } @Override public boolean remove(Object o) { return me.remove(o); } @Override public boolean removeAll(@Nonnull Collection c) { return me.removeAll(c); } @Override public boolean removeIf(Predicate filter) { return me.removeIf(filter); } @Override public boolean retainAll(@Nonnull Collection c) { return me.retainAll(c); } @Override public int size() { return me.size(); } @Override public Spliterator spliterator() { return new MySpliterator(me.spliterator()); } @Override public Stream stream() { return me.stream().map(JsonValue.class::cast); } @Override @Nonnull public Object[] toArray() { return me.toArray(); } @Override @Nonnull public T[] toArray(@Nonnull T[] a) { return me.toArray(a); } @Override public T[] toArray(IntFunction generator) { return me.toArray(generator); } } /** * Convert any map into a JObject. * * @param map the map to convert * * @return the equivalent JObject */ public static CJObject asJObject(Map map) { if (map instanceof CJObject) { return (CJObject) map; } CJObject out = new CJObject(); for (Map.Entry entry : map.entrySet()) { Object key = entry.getKey(); if (key == null) { throw new IllegalArgumentException("Map keys must not be null"); } if (!(key instanceof String)) { throw new IllegalArgumentException("Map keys must be Strings, not " + key.getClass()); } Object value = entry.getValue(); Canonical canonical = Canonical.create(value); out.put((String) key, canonical); } return out; } private final NavigableMap myMap; /** New instance. */ public CJObject() { myMap = new TreeMap<>(CODE_POINT_ORDER); } /** * Create a new instance as a deep copy of the provided map. * * @param map the map to copy. */ public CJObject(Map map) { myMap = new TreeMap<>(CODE_POINT_ORDER); for (Entry e : map.entrySet()) { myMap.put(e.getKey(), Canonical.create(e.getValue())); } } private CJObject(NavigableMap map, boolean makeCopy) { if (makeCopy) { myMap = new TreeMap<>(CODE_POINT_ORDER); for (Entry e : map.entrySet()) { myMap.put(e.getKey(), e.getValue().copy()); } } else { myMap = map; } } @Override public CJObject asJsonObject() { return this; } /** * Offer every mapping in this JSON object to the consumer. * * @param action the consumer */ public void canonicalForEach(BiConsumer action) { myMap.forEach(action); } @Override public Entry ceilingEntry(String key) { return new MyEntry(myMap.ceilingEntry(key)); } @Override public String ceilingKey(String key) { return myMap.ceilingKey(key); } @Override public void clear() { myMap.clear(); } @Override public Comparator comparator() { return myMap.comparator(); } @Override public Canonical compute(String key, BiFunction remappingFunction) { final BiFunction myFunction = (k, v) -> Canonical.cast(remappingFunction.apply(k, v)); return myMap.compute(key, myFunction); } @Override public Canonical computeIfAbsent(String key, Function mappingFunction) { final Function myFunction = k -> Canonical.cast(mappingFunction.apply(k)); return myMap.computeIfAbsent(key, myFunction); } @Override public Canonical computeIfPresent(String key, BiFunction remappingFunction) { final BiFunction myFunction = (k, v) -> Canonical.cast(remappingFunction.apply(k, v)); return myMap.computeIfPresent(key, myFunction); } @Override public boolean containsKey(Object key) { return myMap.containsKey(key); } @Override public boolean containsValue(Object value) { return myMap.containsValue(value); } @Override public CJObject copy() { return new CJObject(myMap, true); } @Override public NavigableSet descendingKeySet() { return myMap.descendingKeySet(); } @Override public NavigableMap descendingMap() { return new CJObject(myMap.descendingMap(), false); } @Override @Nonnull public Set> entrySet() { return new MyEntries(myMap.entrySet()); } @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object o) { if (o == this) { return true; } return myMap.equals(o); } @Override public Entry firstEntry() { return new MyEntry(myMap.firstEntry()); } @Override public String firstKey() { return myMap.firstKey(); } @Override public Entry floorEntry(String key) { return new MyEntry(myMap.floorEntry(key)); } @Override public String floorKey(String key) { return myMap.floorKey(key); } @Override public void forEach(BiConsumer action) { myMap.forEach(action); } @Override public Canonical get(Object key) { return myMap.get(key); } /** * Get an array from the object. * * @param key the key * @param defaultValue the default * * @return the array, or the default */ public CJArray getArray(String key, @Nonnull Function defaultValue) { return getQuiet(CJArray.class, key, defaultValue); } /** * Get an array from the object. * * @param key the key * @param defaultValue the default * * @return the array, or the default */ @Nonnull public CJArray getArray(String key, @Nonnull CJArray defaultValue) { return getQuiet(CJArray.class, key, defaultValue); } /** * Get an array from the object. * * @param key the entry's key * * @return the array */ @Nonnull public CJArray getArray(String key) { return getSafe(CJArray.class, ValueType.ARRAY, key); } /** * Get a big decimal from the object. * * @param key the key * @param defaultValue the default * * @return the big decimal, or the default */ @Nonnull public BigDecimal getBigDecimal(String key, @Nonnull BigDecimal defaultValue) { Number n = getQuiet(Number.class, key); if (n == null) { return defaultValue; } return Canonical.toBigDecimal(n); } /** * Get a big decimal from the object. * * @param key the key * @param defaultValue the default * * @return the big decimal, or the default */ public BigDecimal getBigDecimal(String key, @Nonnull Function defaultValue) { Number n = getQuiet(Number.class, key); if (n == null) { return defaultValue.apply(key); } return Canonical.toBigDecimal(n); } /** * Get a big decimal from the object. * * @param key the entry's key * * @return the big decimal */ @Nonnull public BigDecimal getBigDecimal(String key) { Number n = getSafe(Number.class, ValueType.NUMBER, key); return Canonical.toBigDecimal(n); } /** * Get a big integer from the object. * * @param key the key * @param defaultValue the default * * @return the big integer, or the default */ @Nonnull public BigInteger getBigInteger(String key, @Nonnull BigInteger defaultValue) { Number n = getQuiet(Number.class, key); if (n == null) { return defaultValue; } return Canonical.toBigInteger(n); } /** * Get a big integer from the object. * * @param key the key * @param defaultValue the default * * @return the big integer, or the default */ public BigInteger getBigInteger(String key, @Nonnull Function defaultValue) { Number n = getQuiet(Number.class, key); if (n == null) { return defaultValue.apply(key); } return Canonical.toBigInteger(n); } /** * Get a big integer from the object. * * @param key the entry's key * * @return the big integer */ @Nonnull public BigInteger getBigInteger(String key) { Number n = getSafe(Number.class, ValueType.NUMBER, key); return Canonical.toBigInteger(n); } /** * Get a Boolean from the object. * * @param key the key * @param defaultValue the default * * @return the Boolean, or the default */ public boolean getBoolean(String key, boolean defaultValue) { return getQuiet(Boolean.class, key, defaultValue); } /** * Get a Boolean from the object. * * @param key the key * @param defaultValue the default * * @return the Boolean, or the default */ public boolean getBoolean(String key, @Nonnull Predicate defaultValue) { Boolean value = getQuiet(Boolean.class, key); return (value != null) ? value : defaultValue.test(key); } /** * Get a Boolean from the object. * * @return the Boolean */ public boolean getBoolean(String key) { Canonical canonical = getCanonical(key); if (canonical == null) { throw new MissingItemException(key, IS_BOOLEAN); } Object value = canonical.getValue(); if (value instanceof Boolean) { return (Boolean) value; } throw new IncorrectTypeException(key, Canonical.IS_BOOLEAN, canonical.getValueType()); } /** * Get the canonical JSON value stored under the specified key. * * @param name the key * * @return the canonical JSON value */ public Canonical getCanonical(String name) { return get(name); } /** * Get a double from the object. * * @param key the key * @param defaultValue the default * * @return the double, or the default */ public double getDouble(String key, double defaultValue) { Double n = optDouble(key); return (n != null) ? n : defaultValue; } /** * Get a double from the object. * * @param key the key * @param defaultValue the default * * @return the double, or the default */ public double getDouble(String key, @Nonnull ToDoubleFunction defaultValue) { Double n = optDouble(key); return (n != null) ? n : defaultValue.applyAsDouble(key); } /** * Get a double from the object. * * @param key the entry's key * * @return the double */ public double getDouble(String key) { Canonical p = getCanonical(key); if (p == null) { throw new MissingItemException(key, ValueType.NUMBER); } Double n = optDouble(key); if (n == null) { throw new IncorrectTypeException(key, ValueType.NUMBER, p.getValueType()); } return n; } /** * Get an integer from the object. * * @param key the key * @param defaultValue the default * * @return the integer, or the default */ public int getInt(String key, int defaultValue) { Number n = getQuiet(Number.class, key); return (n != null) ? n.intValue() : defaultValue; } /** * Get an integer from the object. * * @param key the key * @param defaultValue the default * * @return the integer, or the default */ public int getInt(String key, @Nonnull ToIntFunction defaultValue) { Number n = getQuiet(Number.class, key); return (n != null) ? n.intValue() : defaultValue.applyAsInt(key); } /** * Get an integer from the object. * * @return the integer */ @Override public int getInt(String key) { return getSafe(Number.class, ValueType.NUMBER, key).intValue(); } @Override public JsonArray getJsonArray(String name) { return optArray(name); } @Override public JsonNumber getJsonNumber(String name) { Canonical p = getCanonical(name); return (p != null) ? (CJNumber) p : null; } @Override public JsonObject getJsonObject(String name) { return optObject(name); } @Override public JsonString getJsonString(String name) { Canonical p = getCanonical(name); return (p != null) ? (CJString) p : null; } /** * Get a JsonValue from this object. The value must exist. * * @param key the value's key * * @return the value */ public JsonValue getJsonValue(String key) { Canonical canonical = getCanonical(key); if (canonical == null) { throw new MissingItemException(key, EnumSet.allOf(ValueType.class)); } return canonical; } /** * Get a JsonValue from this object. If the value does not exist, the default value is returned. * * @param key the value's key * @param defaultValue the default value * * @return the value */ public JsonValue getJsonValue(String key, JsonValue defaultValue) { Canonical canonical = getCanonical(key); if (canonical == null) { return defaultValue; } return canonical; } /** * Get a JsonValue from this object. If the value does not exist, the function is invoked to create a value. * * @param key the value's key * @param defaultValue supplier of a default value * * @return the value */ public JsonValue getJsonValue(String key, @Nonnull Function defaultValue) { Canonical canonical = getCanonical(key); if (canonical == null) { return defaultValue.apply(key); } return canonical; } /** * Get a long from the object. * * @param key the key * @param defaultValue the default * * @return the long, or the default */ public long getLong(String key, long defaultValue) { Number n = getQuiet(Number.class, key); return (n != null) ? n.longValue() : defaultValue; } /** * Get a long from the object. * * @param key the key * @param defaultValue the default * * @return the long, or the default */ public long getLong(String key, @Nonnull ToLongFunction defaultValue) { Number n = getQuiet(Number.class, key); return (n != null) ? n.longValue() : defaultValue.applyAsLong(key); } /** * Get a long from the object. * * @param key the entry's key * * @return the long */ public long getLong(String key) { Number n = getSafe(Number.class, ValueType.NUMBER, key); return n.longValue(); } /** * Get an object from the object. * * @param key the key * @param defaultValue the default * * @return the object, or the default */ public CJObject getObject(String key, @Nonnull Function defaultValue) { return getQuiet(CJObject.class, key, defaultValue); } /** * Get an object from the object. * * @param key the key * @param defaultValue the default * * @return the object, or the default */ public CJObject getObject(String key, CJObject defaultValue) { return getQuiet(CJObject.class, key, defaultValue); } /** * Get an object from the object. * * @param key the entry's key * * @return the object */ @Nonnull public CJObject getObject(String key) { return getSafe(CJObject.class, ValueType.OBJECT, key); } @Override public JsonValue getOrDefault(Object key, JsonValue defaultValue) { JsonValue value = myMap.get(key); return value != null ? value : defaultValue; } private T getQuiet(Class clazz, String key) { return getQuiet(clazz, key, (Function) k -> null); } private T getQuiet(Class clazz, String key, Function function) { Canonical canonical = getCanonical(key); if (canonical == null) { return function.apply(key); } Object value = canonical.getValue(); if (clazz.isInstance(value)) { return clazz.cast(value); } return function.apply(key); } private T getQuiet(Class clazz, String key, T defaultValue) { return getQuiet(clazz, key, (Function) k -> defaultValue); } private T getSafe(Class clazz, ValueType type, String key) { Canonical canonical = getCanonical(key); if (canonical == null) { throw new MissingItemException(key, type); } Object value = canonical.getValue(); if (clazz.isInstance(value)) { return clazz.cast(value); } throw new IncorrectTypeException(key, type, canonical.getValueType()); } /** * Get a String from the object. * * @param key the key * @param defaultValue the default * * @return the String, or the default */ public String getString(String key, @Nonnull UnaryOperator defaultValue) { return getQuiet(String.class, key, defaultValue); } /** * Get a String from the object. * * @param key the key * @param defaultValue the default * * @return the String, or the default */ public String getString(String key, String defaultValue) { return getQuiet(String.class, key, defaultValue); } /** * Get a String from the object. * * @return the String */ @Nonnull public String getString(String key) { return getSafe(String.class, ValueType.STRING, key); } @Override public T getValue(Class reqType, T defaultValue) { if (reqType.isInstance(this)) { return reqType.cast(this); } return defaultValue; } @Override public Object getValue() { return this; } @Override public T getValueSafe(Class reqType) { return reqType.cast(this); } @Override public ValueType getValueType() { return ValueType.OBJECT; } @Override public int hashCode() { return myMap.hashCode(); } @Override public NavigableMap headMap(String toKey, boolean inclusive) { return new CJObject(myMap.headMap(toKey, inclusive), false); } @Override @Nonnull public SortedMap headMap(String toKey) { return new CJObject(myMap.headMap(toKey, false), false); } @Override public Entry higherEntry(String key) { return new MyEntry(myMap.higherEntry(key)); } @Override public String higherKey(String key) { return myMap.higherKey(key); } @Override public boolean isEmpty() { return myMap.isEmpty(); } @Override public boolean isNull(String name) { Canonical p = getCanonical(name); if (p == null) { throw new MissingItemException(name, ValueType.NULL); } return p.getValueType() == ValueType.NULL; } /** * Verify if the type of the specified property is as required. * * @param key the key * @param type the desired type * * @return True if the property exists and has the required type. False if the property exists and does not have the required type. * * @throws MissingItemException if the property does not exist */ public boolean isType(String key, ValueType type) { Canonical canonical = getCanonical(key); if (canonical == null) { throw new MissingItemException(key, type); } return canonical.getValueType() == type; } @Override @Nonnull public Set keySet() { return myMap.keySet(); } @Override public Entry lastEntry() { return new MyEntry(myMap.lastEntry()); } @Override public String lastKey() { return myMap.lastKey(); } @Override public Entry lowerEntry(String key) { return new MyEntry(myMap.lowerEntry(key)); } @Override public String lowerKey(String key) { return myMap.lowerKey(key); } @Override public JsonValue merge(String key, JsonValue value, BiFunction remappingFunction) { final BinaryOperator myFunction = (v1, v2) -> Canonical.cast(remappingFunction.apply(v1, v2)); return myMap.merge(key, Canonical.cast(value), myFunction); } @Override public NavigableSet navigableKeySet() { return myMap.navigableKeySet(); } /** * Get an array from the object. * * @param key the entry's key * * @return the array, or null */ @Nullable public CJArray optArray(String key) { return getQuiet(CJArray.class, key); } /** * Get a big decimal from the object. * * @param key the entry's key * * @return the big decimal, or null */ @Nullable public BigDecimal optBigDecimal(String key) { Number n = getQuiet(Number.class, key); return Canonical.toBigDecimal(n); } /** * Get a big integer from the object. * * @param key the entry's key * * @return the big integer, or null */ @Nullable public BigInteger optBigInteger(String key) { Number n = getQuiet(Number.class, key); return Canonical.toBigInteger(n); } /** * Get a Boolean from the object. * * @param key the entry's key * * @return the Boolean, or null */ @Nullable public Boolean optBoolean(String key) { return getQuiet(Boolean.class, key); } /** * Get a double from the object. * * @param key the entry's key * * @return the double, or null */ @Nullable public Double optDouble(String key) { Canonical p = getCanonical(key); if (p == null) { return null; } return CJNumber.toDouble(p); } /** * Get an integer from the object. * * @param key the entry's key * * @return the integer, or null */ @Nullable public Integer optInt(String key) { Number n = getQuiet(Number.class, key); return (n != null) ? n.intValue() : null; } /** * Get a JsonValue from this object. If the value does not exist, null is returned. * * @param key the value's key * * @return the value */ public JsonValue optJsonValue(String key) { return getCanonical(key); } /** * Get a long from the object. * * @param key the entry's key * * @return the long, or null */ @Nullable public Long optLong(String key) { Number n = getQuiet(Number.class, key); return (n != null) ? n.longValue() : null; } /** * Get an object from the object. * * @param key the entry's key * * @return the object, or null */ @Nullable public CJObject optObject(String key) { return getQuiet(CJObject.class, key); } /** * Get a String from the object. * * @param key the entry's key * * @return the String, or null */ @Nullable public String optString(String key) { return getQuiet(String.class, key); } /** * Ensure that all Strings and Numbers have a single representation in memory. */ public void optimiseStorage() { optimiseStorage(new HashMap<>()); } /** * Ensure that all Strings and Numbers have a single representation in memory. * * @param values the unique values */ void optimiseStorage(HashMap values) { for (Entry e : myMap.entrySet()) { Canonical current = e.getValue(); switch (current.getValueType()) { case ARRAY: // recurse into array ((CJArray) current).optimiseStorage(values); break; case OBJECT: // recurse into object ((CJObject) current).optimiseStorage(values); break; default: Canonical single = values.computeIfAbsent(current, c -> c); if (single != current) { e.setValue(single); } break; } } } @Override public Entry pollFirstEntry() { return new MyEntry(myMap.pollFirstEntry()); } @Override public Entry pollLastEntry() { return new MyEntry(myMap.pollLastEntry()); } @Override public JsonValue put(String key, JsonValue value) { return myMap.put(key, Canonical.cast(value)); } /** * Put a canonical JSON value into this at the specified key, replacing any existing value. * * @param key the key * @param value the value * * @return the old value */ public Canonical put(String key, Canonical value) { return myMap.put(key, value); } /** * Put a null value into this. * * @param key the key */ public void put(String key) { put(key, CJNull.NULL); } /** * Put a value into this. * * @param key the key * @param value the value * * @return the old value */ public JsonValue put(String key, Boolean value) { if (value != null) { return put(key, value ? CJTrue.TRUE : CJFalse.FALSE); } return put(key, CJNull.NULL); } /** * Put a value into this. * * @param key the key * @param value the value */ public void put(String key, CJArray value) { if (value != null) { put(key, (Canonical) value); } else { put(key, CJNull.NULL); } } /** * Put a value into this. * * @param key the key * @param value the value */ public void put(String key, CJObject value) { if (value != null) { put(key, (Canonical) value); } else { put(key, CJNull.NULL); } } /** * Put a value into this. * * @param key the key * @param value the value */ public void put(String key, Number value) { if (value != null) { put(key, CJNumber.cast(value)); } else { put(key, CJNull.NULL); } } /** * Put a value into this. * * @param key the key * @param value the value */ public void put(String key, String value) { if (value != null) { put(key, CJString.create(value)); } else { put(key, CJNull.NULL); } } /** * Put all the contents of the supplied map into this. * * @param m the map of values */ public void putAll(@Nonnull Map m) { if (m instanceof CJObject) { myMap.putAll(((CJObject) m).myMap); } else { for (Entry e : m.entrySet()) { put(e.getKey(), e.getValue()); } } } @Override public JsonValue putIfAbsent(String key, JsonValue value) { return myMap.putIfAbsent(key, Canonical.cast(value)); } @Override public Canonical remove(Object key) { return myMap.remove(key); } @Override public boolean remove(Object key, Object value) { return myMap.remove(key, value); } /** * Remove a JSON array from this. * * @param key the key to remove, if it is an array * * @return the array removed */ @Nullable public CJArray removeArray(String key) { Canonical canonical = getCanonical(key); if (canonical == null || canonical.getValueType() != ValueType.ARRAY) { return null; } remove(key); return (CJArray) canonical.getValue(); } /** * Remove a Boolean from this. * * @param key the key to remove, if it is a Boolean * * @return the Boolean removed */ @Nullable public Boolean removeBoolean(String key) { Canonical canonical = getCanonical(key); if (canonical == null || !Canonical.IS_BOOLEAN.contains(canonical.getValueType())) { return null; } remove(key); return (Boolean) canonical.getValue(); } /** * Remove a null from this. * * @param key the key to remove, if it is null */ public void removeNull(String key) { Canonical canonical = getCanonical(key); if (canonical != null && canonical.getValueType() == ValueType.NULL) { remove(key); } } /** * Remove a number from this. * * @param key the key to remove, if it is a number * * @return the number removed */ @Nullable public Number removeNumber(String key) { Canonical canonical = getCanonical(key); if (canonical == null || canonical.getValueType() != ValueType.NUMBER) { return null; } remove(key); return (Number) canonical.getValue(); } /** * Remove a JSON object from this. * * @param key the key to remove, if it is an object * * @return the object removed */ @Nullable public CJObject removeObject(String key) { Canonical canonical = getCanonical(key); if (canonical == null || canonical.getValueType() != ValueType.OBJECT) { return null; } remove(key); return (CJObject) canonical.getValue(); } /** * Remove a String from this. * * @param key the key to remove, if it is a String * * @return the String that was removed */ @Nullable public String removeString(String key) { Canonical canonical = getCanonical(key); if (canonical == null || canonical.getValueType() != ValueType.STRING) { return null; } remove(key); return (String) canonical.getValue(); } @Override public boolean replace(String key, JsonValue oldValue, JsonValue newValue) { return myMap.replace(key, Canonical.cast(oldValue), Canonical.cast(newValue)); } @Override public JsonValue replace(String key, JsonValue value) { return myMap.replace(key, Canonical.cast(value)); } @Override public void replaceAll(BiFunction function) { final BiFunction myFunction = (k, v) -> Canonical.cast(function.apply(k, v)); myMap.replaceAll(myFunction); } @Override public int size() { return myMap.size(); } @Override public NavigableMap subMap(String fromKey, boolean fromInclusive, String toKey, boolean toInclusive) { return new CJObject(myMap.subMap(fromKey, fromInclusive, toKey, toInclusive), false); } @Override @Nonnull public SortedMap subMap(String fromKey, String toKey) { return new CJObject(myMap.subMap(fromKey, true, toKey, false), false); } @Override public NavigableMap tailMap(String fromKey, boolean inclusive) { return new CJObject(myMap.tailMap(fromKey, inclusive), false); } @Override @Nonnull public SortedMap tailMap(String fromKey) { return new CJObject(myMap.tailMap(fromKey, true), false); } @Override public String toCanonicalString() { StringBuilder buf = new StringBuilder(); Generator generator = CanonicalJsonProvider.CANONICAL_GENERATOR_FACTORY.createGenerator(buf); generator.writeStartObject(); for (Map.Entry e : myMap.entrySet()) { generator.write(e.getKey(), e.getValue()); } generator.writeEnd(); generator.close(); return buf.toString(); } @Override public String toPrettyString() { StringBuilder buf = new StringBuilder(); Generator generator = CanonicalJsonProvider.PRETTY_GENERATOR_FACTORY.createGenerator(buf); generator.writeStartObject(); for (Map.Entry e : myMap.entrySet()) { generator.write(e.getKey(), e.getValue()); } generator.writeEnd(); generator.close(); return buf.toString(); } @Override public String toString() { return CanonicalJsonProvider.isToStringPretty ? toPrettyString() : toCanonicalString(); } @Override @Nonnull public Collection values() { return new MyValues(myMap.values()); } @Override public void writeTo(Appendable writer) throws IOException { writer.append('{'); boolean isNotFirst = false; for (Map.Entry e : entrySet()) { if (isNotFirst) { writer.append(','); } else { isNotFirst = true; } CJString.format(writer, e.getKey()); writer.append(':'); ((Canonical) e.getValue()).writeTo(writer); } writer.append('}'); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy