Files
hutool/hutool-json/src/main/java/cn/hutool/json/JSONObject.java
2022-02-13 22:51:58 +08:00

778 lines
23 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cn.hutool.json;
import cn.hutool.core.bean.BeanPath;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.BeanCopier;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.lang.mutable.MutablePair;
import cn.hutool.core.map.CaseInsensitiveLinkedMap;
import cn.hutool.core.map.CaseInsensitiveMap;
import cn.hutool.core.map.CaseInsensitiveTreeMap;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.serialize.GlobalSerializeMapping;
import cn.hutool.json.serialize.JSONObjectSerializer;
import cn.hutool.json.serialize.JSONSerializer;
import cn.hutool.json.serialize.JSONWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeMap;
/**
* JSON对象<br>
* 例:<br>
*
* <pre>
* json = new JSONObject().put(&quot;JSON&quot;, &quot;Hello, World!&quot;).toString();
* </pre>
*
* @author looly
*/
public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object> {
private static final long serialVersionUID = -330220388580734346L;
/**
* 默认初始大小
*/
public static final int DEFAULT_CAPACITY = MapUtil.DEFAULT_INITIAL_CAPACITY;
/**
* JSON的KV持有Map
*/
private final Map<String, Object> rawHashMap;
/**
* 配置项
*/
private final JSONConfig config;
// -------------------------------------------------------------------------------------------------------------------- Constructor start
/**
* 构造,初始容量为 {@link #DEFAULT_CAPACITY}KEY无序
*/
public JSONObject() {
this(DEFAULT_CAPACITY, false);
}
/**
* 构造,初始容量为 {@link #DEFAULT_CAPACITY}
*
* @param isOrder 是否有序
* @since 3.0.9
*/
public JSONObject(boolean isOrder) {
this(DEFAULT_CAPACITY, isOrder);
}
/**
* 构造
*
* @param capacity 初始大小
* @param isOrder 是否有序
* @since 3.0.9
*/
public JSONObject(int capacity, boolean isOrder) {
this(capacity, false, isOrder);
}
/**
* 构造
*
* @param capacity 初始大小
* @param isIgnoreCase 是否忽略KEY大小写
* @param isOrder 是否有序
* @since 3.3.1
*/
public JSONObject(int capacity, boolean isIgnoreCase, boolean isOrder) {
this(capacity, JSONConfig.create().setIgnoreCase(isIgnoreCase).setOrder(isOrder));
}
/**
* 构造
*
* @param config JSON配置项
* @since 4.6.5
*/
public JSONObject(JSONConfig config) {
this(DEFAULT_CAPACITY, config);
}
/**
* 构造
*
* @param capacity 初始大小
* @param config JSON配置项{@code null}则使用默认配置
* @since 4.1.19
*/
public JSONObject(int capacity, JSONConfig config) {
if (null == config) {
config = JSONConfig.create();
}
if (config.isIgnoreCase()) {
final Comparator<String> keyComparator = config.getKeyComparator();
if(null != keyComparator){
// 比较器存在情况下isOrder无效
this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator);
}else{
this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity);
}
} else {
final Comparator<String> keyComparator = config.getKeyComparator();
if(null != keyComparator){
// 比较器存在情况下isOrder无效
this.rawHashMap = new TreeMap<>(keyComparator);
}else{
this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder());
}
}
this.config = config;
}
/**
* 构建JSONObjectJavaBean默认忽略null值其它对象不忽略规则如下
* <ol>
* <li>value为Map将键值对加入JSON对象</li>
* <li>value为JSON字符串CharSequence使用JSONTokener解析</li>
* <li>value为JSONTokener直接解析</li>
* <li>value为普通JavaBean如果为普通的JavaBean调用其getters方法getXXX或者isXXX获得值加入到JSON对象。
* 例如如果JavaBean对象中有个方法getName(),值为"张三"获得的键值对为name: "张三"</li>
* </ol>
*
* @param source JavaBean或者Map对象或者String
*/
public JSONObject(Object source) {
this(source, InternalJSONUtil.defaultIgnoreNullValue(source));
}
/**
* 构建JSONObject规则如下
* <ol>
* <li>value为Map将键值对加入JSON对象</li>
* <li>value为JSON字符串CharSequence使用JSONTokener解析</li>
* <li>value为JSONTokener直接解析</li>
* <li>value为普通JavaBean如果为普通的JavaBean调用其getters方法getXXX或者isXXX获得值加入到JSON对象。例如如果JavaBean对象中有个方法getName(),值为"张三"获得的键值对为name: "张三"</li>
* </ol>
*
* @param source JavaBean或者Map对象或者String
* @param ignoreNullValue 是否忽略空值
* @since 3.0.9
*/
public JSONObject(Object source, boolean ignoreNullValue) {
this(source, ignoreNullValue, InternalJSONUtil.isOrder(source));
}
/**
* 构建JSONObject规则如下
* <ol>
* <li>value为Map将键值对加入JSON对象</li>
* <li>value为JSON字符串CharSequence使用JSONTokener解析</li>
* <li>value为JSONTokener直接解析</li>
* <li>value为普通JavaBean如果为普通的JavaBean调用其getters方法getXXX或者isXXX获得值加入到JSON对象。例如如果JavaBean对象中有个方法getName(),值为"张三"获得的键值对为name: "张三"</li>
* </ol>
*
* @param source JavaBean或者Map对象或者String
* @param ignoreNullValue 是否忽略空值如果source为JSON字符串不忽略空值
* @param isOrder 是否有序
* @since 4.2.2
*/
public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) {
this(source, JSONConfig.create().setOrder(isOrder)//
.setIgnoreCase((source instanceof CaseInsensitiveMap))//
.setIgnoreNullValue(ignoreNullValue)
);
}
/**
* 构建JSONObject规则如下
* <ol>
* <li>value为Map将键值对加入JSON对象</li>
* <li>value为JSON字符串CharSequence使用JSONTokener解析</li>
* <li>value为JSONTokener直接解析</li>
* <li>value为普通JavaBean如果为普通的JavaBean调用其getters方法getXXX或者isXXX获得值加入到JSON对象。例如如果JavaBean对象中有个方法getName(),值为"张三"获得的键值对为name: "张三"</li>
* </ol>
* <p>
* 如果给定值为Map将键值对加入JSON对象;<br>
* 如果为普通的JavaBean调用其getters方法getXXX或者isXXX获得值加入到JSON对象<br>
* 例如如果JavaBean对象中有个方法getName(),值为"张三"获得的键值对为name: "张三"
*
* @param source JavaBean或者Map对象或者String
* @param config JSON配置文件{@code null}则使用默认配置
* @since 4.2.2
*/
public JSONObject(Object source, JSONConfig config) {
this(DEFAULT_CAPACITY, config);
init(source);
}
/**
* 构建指定name列表对应的键值对为新的JSONObject情况如下
*
* <pre>
* 1. 若obj为Map则获取name列表对应键值对
* 2. 若obj为普通Bean使用反射方式获取字段名和字段值
* </pre>
* <p>
* KEY或VALUE任意一个为null则不加入字段不存在也不加入<br>
* 若names列表为空则字段全部加入
*
* @param obj 包含需要字段的Bean对象或者Map对象
* @param names 需要构建JSONObject的字段名列表
*/
public JSONObject(Object obj, String... names) {
this();
if (ArrayUtil.isEmpty(names)) {
init(obj);
return;
}
if (obj instanceof Map) {
Object value;
for (String name : names) {
value = ((Map<?, ?>) obj).get(name);
this.putOnce(name, value);
}
} else {
for (String name : names) {
try {
this.putOpt(name, ReflectUtil.getFieldValue(obj, name));
} catch (Exception ignore) {
// ignore
}
}
}
}
/**
* 从JSON字符串解析为JSON对象对于排序单独配置参数
*
* @param source 以大括号 {} 包围的字符串其中KEY和VALUE使用 : 分隔,每个键值对使用逗号分隔
* @param isOrder 是否有序
* @throws JSONException JSON字符串语法错误
* @since 4.2.2
*/
public JSONObject(CharSequence source, boolean isOrder) throws JSONException {
this(source, JSONConfig.create().setOrder(isOrder));
}
// -------------------------------------------------------------------------------------------------------------------- Constructor end
@Override
public JSONConfig getConfig() {
return this.config;
}
/**
* 设置转为字符串时的日期格式默认为时间戳null值<br>
* 此方法设置的日期格式仅对转换为JSON字符串有效对解析JSON为bean无效。
*
* @param format 格式null表示使用时间戳
* @return this
* @since 4.1.19
*/
public JSONObject setDateFormat(String format) {
this.config.setDateFormat(format);
return this;
}
/**
* 将指定KEY列表的值组成新的JSONArray
*
* @param names KEY列表
* @return A JSONArray of values.
* @throws JSONException If any of the values are non-finite numbers.
*/
public JSONArray toJSONArray(Collection<String> names) throws JSONException {
if (CollectionUtil.isEmpty(names)) {
return null;
}
final JSONArray ja = new JSONArray(this.config);
Object value;
for (String name : names) {
value = this.get(name);
if (null != value) {
ja.set(value);
}
}
return ja;
}
@Override
public int size() {
return rawHashMap.size();
}
@Override
public boolean isEmpty() {
return rawHashMap.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return rawHashMap.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return rawHashMap.containsValue(value);
}
@Override
public Object get(Object key) {
return rawHashMap.get(key);
}
@Override
public Object getObj(String key, Object defaultValue) {
Object obj = this.rawHashMap.get(key);
return null == obj ? defaultValue : obj;
}
@Override
public Object getByPath(String expression) {
return BeanPath.create(expression).get(this);
}
@Override
public <T> T getByPath(String expression, Class<T> resultType) {
return JSONConverter.jsonConvert(resultType, getByPath(expression), true);
}
@Override
public void putByPath(String expression, Object value) {
BeanPath.create(expression).set(this, value);
}
/**
* PUT 键值对到JSONObject中在忽略null模式下如果值为{@code null},将此键移除
*
* @param key 键
* @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL.
* @return this.
* @throws JSONException 值是无穷数字抛出此异常
* @deprecated 此方法存在歧义原Map接口返回的是之前的值重写后返回this了未来版本此方法会修改请使用{@link #set(String, Object)}
*/
@Override
@Deprecated
public JSONObject put(String key, Object value) throws JSONException {
return set(key, value);
}
/**
* 设置键值对到JSONObject中在忽略null模式下如果值为{@code null},将此键移除
*
* @param key 键
* @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL.
* @return this.
* @throws JSONException 值是无穷数字抛出此异常
*/
public JSONObject set(String key, Object value) throws JSONException {
if (null == key) {
return this;
}
final boolean ignoreNullValue = this.config.isIgnoreNullValue();
if (ObjectUtil.isNull(value) && ignoreNullValue) {
// 忽略值模式下如果值为空清除key
this.remove(key);
} else {
InternalJSONUtil.testValidity(value);
this.rawHashMap.put(key, JSONUtil.wrap(value, this.config));
}
return this;
}
/**
* 一次性Put 键值对如果key已经存在抛出异常如果键值中有null值忽略
*
* @param key 键
* @param value 值对象,可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL.
* @return this.
* @throws JSONException 值是无穷数字、键重复抛出异常
*/
public JSONObject putOnce(String key, Object value) throws JSONException {
if (key != null) {
if (rawHashMap.containsKey(key)) {
throw new JSONException("Duplicate key \"{}\"", key);
}
this.set(key, value);
}
return this;
}
/**
* 在键和值都为非空的情况下put到JSONObject中
*
* @param key 键
* @param value 值对象,可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL.
* @return this.
* @throws JSONException 值是无穷数字
*/
public JSONObject putOpt(String key, Object value) throws JSONException {
if (key != null && value != null) {
this.set(key, value);
}
return this;
}
@Override
public void putAll(Map<? extends String, ?> m) {
for (Entry<? extends String, ?> entry : m.entrySet()) {
this.set(entry.getKey(), entry.getValue());
}
}
/**
* 积累值。类似于set当key对应value已经存在时与value组成新的JSONArray. <br>
* 如果只有一个值此值就是value如果多个值则是添加到新的JSONArray中
*
* @param key 键
* @param value 被积累的值
* @return this.
* @throws JSONException 如果给定键为{@code null}或者键对应的值存在且为非JSONArray
*/
public JSONObject accumulate(String key, Object value) throws JSONException {
InternalJSONUtil.testValidity(value);
Object object = this.getObj(key);
if (object == null) {
this.set(key, value);
} else if (object instanceof JSONArray) {
((JSONArray) object).set(value);
} else {
this.set(key, JSONUtil.createArray(this.config).set(object).set(value));
}
return this;
}
/**
* 追加值如果key无对应值就添加一个JSONArray其元素只有value如果值已经是一个JSONArray则添加到值JSONArray中。
*
* @param key 键
* @param value 值
* @return this.
* @throws JSONException 如果给定键为{@code null}或者键对应的值存在且为非JSONArray
*/
public JSONObject append(String key, Object value) throws JSONException {
InternalJSONUtil.testValidity(value);
Object object = this.getObj(key);
if (object == null) {
this.set(key, new JSONArray(this.config).set(value));
} else if (object instanceof JSONArray) {
this.set(key, ((JSONArray) object).set(value));
} else {
throw new JSONException("JSONObject [" + key + "] is not a JSONArray.");
}
return this;
}
/**
* 对值加一如果值不存在赋值1如果为数字类型做加一操作
*
* @param key A key string.
* @return this.
* @throws JSONException 如果存在值非Integer, Long, Double, 或 Float.
*/
public JSONObject increment(String key) throws JSONException {
Object value = this.getObj(key);
if (value == null) {
this.set(key, 1);
} else if (value instanceof BigInteger) {
this.set(key, ((BigInteger) value).add(BigInteger.ONE));
} else if (value instanceof BigDecimal) {
this.set(key, ((BigDecimal) value).add(BigDecimal.ONE));
} else if (value instanceof Integer) {
this.set(key, (Integer) value + 1);
} else if (value instanceof Long) {
this.set(key, (Long) value + 1);
} else if (value instanceof Double) {
this.set(key, (Double) value + 1);
} else if (value instanceof Float) {
this.set(key, (Float) value + 1);
} else {
throw new JSONException("Unable to increment [" + JSONUtil.quote(key) + "].");
}
return this;
}
@Override
public Object remove(Object key) {
return rawHashMap.remove(key);
}
@Override
public void clear() {
rawHashMap.clear();
}
@Override
public Set<String> keySet() {
return this.rawHashMap.keySet();
}
@Override
public Collection<Object> values() {
return rawHashMap.values();
}
@Override
public Set<Entry<String, Object>> entrySet() {
return rawHashMap.entrySet();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((rawHashMap == null) ? 0 : rawHashMap.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final JSONObject other = (JSONObject) obj;
if (rawHashMap == null) {
return other.rawHashMap == null;
} else {
return rawHashMap.equals(other.rawHashMap);
}
}
/**
* 返回JSON字符串<br>
* 如果解析错误,返回{@code null}
*
* @return JSON字符串
*/
@Override
public String toString() {
return this.toJSONString(0);
}
/**
* 返回JSON字符串<br>
* 支持过滤器,即选择哪些字段或值不写出
*
* @param indentFactor 每层缩进空格数
* @param filter 过滤器,同时可以修改编辑键和值
* @return JSON字符串
* @since 5.7.15
*/
public String toJSONString(int indentFactor, Filter<MutablePair<String, Object>> filter) {
final StringWriter sw = new StringWriter();
synchronized (sw.getBuffer()) {
return this.write(sw, indentFactor, 0, filter).toString();
}
}
@Override
public Writer write(Writer writer, int indentFactor, int indent) throws JSONException {
return write(writer, indentFactor, indent, null);
}
/**
* 将JSON内容写入Writer<br>
* 支持过滤器,即选择哪些字段或值不写出
*
* @param writer writer
* @param indentFactor 缩进因子,定义每一级别增加的缩进量
* @param indent 本级别缩进量
* @param filter 过滤器,同时可以修改编辑键和值
* @return Writer
* @throws JSONException JSON相关异常
* @since 5.7.15
*/
public Writer write(Writer writer, int indentFactor, int indent, Filter<MutablePair<String, Object>> filter) throws JSONException {
final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config)
.beginObj();
this.forEach((key, value) -> {
if (null != filter){
final MutablePair<String, Object> pair = new MutablePair<>(key, value);
if (filter.accept(pair)) {
// 使用修改后的键值对
jsonWriter.writeField(pair.getKey(), pair.getValue());
}
} else {
jsonWriter.writeField(key, value);
}
});
jsonWriter.end();
// 此处不关闭Writer考虑writer后续还需要填内容
return writer;
}
// ------------------------------------------------------------------------------------------------- Private method start
/**
* Bean对象转Map
*
* @param bean Bean对象
*/
private void populateMap(Object bean) {
BeanCopier.create(bean, this,
CopyOptions.create()
.setIgnoreCase(config.isIgnoreCase())
.setIgnoreError(true)
.setIgnoreNullValue(config.isIgnoreNullValue())
).copy();
}
/**
* 初始化
* <ol>
* <li>value为Map将键值对加入JSON对象</li>
* <li>value为JSON字符串CharSequence使用JSONTokener解析</li>
* <li>value为JSONTokener直接解析</li>
* <li>value为普通JavaBean如果为普通的JavaBean调用其getters方法getXXX或者isXXX获得值加入到JSON对象。例如如果JavaBean对象中有个方法getName(),值为"张三"获得的键值对为name: "张三"</li>
* </ol>
*
* @param source JavaBean或者Map对象或者String
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private void init(Object source) {
if (null == source) {
return;
}
// 自定义序列化
final JSONSerializer serializer = GlobalSerializeMapping.getSerializer(source.getClass());
if (serializer instanceof JSONObjectSerializer) {
serializer.serialize(this, source);
return;
}
if (ArrayUtil.isArray(source) || source instanceof JSONArray) {
// 不支持集合类型转换为JSONObject
throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass());
}
if (source instanceof Map) {
// Map
for (final Entry<?, ?> e : ((Map<?, ?>) source).entrySet()) {
this.set(Convert.toStr(e.getKey()), e.getValue());
}
} else if (source instanceof Map.Entry) {
final Map.Entry entry = (Map.Entry) source;
this.set(Convert.toStr(entry.getKey()), entry.getValue());
} else if (source instanceof CharSequence) {
// 可能为JSON字符串
init((CharSequence) source);
} else if (source instanceof JSONTokener) {
// JSONTokener
init((JSONTokener) source);
} else if (source instanceof ResourceBundle) {
// JSONTokener
init((ResourceBundle) source);
} else if (BeanUtil.isReadableBean(source.getClass())) {
// 普通Bean
this.populateMap(source);
} else {
// 不支持对象类型转换为JSONObject
throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass());
}
}
/**
* 初始化
*
* @param bundle ResourceBundle
* @since 5.3.1
*/
private void init(ResourceBundle bundle) {
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
if (key != null) {
InternalJSONUtil.propertyPut(this, key, bundle.getString(key));
}
}
}
/**
* 初始化可以判断字符串为JSON或者XML
*
* @param source JSON字符串
*/
private void init(CharSequence source) {
final String jsonStr = StrUtil.trim(source);
if (StrUtil.startWith(jsonStr, '<')) {
// 可能为XML
XML.toJSONObject(this, jsonStr, false);
}
init(new JSONTokener(StrUtil.trim(source), this.config));
}
/**
* 初始化
*
* @param x JSONTokener
*/
private void init(JSONTokener x) {
char c;
String key;
if (x.nextClean() != '{') {
throw x.syntaxError("A JSONObject text must begin with '{'");
}
while (true) {
c = x.nextClean();
switch (c) {
case 0:
throw x.syntaxError("A JSONObject text must end with '}'");
case '}':
return;
default:
x.back();
key = x.nextValue().toString();
}
// The key is followed by ':'.
c = x.nextClean();
if (c != ':') {
throw x.syntaxError("Expected a ':' after a key");
}
this.putOnce(key, x.nextValue());
// Pairs are separated by ','.
switch (x.nextClean()) {
case ';':
case ',':
if (x.nextClean() == '}') {
return;
}
x.back();
break;
case '}':
return;
default:
throw x.syntaxError("Expected a ',' or '}'");
}
}
}
// ------------------------------------------------------------------------------------------------- Private method end
}