org.apache.camel.util.json.JsonObject Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of camel-util-json Show documentation
Show all versions of camel-util-json Show documentation
A json simple parser that preserves the ordering in Map as read from JSon source
/*
* 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.camel.util.json;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* JsonObject is a common non-thread safe data format for string to data mappings. The contents of a JsonObject are only
* validated as JSON values on serialization.
*
* @see Jsoner
* @since 2.0.0
*/
public class JsonObject extends LinkedHashMap implements Jsonable {
/**
* The serialization version this class is compatible with. This value doesn't need to be incremented if and only if
* the only changes to occur were updating comments, updating javadocs, adding new fields to the class, changing the
* fields from static to non-static, or changing the fields from transient to non transient. All other changes
* require this number be incremented.
*/
private static final long serialVersionUID = 1L;
/** Instantiates an empty JsonObject. */
public JsonObject() {
}
/**
* Instantiate a new JsonObject by accepting a map's entries, which could lead to de/serialization issues of the
* resulting JsonObject since the entry values aren't validated as JSON values.
*
* @param map represents the mappings to produce the JsonObject with.
*/
public JsonObject(final Map map) {
super(map);
}
/**
* A convenience method that assumes there is a BigDecimal, Number, or String at the given key. If a Number is there
* its Number#toString() is used to construct a new BigDecimal(String). If a String is there it is used to construct
* a new BigDecimal(String).
*
* @param key representing where the value ought to be stored at.
* @return the value stored at the key.
* @throws ClassCastException if the value didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see BigDecimal
* @see Number#toString()
*/
public BigDecimal getBigDecimal(final String key) {
Object returnable = this.get(key);
if (returnable instanceof BigDecimal) {
/* Success there was a BigDecimal or it defaulted. */
} else if (returnable instanceof Number) {
/* A number can be used to construct a BigDecimal */
returnable = new BigDecimal(returnable.toString());
} else if (returnable instanceof String) {
/* A number can be used to construct a BigDecimal */
returnable = new BigDecimal((String) returnable);
}
return (BigDecimal) returnable;
}
/**
* A convenience method that assumes there is a BigDecimal, Number, or String at the given key. If a Number is there
* its Number#toString() is used to construct a new BigDecimal(String). If a String is there it is used to construct
* a new BigDecimal(String).
*
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key or the default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return types.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see BigDecimal
* @see Number#toString()
*/
public BigDecimal getBigDecimalOrDefault(final String key, final BigDecimal defaultValue) {
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
return defaultValue;
}
if (returnable instanceof BigDecimal) {
/* Success there was a BigDecimal or it defaulted. */
} else if (returnable instanceof Number) {
/* A number can be used to construct a BigDecimal */
returnable = new BigDecimal(returnable.toString());
} else if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal */
returnable = new BigDecimal((String) returnable);
}
return (BigDecimal) returnable;
}
/**
* A convenience method that assumes there is a Boolean or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @return the value stored at the key.
* @throws ClassCastException if the value didn't match the assumed return type.
*/
public Boolean getBoolean(final String key) {
Object returnable = this.get(key);
if (returnable instanceof String) {
returnable = Boolean.valueOf((String) returnable);
}
return (Boolean) returnable;
}
/**
* A convenience method that assumes there is a Boolean or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key or the default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return type.
*/
public Boolean getBooleanOrDefault(final String key, final boolean defaultValue) {
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
return defaultValue;
}
if (returnable instanceof String) {
returnable = Boolean.valueOf((String) returnable);
}
return (Boolean) returnable;
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @return the value stored at the key (which may involve rounding or truncation).
* @throws ClassCastException if the value didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#byteValue()
*/
public Byte getByte(final String key) {
Object returnable = this.get(key);
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).byteValue();
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key (which may involve rounding or truncation) or the
* default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#byteValue()
*/
public Byte getByteOrDefault(final String key, final byte defaultValue) {
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
return defaultValue;
}
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).byteValue();
}
/**
* A convenience method that assumes there is a Collection at the given key.
*
* @param the kind of collection to expect at the key. Note unless manually added, collection
* values will be a JsonArray.
* @param key representing where the value ought to be stored at.
* @return the value stored at the key.
* @throws ClassCastException if the value didn't match the assumed return type.
*/
@SuppressWarnings("unchecked")
public > T getCollection(final String key) {
/*
* The unchecked warning is suppressed because there is no way of
* guaranteeing at compile time the cast will work.
*/
return (T) this.get(key);
}
/**
* A convenience method that assumes there is a Collection at the given key.
*
* @param the kind of collection to expect at the key. Note unless manually added, collection
* values will be a JsonArray.
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key or the default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return type.
*/
@SuppressWarnings("unchecked")
public > T getCollectionOrDefault(final String key, final T defaultValue) {
/*
* The unchecked warning is suppressed because there is no way of
* guaranteeing at compile time the cast will work.
*/
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
return defaultValue;
}
return (T) returnable;
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @return the value stored at the key (which may involve rounding or truncation).
* @throws ClassCastException if the value didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#doubleValue()
*/
public Double getDouble(final String key) {
Object returnable = this.get(key);
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).doubleValue();
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key (which may involve rounding or truncation) or the
* default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#doubleValue()
*/
public Double getDoubleOrDefault(final String key, final double defaultValue) {
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
return defaultValue;
}
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).doubleValue();
}
/**
* A convenience method that assumes there is a String value at the given key representing a fully qualified name in
* dot notation of an enum.
*
* @param key representing where the value ought to be stored at.
* @param the Enum type the value at the key is expected to belong to.
* @return the enum based on the string found at the key, or null if the value paired with
* the provided key is null.
* @throws ClassNotFoundException if the value was a String but the declaring enum type couldn't be determined
* with it.
* @throws ClassCastException if the element at the index was not a String or if the fully qualified enum name
* is of the wrong type.
* @throws IllegalArgumentException if an enum type was determined but it doesn't define an enum with the determined
* name.
* @see Enum#valueOf(Class, String)
*/
@SuppressWarnings("unchecked")
public > T getEnum(final String key) throws ClassNotFoundException {
/*
* Supressing the unchecked warning because the returnType is
* dynamically identified and could lead to a ClassCastException when
* returnType is cast to Class, which is expected by the method's
* contract.
*/
T returnable;
final String value;
final String[] splitValues;
final int numberOfSplitValues;
final StringBuilder returnTypeName;
final StringBuilder enumName;
final Class returnType;
/* Make sure the value at the key is a String. */
value = this.getStringOrDefault(key, "");
if (value == null) {
return null;
}
/* Get the package, class, and enum names. */
splitValues = value.split("\\.");
numberOfSplitValues = splitValues.length;
returnTypeName = new StringBuilder();
enumName = new StringBuilder();
for (int i = 0; i < numberOfSplitValues; i++) {
if (i == (numberOfSplitValues - 1)) {
/*
* If it is the last split value then it should be the name of
* the Enum since dots are not allowed in enum names.
*/
enumName.append(splitValues[i]);
} else if (i == (numberOfSplitValues - 2)) {
/*
* If it is the penultimate split value then it should be the
* end of the package/enum type and not need a dot appended to
* it.
*/
returnTypeName.append(splitValues[i]);
} else {
/*
* Must be part of the package/enum type and will need a dot
* appended to it since they got removed in the split.
*/
returnTypeName.append(splitValues[i]);
returnTypeName.append(".");
}
}
/* Use the package/class and enum names to get the Enum. */
returnType = (Class) Class.forName(returnTypeName.toString());
returnable = Enum.valueOf(returnType, enumName.toString());
return returnable;
}
/**
* A convenience method that assumes there is a String value at the given key representing a fully qualified name in
* dot notation of an enum.
*
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @param the Enum type the value at the key is expected to belong to.
* @return the enum based on the string found at the key, or the defaultValue provided if
* the key doesn't exist, or null if the value paired with provided key is null.
* @throws ClassNotFoundException if the value was a String but the declaring enum type couldn't be determined
* with it.
* @throws ClassCastException if the element at the index was not a String or if the fully qualified enum name
* is of the wrong type.
* @throws IllegalArgumentException if an enum type was determined but it doesn't define an enum with the determined
* name.
* @see Enum#valueOf(Class, String)
*/
@SuppressWarnings("unchecked")
public > T getEnumOrDefault(final String key, final T defaultValue) throws ClassNotFoundException {
/*
* Supressing the unchecked warning because the returnType is
* dynamically identified and could lead to a ClassCastException when
* returnType is cast to Class, which is expected by the method's
* contract.
*/
T returnable;
final String value;
final String[] splitValues;
final int numberOfSplitValues;
final StringBuilder returnTypeName;
final StringBuilder enumName;
final Class returnType;
/*
* Check to make sure the key wasn't actually there and wasn't
* coincidentally the defaulted String as its value.
*/
if (this.containsKey(key)) {
/* Make sure the value at the key is a String. */
value = this.getStringOrDefault(key, "");
if (value == null) {
return null;
}
/* Get the package, class, and enum names. */
splitValues = value.split("\\.");
numberOfSplitValues = splitValues.length;
returnTypeName = new StringBuilder();
enumName = new StringBuilder();
for (int i = 0; i < numberOfSplitValues; i++) {
if (i == (numberOfSplitValues - 1)) {
/*
* If it is the last split value then it should be the name
* of the Enum since dots are not allowed in enum names.
*/
enumName.append(splitValues[i]);
} else if (i == (numberOfSplitValues - 2)) {
/*
* If it is the penultimate split value then it should be
* the end of the package/enum type and not need a dot
* appended to it.
*/
returnTypeName.append(splitValues[i]);
} else {
/*
* Must be part of the package/enum type and will need a dot
* appended to it since they got removed in the split.
*/
returnTypeName.append(splitValues[i]);
returnTypeName.append(".");
}
}
/* Use the package/class and enum names to get the Enum. */
returnType = (Class) Class.forName(returnTypeName.toString());
returnable = Enum.valueOf(returnType, enumName.toString());
} else {
/*
* It wasn't there and according to the method's contract we return
* the default value.
*/
return defaultValue;
}
return returnable;
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @return the value stored at the key (which may involve rounding or truncation).
* @throws ClassCastException if the value didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#floatValue()
*/
public Float getFloat(final String key) {
Object returnable = this.get(key);
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).floatValue();
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key (which may involve rounding or truncation) or the
* default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#floatValue()
*/
public Float getFloatOrDefault(final String key, final float defaultValue) {
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
return defaultValue;
}
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).floatValue();
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @return the value stored at the key (which may involve rounding or truncation).
* @throws ClassCastException if the value didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#intValue()
*/
public Integer getInteger(final String key) {
Object returnable = this.get(key);
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).intValue();
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key (which may involve rounding or truncation) or the
* default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#intValue()
*/
public Integer getIntegerOrDefault(final String key, final int defaultValue) {
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
return defaultValue;
}
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).intValue();
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @return the value stored at the key (which may involve rounding or truncation).
* @throws ClassCastException if the value didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#longValue()
*/
public Long getLong(final String key) {
Object returnable = this.get(key);
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).longValue();
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key (which may involve rounding or truncation) or the
* default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#longValue()
*/
public Long getLongOrDefault(final String key, final long defaultValue) {
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
return defaultValue;
}
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).longValue();
}
/**
* A convenience method that assumes there is a Map at the given key.
*
* @param the kind of map to expect at the key. Note unless manually added, Map values will be a
* JsonObject.
* @param key representing where the value ought to be stored at.
* @return the value stored at the key.
* @throws ClassCastException if the value didn't match the assumed return type.
*/
@SuppressWarnings("unchecked")
public > T getMap(final String key) {
/*
* The unchecked warning is suppressed because there is no way of
* guaranteeing at compile time the cast will work.
*/
return (T) this.get(key);
}
/**
* A convenience method that assumes there is a Map at the given key.
*
* @param the kind of map to expect at the key. Note unless manually added, Map values will be a
* JsonObject.
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key or the default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return type.
*/
@SuppressWarnings("unchecked")
public > T getMapOrDefault(final String key, final T defaultValue) {
/*
* The unchecked warning is suppressed because there is no way of
* guaranteeing at compile time the cast will work.
*/
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
returnable = defaultValue;
}
return (T) returnable;
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @return the value stored at the key (which may involve rounding or truncation).
* @throws ClassCastException if the value didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#shortValue()
*/
public Short getShort(final String key) {
Object returnable = this.get(key);
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).shortValue();
}
/**
* A convenience method that assumes there is a Number or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key (which may involve rounding or truncation) or the
* default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return type.
* @throws NumberFormatException if a String isn't a valid representation of a BigDecimal or if the Number
* represents the double or float Infinity or NaN.
* @see Number#shortValue()
*/
public Short getShortOrDefault(final String key, final short defaultValue) {
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
return defaultValue;
}
if (returnable == null) {
return null;
}
if (returnable instanceof String) {
/* A String can be used to construct a BigDecimal. */
returnable = new BigDecimal((String) returnable);
}
return ((Number) returnable).shortValue();
}
/**
* A convenience method that assumes there is a Boolean, Number, or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @return the value stored at the key.
* @throws ClassCastException if the value didn't match the assumed return type.
*/
public String getString(final String key) {
Object returnable = this.get(key);
if (returnable instanceof Boolean) {
returnable = returnable.toString();
} else if (returnable instanceof Number) {
returnable = returnable.toString();
}
return (String) returnable;
}
/**
* A convenience method that assumes there is a Boolean, Number, or String value at the given key.
*
* @param key representing where the value ought to be stored at.
* @param defaultValue representing what is returned when the key isn't in the JsonObject.
* @return the value stored at the key or the default provided if the key doesn't exist.
* @throws ClassCastException if there was a value but didn't match the assumed return type.
*/
public String getStringOrDefault(final String key, final String defaultValue) {
Object returnable;
if (this.containsKey(key)) {
returnable = this.get(key);
} else {
return defaultValue;
}
if (returnable instanceof Boolean) {
returnable = returnable.toString();
} else if (returnable instanceof Number) {
returnable = returnable.toString();
}
return (String) returnable;
}
/*
* (non-Javadoc)
* @see org.apache.camel.util.json.Jsonable#asJsonString()
*/
@Override
public String toJson() {
final StringWriter writable = new StringWriter();
try {
this.toJson(writable);
} catch (final IOException caught) {
/* See java.io.StringWriter. */
}
return writable.toString();
}
/*
* (non-Javadoc)
* @see org.apache.camel.util.json.Jsonable#toJsonString(java.io.Writer)
*/
@Override
public void toJson(final Writer writable) throws IOException {
/* Writes the map in JSON object format. */
boolean isFirstEntry = true;
final Iterator> entries = this.entrySet().iterator();
writable.write('{');
while (entries.hasNext()) {
if (isFirstEntry) {
isFirstEntry = false;
} else {
writable.write(',');
}
final Map.Entry entry = entries.next();
Jsoner.serialize(entry.getKey(), writable);
writable.write(':');
Jsoner.serialize(entry.getValue(), writable);
}
writable.write('}');
}
}