diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/InternalJSONUtil.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/InternalJSONUtil.java new file mode 100755 index 0000000..bcd2152 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/InternalJSONUtil.java @@ -0,0 +1,214 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.bean.copier.CopyOptions; +import aiyh.utils.tool.cn.hutool.core.convert.Convert; +import aiyh.utils.tool.cn.hutool.core.lang.Filter; +import aiyh.utils.tool.cn.hutool.core.lang.mutable.MutablePair; +import aiyh.utils.tool.cn.hutool.core.map.CaseInsensitiveLinkedMap; +import aiyh.utils.tool.cn.hutool.core.map.CaseInsensitiveTreeMap; +import aiyh.utils.tool.cn.hutool.core.util.*; + +import java.math.BigDecimal; +import java.util.*; + +/** + * 内部JSON工具类,仅用于JSON内部使用 + * + * @author Looly + */ +public final class InternalJSONUtil { + + private InternalJSONUtil() { + } + + /** + * 如果对象是Number 且是 NaN or infinite,将抛出异常 + * + * @param obj 被检查的对象 + * @return 检测后的值 + * @throws JSONException If o is a non-finite number. + */ + static Object testValidity(Object obj) throws JSONException { + if (!ObjectUtil.isValidIfNumber(obj)) { + throw new JSONException("JSON does not allow non-finite numbers."); + } + return obj; + } + + /** + * 值转为String,用于JSON中。规则为: + * + * + * @param value 需要转为字符串的对象 + * @return 字符串 + * @throws JSONException If the value is or contains an invalid number. + */ + static String valueToString(Object value) throws JSONException { + if (value == null || value instanceof JSONNull) { + return JSONNull.NULL.toString(); + } + if (value instanceof JSONString) { + try { + return ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + } else if (value instanceof Number) { + return NumberUtil.toStr((Number) value); + } else if (value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray) { + return value.toString(); + } else if (value instanceof Map) { + Map map = (Map) value; + return new JSONObject(map).toString(); + } else if (value instanceof Collection) { + Collection coll = (Collection) value; + return new JSONArray(coll).toString(); + } else if (ArrayUtil.isArray(value)) { + return new JSONArray(value).toString(); + } else { + return JSONUtil.quote(value.toString()); + } + } + + /** + * 尝试转换字符串为number, boolean, or null,无法转换返回String + * + * @param string A String. + * @return A simple JSON value. + */ + public static Object stringToValue(String string) { + // null处理 + if (StrUtil.isEmpty(string) || StrUtil.NULL.equalsIgnoreCase(string)) { + return JSONNull.NULL; + } + + // boolean处理 + if ("true".equalsIgnoreCase(string)) { + return Boolean.TRUE; + } + if ("false".equalsIgnoreCase(string)) { + return Boolean.FALSE; + } + + // Number处理 + char b = string.charAt(0); + if ((b >= '0' && b <= '9') || b == '-') { + try { + if (StrUtil.containsAnyIgnoreCase(string, ".", "e")) { + // pr#192@Gitee,Double会出现小数精度丢失问题,此处使用BigDecimal + return new BigDecimal(string); + } else { + final long myLong = Long.parseLong(string); + if (string.equals(Long.toString(myLong))) { + if (myLong == (int) myLong) { + return (int) myLong; + } else { + return myLong; + } + } + } + } catch (Exception ignore) { + } + } + + // 其它情况返回原String值下 + return string; + } + + /** + * 将Property的键转化为JSON形式
+ * 用于识别类似于:com.luxiaolei.package.hutool这类用点隔开的键
+ * 注意:是否允许重复键,取决于JSONObject配置 + * + * @param jsonObject JSONObject + * @param key 键 + * @param value 值 + * @return JSONObject + */ + static JSONObject propertyPut(JSONObject jsonObject, Object key, Object value, Filter> filter) { + final String[] path = StrUtil.splitToArray(Convert.toStr(key), CharUtil.DOT); + final int last = path.length - 1; + JSONObject target = jsonObject; + for (int i = 0; i < last; i += 1) { + final String segment = path[i]; + JSONObject nextTarget = target.getJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(target.getConfig()); + target.set(segment, nextTarget, filter, target.getConfig().isCheckDuplicate()); + } + target = nextTarget; + } + target.set(path[last], value, filter, target.getConfig().isCheckDuplicate()); + return jsonObject; + } + + /** + * 默认情况下是否忽略null值的策略选择,以下对象不忽略null值,其它对象忽略: + * + *
+	 *     1. CharSequence
+	 *     2. JSONTokener
+	 *     3. Map
+	 * 
+ * + * @param obj 需要检查的对象 + * @return 是否忽略null值 + * @since 4.3.1 + */ + static boolean defaultIgnoreNullValue(Object obj) { + return (!(obj instanceof CharSequence))// + && (!(obj instanceof JSONTokener))// + && (!(obj instanceof Map)); + } + + /** + * 将{@link JSONConfig}参数转换为Bean拷贝所用的{@link CopyOptions} + * + * @param config {@link JSONConfig} + * @return {@link CopyOptions} + * @since 5.8.0 + */ + static CopyOptions toCopyOptions(JSONConfig config) { + return CopyOptions.create() + .setIgnoreCase(config.isIgnoreCase()) + .setIgnoreError(config.isIgnoreError()) + .setIgnoreNullValue(config.isIgnoreNullValue()) + .setTransientSupport(config.isTransientSupport()); + } + + /** + * 根据配置创建对应的原始Map + * + * @param capacity 初始大小 + * @param config JSON配置项,{@code null}则使用默认配置 + * @return Map + */ + static Map createRawMap(int capacity, JSONConfig config) { + final Map rawHashMap; + if (null == config) { + config = JSONConfig.create(); + } + final Comparator keyComparator = config.getKeyComparator(); + if (config.isIgnoreCase()) { + if (null != keyComparator) { + rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + rawHashMap = new CaseInsensitiveLinkedMap<>(capacity); + } + } else { + if (null != keyComparator) { + rawHashMap = new TreeMap<>(keyComparator); + } else { + rawHashMap = new LinkedHashMap<>(capacity); + } + } + return rawHashMap; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSON.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSON.java new file mode 100755 index 0000000..0c50be2 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSON.java @@ -0,0 +1,195 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.bean.BeanPath; +import aiyh.utils.tool.cn.hutool.core.lang.TypeReference; + +import java.io.Serializable; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Type; + +/** + * JSON接口 + * + * @author Looly + */ +public interface JSON extends Cloneable, Serializable { + + /** + * 获取JSON配置 + * + * @return {@link JSONConfig} + * @since 5.8.6 + */ + JSONConfig getConfig(); + + /** + * 通过表达式获取JSON中嵌套的对象
+ *
    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ * + * @param expression 表达式 + * @return 对象 + * @see BeanPath#get(Object) + * @since 4.0.6 + */ + Object getByPath(String expression); + + /** + * 设置表达式指定位置(或filed对应)的值
+ * 若表达式指向一个JSONArray则设置其坐标对应位置的值,若指向JSONObject则put对应key的值
+ * 注意:如果为JSONArray,设置值下标小于其长度,将替换原有值,否则追加新值
+ *
    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ * + * @param expression 表达式 + * @param value 值 + */ + void putByPath(String expression, Object value); + + /** + * 通过表达式获取JSON中嵌套的对象
+ *
    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ *

+ * 获取表达式对应值后转换为对应类型的值 + * + * @param 返回值类型 + * @param expression 表达式 + * @param resultType 返回值类型 + * @return 对象 + * @see BeanPath#get(Object) + * @since 4.0.6 + */ + T getByPath(String expression, Class resultType); + + /** + * 格式化打印JSON,缩进为4个空格 + * + * @return 格式化后的JSON字符串 + * @throws JSONException 包含非法数抛出此异常 + * @since 3.0.9 + */ + default String toStringPretty() throws JSONException { + return this.toJSONString(4); + } + + /** + * 格式化输出JSON字符串 + * + * @param indentFactor 每层缩进空格数 + * @return JSON字符串 + * @throws JSONException 包含非法数抛出此异常 + */ + default String toJSONString(int indentFactor) throws JSONException { + final StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0).toString(); + } + } + + /** + * 将JSON内容写入Writer,无缩进
+ * Warning: This method assumes that the data structure is acyclical. + * + * @param writer Writer + * @return Writer + * @throws JSONException JSON相关异常 + */ + default Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + /** + * 将JSON内容写入Writer
+ * Warning: This method assumes that the data structure is acyclical. + * + * @param writer writer + * @param indentFactor 缩进因子,定义每一级别增加的缩进量 + * @param indent 本级别缩进量 + * @return Writer + * @throws JSONException JSON相关异常 + */ + Writer write(Writer writer, int indentFactor, int indent) throws JSONException; + + /** + * 转为实体类对象,转换异常将被抛出 + * + * @param Bean类型 + * @param clazz 实体类 + * @return 实体类对象 + */ + default T toBean(Class clazz) { + return toBean((Type) clazz); + } + + /** + * 转为实体类对象,转换异常将被抛出 + * + * @param Bean类型 + * @param reference {@link TypeReference}类型参考子类,可以获取其泛型参数中的Type类型 + * @return 实体类对象 + * @since 4.2.2 + */ + default T toBean(TypeReference reference) { + return toBean(reference.getType()); + } + + /** + * 转为实体类对象 + * + * @param Bean类型 + * @param type {@link Type} + * @return 实体类对象 + * @since 3.0.8 + */ + default T toBean(Type type) { + return toBean(type, getConfig().isIgnoreError()); + } + + /** + * 转为实体类对象 + * + * @param Bean类型 + * @param type {@link Type} + * @param ignoreError 是否忽略转换错误 + * @return 实体类对象 + * @since 4.3.2 + */ + default T toBean(Type type, boolean ignoreError) { + return JSONConverter.jsonConvert(type, this, ignoreError); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONArray.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONArray.java new file mode 100755 index 0000000..ad8ecd0 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONArray.java @@ -0,0 +1,591 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.bean.BeanPath; +import aiyh.utils.tool.cn.hutool.core.collection.CollUtil; +import aiyh.utils.tool.cn.hutool.core.lang.Filter; +import aiyh.utils.tool.cn.hutool.core.lang.mutable.Mutable; +import aiyh.utils.tool.cn.hutool.core.lang.mutable.MutableObj; +import aiyh.utils.tool.cn.hutool.core.lang.mutable.MutablePair; +import aiyh.utils.tool.cn.hutool.core.text.StrJoiner; +import aiyh.utils.tool.cn.hutool.core.util.ObjectUtil; +import aiyh.utils.tool.cn.hutool.json.serialize.JSONWriter; + +import java.io.StringWriter; +import java.io.Writer; +import java.util.*; + +/** + * JSON数组
+ * JSON数组是表示中括号括住的数据表现形式
+ * 对应的JSON字符串格格式例如: + * + *

+ * ["a", "b", "c", 12]
+ * 
+ * + * @author looly + */ +public class JSONArray implements JSON, JSONGetter, List, RandomAccess { + private static final long serialVersionUID = 2664900568717612292L; + + /** + * 默认初始大小 + */ + public static final int DEFAULT_CAPACITY = 10; + + /** + * 持有原始数据的List + */ + private List rawList; + /** + * 配置项 + */ + private final JSONConfig config; + + // region Constructors + + /** + * 构造
+ * 默认使用{@link ArrayList} 实现 + */ + public JSONArray() { + this(DEFAULT_CAPACITY); + } + + /** + * 构造
+ * 默认使用{@link ArrayList} 实现 + * + * @param initialCapacity 初始大小 + * @since 3.2.2 + */ + public JSONArray(int initialCapacity) { + this(initialCapacity, JSONConfig.create()); + } + + /** + * 构造
+ * 默认使用{@link ArrayList} 实现 + * + * @param config JSON配置项 + * @since 4.6.5 + */ + public JSONArray(JSONConfig config) { + this(DEFAULT_CAPACITY, config); + } + + /** + * 构造
+ * 默认使用{@link ArrayList} 实现 + * + * @param initialCapacity 初始大小 + * @param config JSON配置项 + * @since 4.1.19 + */ + public JSONArray(int initialCapacity, JSONConfig config) { + this.rawList = new ArrayList<>(initialCapacity); + this.config = ObjectUtil.defaultIfNull(config, JSONConfig::create); + } + + /** + * 从对象构造,忽略{@code null}的值
+ * 支持以下类型的参数: + * + *
+	 * 1. 数组
+	 * 2. {@link Iterable}对象
+	 * 3. JSON数组字符串
+	 * 
+ * + * @param object 数组或集合或JSON数组字符串 + * @throws JSONException 非数组或集合 + */ + public JSONArray(Object object) throws JSONException { + this(object, true); + } + + /** + * 从对象构造
+ * 支持以下类型的参数: + * + *
+	 * 1. 数组
+	 * 2. {@link Iterable}对象
+	 * 3. JSON数组字符串
+	 * 
+ * + * @param object 数组或集合或JSON数组字符串 + * @param ignoreNullValue 是否忽略空值 + * @throws JSONException 非数组或集合 + */ + public JSONArray(Object object, boolean ignoreNullValue) throws JSONException { + this(object, JSONConfig.create().setIgnoreNullValue(ignoreNullValue)); + } + + /** + * 从对象构造
+ * 支持以下类型的参数: + * + *
+	 * 1. 数组
+	 * 2. {@link Iterable}对象
+	 * 3. JSON数组字符串
+	 * 
+ * + * @param object 数组或集合或JSON数组字符串 + * @param jsonConfig JSON选项 + * @throws JSONException 非数组或集合 + * @since 4.6.5 + */ + public JSONArray(Object object, JSONConfig jsonConfig) throws JSONException { + this(object, jsonConfig, null); + } + + /** + * 从对象构造
+ * 支持以下类型的参数: + * + *
+	 * 1. 数组
+	 * 2. {@link Iterable}对象
+	 * 3. JSON数组字符串
+	 * 
+ * + * @param object 数组或集合或JSON数组字符串 + * @param jsonConfig JSON选项 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + * @throws JSONException 非数组或集合 + * @since 5.8.0 + */ + public JSONArray(Object object, JSONConfig jsonConfig, Filter> filter) throws JSONException { + this(DEFAULT_CAPACITY, jsonConfig); + ObjectMapper.of(object).map(this, filter); + } + // endregion + + @Override + public JSONConfig getConfig() { + return this.config; + } + + /** + * 设置转为字符串时的日期格式,默认为时间戳(null值) + * + * @param format 格式,null表示使用时间戳 + * @return this + * @since 4.1.19 + */ + public JSONArray setDateFormat(String format) { + this.config.setDateFormat(format); + return this; + } + + /** + * JSONArray转为以{@code separator}为分界符的字符串 + * + * @param separator 分界符 + * @return a string. + * @throws JSONException If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + return StrJoiner.of(separator) + .append(this, InternalJSONUtil::valueToString).toString(); + } + + @Override + public Object get(int index) { + return this.rawList.get(index); + } + + @Override + public Object getObj(Integer index, Object defaultValue) { + return (index < 0 || index >= this.size()) ? defaultValue : this.rawList.get(index); + } + + @Override + public Object getByPath(String expression) { + return BeanPath.create(expression).get(this); + } + + @Override + public T getByPath(String expression, Class resultType) { + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); + } + + @Override + public void putByPath(String expression, Object value) { + BeanPath.create(expression).set(this, value); + } + + /** + * Append an object value. This increases the array's length by one.
+ * 加入元素,数组长度+1,等同于 {@link JSONArray#add(Object)} + * + * @param value 值,可以是: Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONNull.NULL。 + * @return this. + * @see #set(Object) + */ + public JSONArray put(Object value) { + return set(value); + } + + /** + * Append an object value. This increases the array's length by one.
+ * 加入元素,数组长度+1,等同于 {@link JSONArray#add(Object)} + * + * @param value 值,可以是: Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONNull.NULL。 + * @return this. + * @since 5.2.5 + */ + public JSONArray set(Object value) { + this.add(value); + return this; + } + + /** + * 加入或者替换JSONArray中指定Index的值,如果index大于JSONArray的长度,将在指定index设置值,之前的位置填充JSONNull.Null + * + * @param index 位置 + * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @return this. + * @throws JSONException index < 0 或者非有限的数字 + * @see #set(int, Object) + */ + public JSONArray put(int index, Object value) throws JSONException { + this.set(index, value); + return this; + } + + /** + * 根据给定名列表,与其位置对应的值组成JSONObject + * + * @param names 名列表,位置与JSONArray中的值位置对应 + * @return A JSONObject,无名或值返回null + * @throws JSONException 如果任何一个名为null + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.size() == 0 || this.size() == 0) { + return null; + } + final JSONObject jo = new JSONObject(this.config); + for (int i = 0; i < names.size(); i += 1) { + jo.set(names.getStr(i), this.getObj(i)); + } + return jo; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((rawList == null) ? 0 : rawList.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 JSONArray other = (JSONArray) obj; + if (rawList == null) { + return other.rawList == null; + } else { + return rawList.equals(other.rawList); + } + } + + @Override + public Iterator iterator() { + return rawList.iterator(); + } + + /** + * 当此JSON列表的每个元素都是一个JSONObject时,可以调用此方法返回一个Iterable,便于使用foreach语法遍历 + * + * @return Iterable + * @since 4.0.12 + */ + public Iterable jsonIter() { + return new JSONObjectIter(iterator()); + } + + @Override + public int size() { + return rawList.size(); + } + + @Override + public boolean isEmpty() { + return rawList.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return rawList.contains(o); + } + + @Override + public Object[] toArray() { + return rawList.toArray(); + } + + @Override + @SuppressWarnings({"unchecked"}) + public T[] toArray(T[] a) { + return (T[]) JSONConverter.toArray(this, a.getClass().getComponentType()); + } + + @Override + public boolean add(Object e) { + return addRaw(JSONUtil.wrap(e, this.config), null); + } + + @Override + public Object remove(int index) { + return index >= 0 && index < this.size() ? this.rawList.remove(index) : null; + } + + @Override + public boolean remove(Object o) { + return rawList.remove(o); + } + + @SuppressWarnings("NullableProblems") + @Override + public boolean containsAll(Collection c) { + return rawList.containsAll(c); + } + + @SuppressWarnings("NullableProblems") + @Override + public boolean addAll(Collection c) { + if (CollUtil.isEmpty(c)) { + return false; + } + for (Object obj : c) { + this.add(obj); + } + return true; + } + + @SuppressWarnings("NullableProblems") + @Override + public boolean addAll(int index, Collection c) { + if (CollUtil.isEmpty(c)) { + return false; + } + final ArrayList list = new ArrayList<>(c.size()); + for (Object object : c) { + list.add(JSONUtil.wrap(object, this.config)); + } + return rawList.addAll(index, list); + } + + @SuppressWarnings("NullableProblems") + @Override + public boolean removeAll(Collection c) { + return this.rawList.removeAll(c); + } + + @SuppressWarnings("NullableProblems") + @Override + public boolean retainAll(Collection c) { + return this.rawList.retainAll(c); + } + + @Override + public void clear() { + this.rawList.clear(); + + } + + /** + * 加入或者替换JSONArray中指定Index的值,如果index大于JSONArray的长度,将在指定index设置值,之前的位置填充JSONNull.Null + * + * @param index 位置 + * @param element 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @return 替换的值,即之前的值 + */ + @Override + public Object set(int index, Object element) { + return set(index, element, null); + } + + /** + * 加入或者替换JSONArray中指定Index的值,如果index大于JSONArray的长度,将在指定index设置值,之前的位置填充JSONNull.Null + * + * @param index 位置 + * @param element 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @param filter 过滤器,可以修改值,key(index)无法修改 + * @return 替换的值,即之前的值 + * @since 5.8.0 + */ + public Object set(int index, Object element, Filter> filter) { + // 添加前置过滤,通过MutablePair实现过滤、修改键值对等 + if (null != filter) { + final MutablePair pair = new MutablePair<>(index, element); + if (filter.accept(pair)) { + // 使用修改后的值 + element = pair.getValue(); + } + } + + if (index >= size()) { + add(index, element); + } + return this.rawList.set(index, JSONUtil.wrap(element, this.config)); + } + + @Override + public void add(int index, Object element) { + if (index < 0) { + throw new JSONException("JSONArray[{}] not found.", index); + } + if (index < this.size()) { + InternalJSONUtil.testValidity(element); + this.rawList.add(index, JSONUtil.wrap(element, this.config)); + } else { + while (index != this.size()) { + this.add(JSONNull.NULL); + } + this.set(element); + } + + } + + @Override + public int indexOf(Object o) { + return this.rawList.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return this.rawList.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return this.rawList.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return this.rawList.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return this.rawList.subList(fromIndex, toIndex); + } + + /** + * 转为Bean数组 + * + * @param arrayClass 数组元素类型 + * @return 实体类对象 + */ + public Object toArray(Class arrayClass) { + return JSONConverter.toArray(this, arrayClass); + } + + /** + * 转为{@link ArrayList} + * + * @param 元素类型 + * @param elementType 元素类型 + * @return {@link ArrayList} + * @since 3.0.8 + */ + public List toList(Class elementType) { + return JSONConverter.toList(this, elementType); + } + + /** + * 转为JSON字符串,无缩进 + * + * @return JSONArray字符串 + */ + @Override + public String toString() { + return this.toJSONString(0); + } + + /** + * 返回JSON字符串
+ * 支持过滤器,即选择哪些字段或值不写出 + * + * @param indentFactor 每层缩进空格数 + * @param filter 过滤器,可以修改值,key(index)无法修改 + * @return JSON字符串 + * @since 5.7.15 + */ + public String toJSONString(int indentFactor, Filter> 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
+ * 支持过滤器,即选择哪些字段或值不写出 + * + * @param writer writer + * @param indentFactor 缩进因子,定义每一级别增加的缩进量 + * @param indent 本级别缩进量 + * @param filter 过滤器,可以修改值,key(index)无法修改 + * @return Writer + * @throws JSONException JSON相关异常 + * @since 5.7.15 + */ + public Writer write(Writer writer, int indentFactor, int indent, Filter> filter) throws JSONException { + final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config).beginArray(); + + CollUtil.forEach(this, (value, index) -> jsonWriter.writeField(new MutablePair<>(index, value), filter)); + jsonWriter.end(); + // 此处不关闭Writer,考虑writer后续还需要填内容 + return writer; + } + + @Override + public Object clone() throws CloneNotSupportedException { + final JSONArray clone = (JSONArray) super.clone(); + clone.rawList = ObjectUtil.clone(this.rawList); + return clone; + } + + /** + * 原始添加,添加的对象不做任何处理 + * + * @param obj 添加的对象 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + * @return 是否加入成功 + * @since 5.8.0 + */ + protected boolean addRaw(Object obj, Filter> filter) { + // 添加前置过滤,通过MutablePair实现过滤、修改键值对等 + if (null != filter) { + final Mutable mutable = new MutableObj<>(obj); + if (filter.accept(mutable)) { + // 使用修改后的值 + obj = mutable.get(); + } else { + // 键值对被过滤 + return false; + } + } + return this.rawList.add(obj); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONBeanParser.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONBeanParser.java new file mode 100755 index 0000000..9678ed8 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONBeanParser.java @@ -0,0 +1,18 @@ +package aiyh.utils.tool.cn.hutool.json; + +/** + * 实现此接口的类可以通过实现{@code parse(value)}方法来将JSON中的值解析为此对象的值 + * + * @author Looly + * @since 5.7.8 + */ +public interface JSONBeanParser { + + /** + * value转Bean
+ * 通过实现此接口,将JSON中的值填充到当前对象的字段值中,即对象自行实现JSON反序列化逻辑 + * + * @param value 被解析的对象类型,可能为JSON或者普通String、Number等 + */ + void parse(T value); +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONConfig.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONConfig.java new file mode 100755 index 0000000..e2eb4a0 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONConfig.java @@ -0,0 +1,265 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.comparator.CompareUtil; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * JSON配置项 + * + * @author looly + * @since 4.1.19 + */ +public class JSONConfig implements Serializable { + private static final long serialVersionUID = 119730355204738278L; + + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,按照加入顺序排序 + */ + private Comparator keyComparator; + /** + * 是否忽略转换过程中的异常 + */ + private boolean ignoreError; + /** + * 是否忽略键的大小写 + */ + private boolean ignoreCase; + /** + * 日期格式,null表示默认的时间戳 + */ + private String dateFormat; + /** + * 是否忽略null值 + */ + private boolean ignoreNullValue = true; + /** + * 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。 + */ + private boolean transientSupport = true; + + /** + * 是否去除末尾多余0,例如如果为true,5.0返回5 + */ + private boolean stripTrailingZeros = true; + + /** + * 是否检查重复key + */ + private boolean checkDuplicate; + + /** + * 创建默认的配置项 + * + * @return JSONConfig + */ + public static JSONConfig create() { + return new JSONConfig(); + } + + /** + * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 + * + * @return 是否有序 + * @deprecated 始终返回 {@code true} + */ + @Deprecated + public boolean isOrder() { + return true; + } + + /** + * 设置是否有序,顺序按照加入顺序排序,只针对JSONObject有效 + * + * @param order 是否有序 + * @return this + * @deprecated 始终有序,无需设置 + */ + @SuppressWarnings("unused") + @Deprecated + public JSONConfig setOrder(boolean order) { + return this; + } + + /** + * 获取键排序规则
+ * 键排序规则,{@code null}表示不排序,不排序情况下,按照加入顺序排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则
+ * 键排序规则,{@code null}表示不排序,不排序情况下,按照加入顺序排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + + /** + * 是否忽略转换过程中的异常 + * + * @return 是否忽略转换过程中的异常 + */ + public boolean isIgnoreError() { + return ignoreError; + } + + /** + * 设置是否忽略转换过程中的异常 + * + * @param ignoreError 是否忽略转换过程中的异常 + * @return this + */ + public JSONConfig setIgnoreError(boolean ignoreError) { + this.ignoreError = ignoreError; + return this; + } + + /** + * 是否忽略键的大小写 + * + * @return 是否忽略键的大小写 + */ + public boolean isIgnoreCase() { + return ignoreCase; + } + + /** + * 设置是否忽略键的大小写 + * + * @param ignoreCase 是否忽略键的大小写 + * @return this + */ + public JSONConfig setIgnoreCase(boolean ignoreCase) { + this.ignoreCase = ignoreCase; + return this; + } + + /** + * 日期格式,null表示默认的时间戳 + * + * @return 日期格式,null表示默认的时间戳 + */ + public String getDateFormat() { + return dateFormat; + } + + /** + * 设置日期格式,null表示默认的时间戳
+ * 此方法设置的日期格式仅对转换为JSON字符串有效,对解析JSON为bean无效。 + * + * @param dateFormat 日期格式,null表示默认的时间戳 + * @return this + */ + public JSONConfig setDateFormat(String dateFormat) { + this.dateFormat = dateFormat; + return this; + } + + /** + * 是否忽略null值 + * + * @return 是否忽略null值 + */ + public boolean isIgnoreNullValue() { + return this.ignoreNullValue; + } + + /** + * 设置是否忽略null值 + * + * @param ignoreNullValue 是否忽略null值 + * @return this + */ + public JSONConfig setIgnoreNullValue(boolean ignoreNullValue) { + this.ignoreNullValue = ignoreNullValue; + return this; + } + + /** + * 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。 + * + * @return 是否支持 + * @since 5.4.2 + */ + public boolean isTransientSupport() { + return this.transientSupport; + } + + /** + * 设置是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。 + * + * @param transientSupport 是否支持 + * @return this + * @since 5.4.2 + */ + public JSONConfig setTransientSupport(boolean transientSupport) { + this.transientSupport = transientSupport; + return this; + } + + /** + * 是否去除末尾多余0,例如如果为true,5.0返回5 + * + * @return 是否去除末尾多余0,例如如果为true,5.0返回5 + * @since 5.6.2 + */ + public boolean isStripTrailingZeros() { + return stripTrailingZeros; + } + + /** + * 设置是否去除末尾多余0,例如如果为true,5.0返回5 + * + * @param stripTrailingZeros 是否去除末尾多余0,例如如果为true,5.0返回5 + * @return this + * @since 5.6.2 + */ + public JSONConfig setStripTrailingZeros(boolean stripTrailingZeros) { + this.stripTrailingZeros = stripTrailingZeros; + return this; + } + + /** + * 是否检查多个相同的key + * + * @return 是否检查多个相同的key + * @since 5.8.5 + */ + public boolean isCheckDuplicate() { + return checkDuplicate; + } + + /** + * 是否检查多个相同的key + * + * @param checkDuplicate 是否检查多个相同的key + * @return this + * @since 5.8.5 + */ + public JSONConfig setCheckDuplicate(boolean checkDuplicate) { + this.checkDuplicate = checkDuplicate; + return this; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONConverter.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONConverter.java new file mode 100644 index 0000000..8d94b3e --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONConverter.java @@ -0,0 +1,151 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.bean.BeanUtil; +import aiyh.utils.tool.cn.hutool.core.codec.Base64; +import aiyh.utils.tool.cn.hutool.core.convert.Convert; +import aiyh.utils.tool.cn.hutool.core.convert.ConvertException; +import aiyh.utils.tool.cn.hutool.core.convert.Converter; +import aiyh.utils.tool.cn.hutool.core.convert.ConverterRegistry; +import aiyh.utils.tool.cn.hutool.core.convert.impl.ArrayConverter; +import aiyh.utils.tool.cn.hutool.core.convert.impl.BeanConverter; +import aiyh.utils.tool.cn.hutool.core.util.ObjectUtil; +import aiyh.utils.tool.cn.hutool.core.util.ReflectUtil; +import aiyh.utils.tool.cn.hutool.core.util.StrUtil; +import aiyh.utils.tool.cn.hutool.core.util.TypeUtil; +import aiyh.utils.tool.cn.hutool.json.serialize.GlobalSerializeMapping; +import aiyh.utils.tool.cn.hutool.json.serialize.JSONDeserializer; + +import java.lang.reflect.Type; +import java.util.List; + +/** + * JSON转换器 + * + * @author looly + * @since 4.2.2 + */ +public class JSONConverter implements Converter { + + static { + // 注册到转换中心 + final ConverterRegistry registry = ConverterRegistry.getInstance(); + registry.putCustom(JSON.class, JSONConverter.class); + registry.putCustom(JSONObject.class, JSONConverter.class); + registry.putCustom(JSONArray.class, JSONConverter.class); + } + + /** + * JSONArray转数组 + * + * @param jsonArray JSONArray + * @param arrayClass 数组元素类型 + * @return 数组对象 + */ + protected static Object toArray(JSONArray jsonArray, Class arrayClass) { + return new ArrayConverter(arrayClass).convert(jsonArray, null); + } + + /** + * 将JSONArray转换为指定类型的对量列表 + * + * @param 元素类型 + * @param jsonArray JSONArray + * @param elementType 对象元素类型 + * @return 对象列表 + */ + protected static List toList(JSONArray jsonArray, Class elementType) { + return Convert.toList(elementType, jsonArray); + } + + /** + * JSON递归转换
+ * 首先尝试JDK类型转换,如果失败尝试JSON转Bean
+ * 如果遇到{@link JSONBeanParser},则调用其{@link JSONBeanParser#parse(Object)}方法转换。 + * + * @param 转换后的对象类型 + * @param targetType 目标类型 + * @param value 值 + * @param ignoreError 是否忽略转换错误 + * @return 目标类型的值 + * @throws ConvertException 转换失败 + */ + @SuppressWarnings("unchecked") + protected static T jsonConvert(Type targetType, Object value, boolean ignoreError) throws ConvertException { + if (JSONUtil.isNull(value)) { + return null; + } + + // since 5.7.8,增加自定义Bean反序列化接口 + if (targetType instanceof Class) { + final Class clazz = (Class) targetType; + if (JSONBeanParser.class.isAssignableFrom(clazz)) { + @SuppressWarnings("rawtypes") final JSONBeanParser target = (JSONBeanParser) ReflectUtil.newInstanceIfPossible(clazz); + if (null == target) { + throw new ConvertException("Can not instance [{}]", targetType); + } + target.parse(value); + return (T) target; + } else if (targetType == byte[].class && value instanceof CharSequence) { + // issue#I59LW4 + return (T) Base64.decode((CharSequence) value); + } + } + + return jsonToBean(targetType, value, ignoreError); + } + + /** + * JSON递归转换
+ * 首先尝试JDK类型转换,如果失败尝试JSON转Bean + * + * @param 转换后的对象类型 + * @param targetType 目标类型 + * @param value 值,JSON格式 + * @param ignoreError 是否忽略转换错误 + * @return 目标类型的值 + * @throws ConvertException 转换失败 + * @since 5.7.10 + */ + protected static T jsonToBean(Type targetType, Object value, boolean ignoreError) throws ConvertException { + if (JSONUtil.isNull(value)) { + return null; + } + + if (value instanceof JSON) { + final JSONDeserializer deserializer = GlobalSerializeMapping.getDeserializer(targetType); + if (null != deserializer) { + // noinspection unchecked + return (T) deserializer.deserialize((JSON) value); + } + + // issue#2212@Github + // 在JSONObject转Bean时,读取JSONObject本身的配置文件 + if (value instanceof JSONGetter + && targetType instanceof Class && BeanUtil.hasSetter((Class) targetType)) { + final JSONConfig config = ((JSONGetter) value).getConfig(); + final Converter converter = new BeanConverter<>(targetType, + InternalJSONUtil.toCopyOptions(config).setIgnoreError(ignoreError)); + return converter.convertWithCheck(value, null, ignoreError); + } + } + + final T targetValue = Convert.convertWithCheck(targetType, value, null, ignoreError); + + if (null == targetValue && !ignoreError) { + if (StrUtil.isBlankIfStr(value)) { + // 对于传入空字符串的情况,如果转换的目标对象是非字符串或非原始类型,转换器会返回false。 + // 此处特殊处理,认为返回null属于正常情况 + return null; + } + + throw new ConvertException("Can not convert {} to type {}", value, ObjectUtil.defaultIfNull(TypeUtil.getClass(targetType), targetType)); + } + + return targetValue; + } + + @Override + public JSON convert(Object value, JSON defaultValue) throws IllegalArgumentException { + return JSONUtil.parse(value); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONException.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONException.java new file mode 100644 index 0000000..4445ece --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONException.java @@ -0,0 +1,38 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.exceptions.ExceptionUtil; +import aiyh.utils.tool.cn.hutool.core.util.StrUtil; + +/** + * JSON异常 + * + * @author looly + * @since 3.0.2 + */ +public class JSONException extends RuntimeException { + private static final long serialVersionUID = 0; + + public JSONException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public JSONException(String message) { + super(message); + } + + public JSONException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public JSONException(String message, Throwable cause) { + super(message, cause); + } + + public JSONException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) { + super(message, throwable, enableSuppression, writableStackTrace); + } + + public JSONException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONGetter.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONGetter.java new file mode 100644 index 0000000..d49f055 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONGetter.java @@ -0,0 +1,237 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.convert.Convert; +import aiyh.utils.tool.cn.hutool.core.convert.ConvertException; +import aiyh.utils.tool.cn.hutool.core.date.DateUtil; +import aiyh.utils.tool.cn.hutool.core.date.LocalDateTimeUtil; +import aiyh.utils.tool.cn.hutool.core.getter.OptNullBasicTypeFromObjectGetter; +import aiyh.utils.tool.cn.hutool.core.util.StrUtil; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +/** + * 用于JSON的Getter类,提供各种类型的Getter方法 + * + * @param Key类型 + * @author Looly + */ +public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { + + /** + * 获取JSON配置 + * + * @return {@link JSONConfig} + * @since 5.3.0 + */ + JSONConfig getConfig(); + + /** + * key对应值是否为{@code null}或无此key + * + * @param key 键 + * @return true 无此key或值为{@code null}或{@link JSONNull#NULL}返回{@code false},其它返回{@code true} + */ + default boolean isNull(K key) { + return JSONUtil.isNull(this.getObj(key)); + } + + /** + * 获取字符串类型值,并转义不可见字符,如'\n'换行符会被转义为字符串"\n" + * + * @param key 键 + * @return 字符串类型值 + * @since 4.2.2 + */ + default String getStrEscaped(K key) { + return getStrEscaped(key, null); + } + + /** + * 获取字符串类型值,并转义不可见字符,如'\n'换行符会被转义为字符串"\n" + * + * @param key 键 + * @param defaultValue 默认值 + * @return 字符串类型值 + * @since 4.2.2 + */ + default String getStrEscaped(K key, String defaultValue) { + return JSONUtil.escape(getStr(key, defaultValue)); + } + + /** + * 获得JSONArray对象
+ * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 + * + * @param key KEY + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 + */ + default JSONArray getJSONArray(K key) { + final Object object = this.getObj(key); + if (JSONUtil.isNull(object)) { + return null; + } + + if (object instanceof JSON) { + return (JSONArray) object; + } + return new JSONArray(object, getConfig()); + } + + /** + * 获得JSONObject对象
+ * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 + * + * @param key KEY + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 + */ + default JSONObject getJSONObject(K key) { + final Object object = this.getObj(key); + if (JSONUtil.isNull(object)) { + return null; + } + + if (object instanceof JSON) { + return (JSONObject) object; + } + return new JSONObject(object, getConfig()); + } + + /** + * 从JSON中直接获取Bean对象
+ * 先获取JSONObject对象,然后转为Bean对象 + * + * @param Bean类型 + * @param key KEY + * @param beanType Bean类型 + * @return Bean对象,如果值为null或者非JSONObject类型,返回null + * @since 3.1.1 + */ + default T getBean(K key, Class beanType) { + final JSONObject obj = getJSONObject(key); + return (null == obj) ? null : obj.toBean(beanType); + } + + /** + * 从JSON中直接获取Bean的List列表
+ * 先获取JSONArray对象,然后转为Bean的List + * + * @param Bean类型 + * @param key KEY + * @param beanType Bean类型 + * @return Bean的List,如果值为null或者非JSONObject类型,返回null + * @since 5.7.20 + */ + default List getBeanList(K key, Class beanType) { + final JSONArray jsonArray = getJSONArray(key); + return (null == jsonArray) ? null : jsonArray.toList(beanType); + } + + @Override + default Date getDate(K key, Date defaultValue) { + // 默认转换 + final Object obj = getObj(key); + if (JSONUtil.isNull(obj)) { + return defaultValue; + } + if (obj instanceof Date) { + return (Date) obj; + } + + final Optional formatOps = Optional.ofNullable(getConfig()).map(JSONConfig::getDateFormat); + if (formatOps.isPresent()) { + final String format = formatOps.get(); + if (StrUtil.isNotBlank(format)) { + // 用户指定了日期格式,获取日期属性时使用对应格式 + final String str = Convert.toStr(obj); + if (null == str) { + return defaultValue; + } + return DateUtil.parse(str, format); + } + } + + return Convert.toDate(obj, defaultValue); + } + + /** + * 获取{@link LocalDateTime}类型值 + * + * @param key 键 + * @param defaultValue 默认值 + * @return {@link LocalDateTime} + * @since 5.7.7 + */ + default LocalDateTime getLocalDateTime(K key, LocalDateTime defaultValue) { + // 默认转换 + final Object obj = getObj(key); + if (JSONUtil.isNull(obj)) { + return defaultValue; + } + if (obj instanceof LocalDateTime) { + return (LocalDateTime) obj; + } + + final Optional formatOps = Optional.ofNullable(getConfig()).map(JSONConfig::getDateFormat); + if (formatOps.isPresent()) { + final String format = formatOps.get(); + if (StrUtil.isNotBlank(format)) { + // 用户指定了日期格式,获取日期属性时使用对应格式 + final String str = Convert.toStr(obj); + if (null == str) { + return defaultValue; + } + return LocalDateTimeUtil.parse(str, format); + } + } + + return Convert.toLocalDateTime(obj, defaultValue); + } + + /** + * 获取byte[]数据 + * + * @param key 键 + * @return 值 + * @since 5.8.2 + */ + default byte[] getBytes(K key) { + return get(key, byte[].class); + } + + /** + * 获取指定类型的对象
+ * 转换失败或抛出异常 + * + * @param 获取的对象类型 + * @param key 键 + * @param type 获取对象类型 + * @return 对象 + * @throws ConvertException 转换异常 + * @since 3.0.8 + */ + default T get(K key, Class type) throws ConvertException { + return get(key, type, false); + } + + /** + * 获取指定类型的对象 + * + * @param 获取的对象类型 + * @param key 键 + * @param type 获取对象类型 + * @param ignoreError 是否跳过转换失败的对象或值 + * @return 对象 + * @throws ConvertException 转换异常 + * @since 3.0.8 + */ + default T get(K key, Class type, boolean ignoreError) throws ConvertException { + final Object value = this.getObj(key); + if (JSONUtil.isNull(value)) { + return null; + } + return JSONConverter.jsonConvert(type, value, ignoreError); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONNull.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONNull.java new file mode 100644 index 0000000..b722d63 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONNull.java @@ -0,0 +1,46 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.util.StrUtil; + +import java.io.Serializable; + +/** + * 用于定义{@code null},与Javascript中null相对应
+ * Java中的{@code null}值在js中表示为undefined。 + * + * @author Looly + */ +public class JSONNull implements Serializable { + private static final long serialVersionUID = 2633815155870764938L; + + /** + * {@code NULL} 对象用于减少歧义来表示Java 中的{@code null}
+ * {@code NULL.equals(null)} 返回 {@code true}.
+ * {@code NULL.toString()} 返回 {@code "null"}. + */ + public static final JSONNull NULL = new JSONNull(); + + /** + * A Null object is equal to the null value and to itself. + * 对象与其本身和{@code null}值相等 + * + * @param object An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or null. + */ + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object object) { + return object == null || (object == this); + } + + /** + * Get the "null" string value. + * 获得“null”字符串 + * + * @return The string "null". + */ + @Override + public String toString() { + return StrUtil.NULL; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONObject.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONObject.java new file mode 100755 index 0000000..e95f38f --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONObject.java @@ -0,0 +1,577 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.bean.BeanPath; +import aiyh.utils.tool.cn.hutool.core.collection.CollectionUtil; +import aiyh.utils.tool.cn.hutool.core.lang.Filter; +import aiyh.utils.tool.cn.hutool.core.lang.mutable.MutablePair; +import aiyh.utils.tool.cn.hutool.core.map.CaseInsensitiveMap; +import aiyh.utils.tool.cn.hutool.core.map.MapUtil; +import aiyh.utils.tool.cn.hutool.core.map.MapWrapper; +import aiyh.utils.tool.cn.hutool.core.util.ArrayUtil; +import aiyh.utils.tool.cn.hutool.core.util.ObjectUtil; +import aiyh.utils.tool.cn.hutool.core.util.ReflectUtil; +import aiyh.utils.tool.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.Map; + +/** + * JSON对象
+ * 例:
+ * + *
+ * json = new JSONObject().put("JSON", "Hello, World!").toString();
+ * 
+ * + * @author looly + */ +public class JSONObject extends MapWrapper implements JSON, JSONGetter { + private static final long serialVersionUID = -330220388580734346L; + + /** + * 默认初始大小 + */ + public static final int DEFAULT_CAPACITY = MapUtil.DEFAULT_INITIAL_CAPACITY; + + /** + * 配置项 + */ + private 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 + * @deprecated isOrder无效 + */ + @SuppressWarnings("unused") + @Deprecated + public JSONObject(int capacity, boolean isIgnoreCase, boolean isOrder) { + this(capacity, JSONConfig.create().setIgnoreCase(isIgnoreCase)); + } + + /** + * 构造 + * + * @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) { + super(InternalJSONUtil.createRawMap(capacity, ObjectUtil.defaultIfNull(config, JSONConfig.create()))); + this.config = ObjectUtil.defaultIfNull(config, JSONConfig.create()); + } + + /** + * 构建JSONObject,JavaBean默认忽略null值,其它对象不忽略,规则如下: + *
    + *
  1. value为Map,将键值对加入JSON对象
  2. + *
  3. value为JSON字符串(CharSequence),使用JSONTokener解析
  4. + *
  5. value为JSONTokener,直接解析
  6. + *
  7. value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。 + * 例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  8. + *
+ * + * @param source JavaBean或者Map对象或者String + */ + public JSONObject(Object source) { + this(source, InternalJSONUtil.defaultIgnoreNullValue(source)); + } + + /** + * 构建JSONObject,规则如下: + *
    + *
  1. value为Map,将键值对加入JSON对象
  2. + *
  3. value为JSON字符串(CharSequence),使用JSONTokener解析
  4. + *
  5. value为JSONTokener,直接解析
  6. + *
  7. value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  8. + *
+ * + * @param source JavaBean或者Map对象或者String + * @param ignoreNullValue 是否忽略空值 + * @since 3.0.9 + */ + public JSONObject(Object source, boolean ignoreNullValue) { + this(source, JSONConfig.create().setIgnoreNullValue(ignoreNullValue)); + } + + /** + * 构建JSONObject,规则如下: + *
    + *
  1. value为Map,将键值对加入JSON对象
  2. + *
  3. value为JSON字符串(CharSequence),使用JSONTokener解析
  4. + *
  5. value为JSONTokener,直接解析
  6. + *
  7. value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  8. + *
+ * + * @param source JavaBean或者Map对象或者String + * @param ignoreNullValue 是否忽略空值,如果source为JSON字符串,不忽略空值 + * @param isOrder 是否有序 + * @since 4.2.2 + * @deprecated isOrder参数不再需要,JSONObject默认有序! + */ + @SuppressWarnings("unused") + @Deprecated + public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { + this(source, JSONConfig.create()// + .setIgnoreCase((source instanceof CaseInsensitiveMap))// + .setIgnoreNullValue(ignoreNullValue) + ); + } + + /** + * 构建JSONObject,规则如下: + *
    + *
  1. value为Map,将键值对加入JSON对象
  2. + *
  3. value为JSON字符串(CharSequence),使用JSONTokener解析
  4. + *
  5. value为JSONTokener,直接解析
  6. + *
  7. value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  8. + *
+ *

+ * 如果给定值为Map,将键值对加入JSON对象;
+ * 如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象
+ * 例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三" + * + * @param source JavaBean或者Map对象或者String + * @param config JSON配置文件,{@code null}则使用默认配置 + * @since 4.2.2 + */ + public JSONObject(Object source, JSONConfig config) { + this(source, config, null); + } + + /** + * 构建JSONObject,规则如下: + *

    + *
  1. value为Map,将键值对加入JSON对象
  2. + *
  3. value为JSON字符串(CharSequence),使用JSONTokener解析
  4. + *
  5. value为JSONTokener,直接解析
  6. + *
  7. value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  8. + *
+ *

+ * 如果给定值为Map,将键值对加入JSON对象;
+ * 如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象
+ * 例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三" + * + * @param source JavaBean或者Map对象或者String + * @param config JSON配置文件,{@code null}则使用默认配置 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @since 5.8.0 + */ + public JSONObject(Object source, JSONConfig config, Filter> filter) { + this(DEFAULT_CAPACITY, config); + ObjectMapper.of(source).map(this, filter); + } + + /** + * 构建指定name列表对应的键值对为新的JSONObject,情况如下: + * + *

+	 * 1. 若obj为Map,则获取name列表对应键值对
+	 * 2. 若obj为普通Bean,使用反射方式获取字段名和字段值
+	 * 
+ *

+ * KEY或VALUE任意一个为null则不加入,字段不存在也不加入
+ * 若names列表为空,则字段全部加入 + * + * @param source 包含需要字段的Bean对象或者Map对象 + * @param names 需要构建JSONObject的字段名列表 + */ + public JSONObject(Object source, String... names) { + this(); + if (ArrayUtil.isEmpty(names)) { + ObjectMapper.of(source).map(this, null); + return; + } + + if (source instanceof Map) { + Object value; + for (String name : names) { + value = ((Map) source).get(name); + this.set(name, value, null, getConfig().isCheckDuplicate()); + } + } else { + for (String name : names) { + try { + this.putOpt(name, ReflectUtil.getFieldValue(source, name)); + } catch (Exception ignore) { + // ignore + } + } + } + } + + /** + * 从JSON字符串解析为JSON对象,对于排序单独配置参数 + * + * @param source 以大括号 {} 包围的字符串,其中KEY和VALUE使用 : 分隔,每个键值对使用逗号分隔 + * @param isOrder 是否有序 + * @throws JSONException JSON字符串语法错误 + * @since 4.2.2 + * @deprecated isOrder无效 + */ + @SuppressWarnings("unused") + @Deprecated + public JSONObject(CharSequence source, boolean isOrder) throws JSONException { + this(source, JSONConfig.create()); + } + + // -------------------------------------------------------------------------------------------------------------------- Constructor end + + @Override + public JSONConfig getConfig() { + return this.config; + } + + /** + * 设置转为字符串时的日期格式,默认为时间戳(null值)
+ * 此方法设置的日期格式仅对转换为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 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 Object getObj(String key, Object defaultValue) { + return this.getOrDefault(key, defaultValue); + } + + @Override + public Object getByPath(String expression) { + return BeanPath.create(expression).get(this); + } + + @Override + public T getByPath(String expression, Class 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 { + return set(key, value, null, false); + } + + /** + * 设置键值对到JSONObject中,在忽略null模式下,如果值为{@code null},将此键移除 + * + * @param key 键 + * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @param checkDuplicate 是否检查重复键,如果为{@code true},则出现重复键时抛出{@link JSONException}异常 + * @return this. + * @throws JSONException 值是无穷数字抛出此异常 + * @since 5.8.0 + */ + public JSONObject set(String key, Object value, Filter> filter, boolean checkDuplicate) throws JSONException { + if (null == key) { + return this; + } + + // 添加前置过滤,通过MutablePair实现过滤、修改键值对等 + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + key = pair.getKey(); + value = pair.getValue(); + } else { + // 键值对被过滤 + return this; + } + } + + final boolean ignoreNullValue = this.config.isIgnoreNullValue(); + if (ObjectUtil.isNull(value) && ignoreNullValue) { + // 忽略值模式下如果值为空清除key + this.remove(key); + } else { + if (checkDuplicate && containsKey(key)) { + throw new JSONException("Duplicate key \"{}\"", key); + } + + super.put(key, JSONUtil.wrap(InternalJSONUtil.testValidity(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 { + return setOnce(key, value, null); + } + + /** + * 一次性Put 键值对,如果key已经存在抛出异常,如果键值中有null值,忽略 + * + * @param key 键 + * @param value 值对象,可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @return this + * @throws JSONException 值是无穷数字、键重复抛出异常 + * @since 5.8.0 + */ + public JSONObject setOnce(String key, Object value, Filter> filter) throws JSONException { + return set(key, value, filter, true); + } + + /** + * 在键和值都为非空的情况下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 m) { + for (Entry entry : m.entrySet()) { + this.set(entry.getKey(), entry.getValue()); + } + } + + /** + * 积累值。类似于set,当key对应value已经存在时,与value组成新的JSONArray.
+ * 如果只有一个值,此值就是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; + } + + /** + * 返回JSON字符串
+ * 如果解析错误,返回{@code null} + * + * @return JSON字符串 + */ + @Override + public String toString() { + return this.toJSONString(0); + } + + /** + * 返回JSON字符串
+ * 支持过滤器,即选择哪些字段或值不写出 + * + * @param indentFactor 每层缩进空格数 + * @param filter 过滤器,同时可以修改编辑键和值 + * @return JSON字符串 + * @since 5.7.15 + */ + public String toJSONString(int indentFactor, Filter> 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
+ * 支持过滤器,即选择哪些字段或值不写出 + * + * @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> filter) throws JSONException { + final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) + .beginObj(); + this.forEach((key, value) -> jsonWriter.writeField(new MutablePair<>(key, value), filter)); + jsonWriter.end(); + // 此处不关闭Writer,考虑writer后续还需要填内容 + return writer; + } + + @Override + public JSONObject clone() throws CloneNotSupportedException { + final JSONObject clone = (JSONObject) super.clone(); + clone.config = this.config; + return clone; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONObjectIter.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONObjectIter.java new file mode 100644 index 0000000..cf288e9 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONObjectIter.java @@ -0,0 +1,41 @@ +package aiyh.utils.tool.cn.hutool.json; + + +import java.util.Iterator; + +/** + * 此类用于在JSONAray中便于遍历JSONObject而封装的Iterable,可以借助foreach语法遍历 + * + * @author looly + * @since 4.0.12 + */ +public class JSONObjectIter implements Iterable { + + Iterator iterator; + + public JSONObjectIter(Iterator iterator) { + this.iterator = iterator; + } + + @Override + public Iterator iterator() { + return new Iterator() { + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public JSONObject next() { + return (JSONObject) iterator.next(); + } + + @Override + public void remove() { + iterator.remove(); + } + }; + } + +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONParser.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONParser.java new file mode 100755 index 0000000..ede4f0d --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONParser.java @@ -0,0 +1,139 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.lang.Filter; +import aiyh.utils.tool.cn.hutool.core.lang.mutable.Mutable; +import aiyh.utils.tool.cn.hutool.core.lang.mutable.MutablePair; + +/** + * JSON字符串解析器 + * + * @author looly + * @since 5.8.0 + */ +public class JSONParser { + + /** + * 创建JSONParser + * + * @param tokener {@link JSONTokener} + * @return JSONParser + */ + public static JSONParser of(JSONTokener tokener) { + return new JSONParser(tokener); + } + + private final JSONTokener tokener; + + /** + * 构造 + * + * @param tokener {@link JSONTokener} + */ + public JSONParser(JSONTokener tokener) { + this.tokener = tokener; + } + + // region parseTo + + /** + * 解析{@link JSONTokener}中的字符到目标的{@link JSONObject}中 + * + * @param jsonObject {@link JSONObject} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + */ + public void parseTo(JSONObject jsonObject, Filter> filter) { + final JSONTokener tokener = this.tokener; + + if (tokener.nextClean() != '{') { + throw tokener.syntaxError("A JSONObject text must begin with '{'"); + } + + char prev; + char c; + String key; + while (true) { + prev = tokener.getPrevious(); + c = tokener.nextClean(); + switch (c) { + case 0: + throw tokener.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + case '{': + case '[': + if (prev == '{') { + throw tokener.syntaxError("A JSONObject can not directly nest another JSONObject or JSONArray."); + } + default: + tokener.back(); + key = tokener.nextValue().toString(); + } + + // The key is followed by ':'. + + c = tokener.nextClean(); + if (c != ':') { + throw tokener.syntaxError("Expected a ':' after a key"); + } + + jsonObject.set(key, tokener.nextValue(), filter, jsonObject.getConfig().isCheckDuplicate()); + + // Pairs are separated by ','. + + switch (tokener.nextClean()) { + case ';': + case ',': + if (tokener.nextClean() == '}') { + // issue#2380 + // 尾后逗号(Trailing Commas),JSON中虽然不支持,但是ECMAScript 2017支持,此处做兼容。 + return; + } + tokener.back(); + break; + case '}': + return; + default: + throw tokener.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * 解析JSON字符串到{@link JSONArray}中 + * + * @param jsonArray {@link .JSONArray} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null} 表示不过滤 + */ + public void parseTo(JSONArray jsonArray, Filter> filter) { + final JSONTokener x = this.tokener; + + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + if (x.nextClean() != ']') { + x.back(); + for (; ; ) { + if (x.nextClean() == ',') { + x.back(); + jsonArray.addRaw(JSONNull.NULL, filter); + } else { + x.back(); + jsonArray.addRaw(x.nextValue(), filter); + } + switch (x.nextClean()) { + case ',': + if (x.nextClean() == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } + // endregion +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONStrFormatter.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONStrFormatter.java new file mode 100644 index 0000000..dc3f329 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONStrFormatter.java @@ -0,0 +1,140 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.util.CharUtil; +import aiyh.utils.tool.cn.hutool.core.util.StrUtil; + +/** + * JSON字符串格式化工具,用于简单格式化JSON字符串
+ * from http://blog.csdn.net/lovelong8808/article/details/54580278 + * + * @author looly + * @since 3.1.2 + */ +public class JSONStrFormatter { + + /** + * 单位缩进字符串。 + */ + private static final String SPACE = " "; + /** + * 换行符 + */ + private static final char NEW_LINE = StrUtil.C_LF; + + /** + * 返回格式化JSON字符串。 + * + * @param json 未格式化的JSON字符串。 + * @return 格式化的JSON字符串。 + */ + public static String format(String json) { + final StringBuilder result = new StringBuilder(); + + Character wrapChar = null; + boolean isEscapeMode = false; + int length = json.length(); + int number = 0; + char key; + for (int i = 0; i < length; i++) { + key = json.charAt(i); + + if (CharUtil.DOUBLE_QUOTES == key || CharUtil.SINGLE_QUOTE == key) { + if (null == wrapChar) { + // 字符串模式开始 + wrapChar = key; + } else if (isEscapeMode) { + // 在字符串模式下的转义 + isEscapeMode = false; + } else if (wrapChar.equals(key)) { + // 字符串包装结束 + wrapChar = null; + } + + if ((i > 1) && (json.charAt(i - 1) == CharUtil.COLON)) { + result.append(CharUtil.SPACE); + } + + result.append(key); + continue; + } + + if (CharUtil.BACKSLASH == key) { + if (null != wrapChar) { + // 字符串模式下转义有效 + isEscapeMode = !isEscapeMode; + result.append(key); + continue; + } else { + result.append(key); + } + } + + if (null != wrapChar) { + // 字符串模式 + result.append(key); + continue; + } + + // 如果当前字符是前方括号、前花括号做如下处理: + if ((key == CharUtil.BRACKET_START) || (key == CharUtil.DELIM_START)) { + // 如果前面还有字符,并且字符为“:”,打印:换行和缩进字符字符串。 + if ((i > 1) && (json.charAt(i - 1) == CharUtil.COLON)) { + result.append(NEW_LINE); + result.append(indent(number)); + } + result.append(key); + // 前方括号、前花括号,的后面必须换行。打印:换行。 + result.append(NEW_LINE); + // 每出现一次前方括号、前花括号;缩进次数增加一次。打印:新行缩进。 + number++; + result.append(indent(number)); + + continue; + } + + // 3、如果当前字符是后方括号、后花括号做如下处理: + if ((key == CharUtil.BRACKET_END) || (key == CharUtil.DELIM_END)) { + // (1)后方括号、后花括号,的前面必须换行。打印:换行。 + result.append(NEW_LINE); + // (2)每出现一次后方括号、后花括号;缩进次数减少一次。打印:缩进。 + number--; + result.append(indent(number)); + // (3)打印:当前字符。 + result.append(key); + // (4)如果当前字符后面还有字符,并且字符不为“,”,打印:换行。 +// if (((i + 1) < length) && (json.charAt(i + 1) != ',')) { +// result.append(NEW_LINE); +// } + // (5)继续下一次循环。 + continue; + } + + // 4、如果当前字符是逗号。逗号后面换行,并缩进,不改变缩进次数。 + if ((key == ',')) { + result.append(key); + result.append(NEW_LINE); + result.append(indent(number)); + continue; + } + + if ((i > 1) && (json.charAt(i - 1) == CharUtil.COLON)) { + result.append(CharUtil.SPACE); + } + + // 5、打印:当前字符。 + result.append(key); + } + + return result.toString(); + } + + /** + * 返回指定次数的缩进字符串。每一次缩进4个空格,即SPACE。 + * + * @param number 缩进次数。 + * @return 指定缩进次数的字符串。 + */ + private static String indent(int number) { + return StrUtil.repeat(SPACE, number); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONString.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONString.java new file mode 100644 index 0000000..f0b07f9 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONString.java @@ -0,0 +1,17 @@ +package aiyh.utils.tool.cn.hutool.json; + +/** + * {@code JSONString}接口定义了一个{@code toJSONString()}
+ * 实现此接口的类可以通过实现{@code toJSONString()}方法来改变转JSON字符串的方式。 + * + * @author Looly + */ +public interface JSONString { + + /** + * 自定义转JSON字符串的方法 + * + * @return JSON字符串 + */ + String toJSONString(); +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONSupport.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONSupport.java new file mode 100644 index 0000000..d930440 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONSupport.java @@ -0,0 +1,58 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.bean.BeanUtil; + +/** + * JSON支持
+ * 继承此类实现实体类与JSON的相互转换 + * + * @author Looly + */ +public class JSONSupport implements JSONString, JSONBeanParser { + + /** + * JSON String转Bean + * + * @param jsonString JSON String + */ + public void parse(String jsonString) { + parse(new JSONObject(jsonString)); + } + + /** + * JSON转Bean + * + * @param json JSON + */ + @Override + public void parse(JSON json) { + final JSONSupport support = JSONConverter.jsonToBean(getClass(), json, false); + BeanUtil.copyProperties(support, this); + } + + /** + * @return JSON对象 + */ + public JSONObject toJSON() { + return new JSONObject(this); + } + + @Override + public String toJSONString() { + return toJSON().toString(); + } + + /** + * 美化的JSON(使用回车缩进显示JSON),用于打印输出debug + * + * @return 美化的JSON + */ + public String toPrettyString() { + return toJSON().toStringPretty(); + } + + @Override + public String toString() { + return toJSONString(); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONTokener.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONTokener.java new file mode 100755 index 0000000..01cab6f --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONTokener.java @@ -0,0 +1,458 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.io.IoUtil; +import aiyh.utils.tool.cn.hutool.core.util.StrUtil; + +import java.io.*; + +/** + * JSON解析器,用于将JSON字符串解析为JSONObject或者JSONArray + * + * @author from JSON.org + */ +public class JSONTokener { + + private long character; + /** + * 是否结尾 End of stream + */ + private boolean eof; + /** + * 在Reader的位置(解析到第几个字符) + */ + private long index; + /** + * 当前所在行 + */ + private long line; + /** + * 前一个字符 + */ + private char previous; + /** + * 是否使用前一个字符 + */ + private boolean usePrevious; + /** + * 源 + */ + private final Reader reader; + + /** + * JSON配置 + */ + private final JSONConfig config; + + // ------------------------------------------------------------------------------------ Constructor start + + /** + * 从Reader中构建 + * + * @param reader Reader + * @param config JSON配置 + */ + public JSONTokener(Reader reader, JSONConfig config) { + this.reader = reader.markSupported() ? reader : new BufferedReader(reader); + this.eof = false; + this.usePrevious = false; + this.previous = 0; + this.index = 0; + this.character = 1; + this.line = 1; + this.config = config; + } + + /** + * 从InputStream中构建,使用UTF-8编码 + * + * @param inputStream InputStream + * @param config JSON配置 + */ + public JSONTokener(InputStream inputStream, JSONConfig config) throws JSONException { + this(IoUtil.getUtf8Reader(inputStream), config); + } + + /** + * 从字符串中构建 + * + * @param s JSON字符串 + * @param config JSON配置 + */ + public JSONTokener(CharSequence s, JSONConfig config) { + this(new StringReader(StrUtil.str(s)), config); + } + // ------------------------------------------------------------------------------------ Constructor end + + /** + * 将标记回退到第一个字符,重新开始解析新的JSON + */ + public void back() throws JSONException { + if (this.usePrevious || this.index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + this.index -= 1; + this.character -= 1; + this.usePrevious = true; + this.eof = false; + } + + /** + * @return 是否进入结尾 + */ + public boolean end() { + return this.eof && !this.usePrevious; + } + + /** + * 源字符串是否有更多的字符 + * + * @return 如果未达到结尾返回true,否则false + */ + public boolean more() throws JSONException { + this.next(); + if (this.end()) { + return false; + } + this.back(); + return true; + } + + /** + * 获得源字符串中的下一个字符 + * + * @return 下一个字符, or 0 if past the end of the source string. + * @throws JSONException JSON异常,包装IO异常 + */ + public char next() throws JSONException { + int c; + if (this.usePrevious) { + this.usePrevious = false; + c = this.previous; + } else { + try { + c = this.reader.read(); + } catch (IOException exception) { + throw new JSONException(exception); + } + + if (c <= 0) { // End of stream + this.eof = true; + c = 0; + } + } + this.index += 1; + if (this.previous == '\r') { + this.line += 1; + this.character = c == '\n' ? 0 : 1; + } else if (c == '\n') { + this.line += 1; + this.character = 0; + } else { + this.character += 1; + } + this.previous = (char) c; + return this.previous; + } + + /** + * Get the last character read from the input or '\0' if nothing has been read yet. + * + * @return the last character read from the input. + */ + protected char getPrevious() { + return this.previous; + } + + /** + * 读取下一个字符,并比对是否和指定字符匹配 + * + * @param c 被匹配的字符 + * @return The character 匹配到的字符 + * @throws JSONException 如果不匹配抛出此异常 + */ + public char next(char c) throws JSONException { + char n = this.next(); + if (n != c) { + throw this.syntaxError("Expected '" + c + "' and instead saw '" + n + "'"); + } + return n; + } + + /** + * 获得接下来的n个字符 + * + * @param n 字符数 + * @return 获得的n个字符组成的字符串 + * @throws JSONException 如果源中余下的字符数不足以提供所需的字符数,抛出此异常 + */ + public String next(int n) throws JSONException { + if (n == 0) { + return ""; + } + + char[] chars = new char[n]; + int pos = 0; + while (pos < n) { + chars[pos] = this.next(); + if (this.end()) { + throw this.syntaxError("Substring bounds error"); + } + pos += 1; + } + return new String(chars); + } + + /** + * 获得下一个字符,跳过空白符 + * + * @return 获得的字符,0表示没有更多的字符 + * @throws JSONException 获得下一个字符时抛出的异常 + */ + public char nextClean() throws JSONException { + char c; + while (true) { + c = this.next(); + if (c == 0 || c > ' ') { + return c; + } + } + } + + /** + * 返回当前位置到指定引号前的所有字符,反斜杠的转义符也会被处理。
+ * 标准的JSON是不允许使用单引号包含字符串的,但是此实现允许。 + * + * @param quote 字符引号, 包括 {@code "}(双引号) 或 {@code '}(单引号)。 + * @return 截止到引号前的字符串 + * @throws JSONException 出现无结束的字符串时抛出此异常 + */ + public String nextString(char quote) throws JSONException { + char c; + StringBuilder sb = new StringBuilder(); + while (true) { + c = this.next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw this.syntaxError("Unterminated string"); + case '\\':// 转义符 + c = this.next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u':// Unicode符 + sb.append((char) Integer.parseInt(this.next(4), 16)); + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw this.syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } + + /** + * Get the text up but not including the specified character or the end of line, whichever comes first.
+ * 获得从当前位置直到分隔符(不包括分隔符)或行尾的的所有字符。 + * + * @param delimiter 分隔符 + * @return 字符串 + */ + public String nextTo(char delimiter) throws JSONException { + StringBuilder sb = new StringBuilder(); + for (; ; ) { + char c = this.next(); + if (c == delimiter || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the text up but not including one of the specified delimiter characters or the end of line, whichever comes first. + * + * @param delimiters A set of delimiter characters. + * @return A string, trimmed. + */ + public String nextTo(String delimiters) throws JSONException { + char c; + StringBuilder sb = new StringBuilder(); + for (; ; ) { + c = this.next(); + if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * 获得下一个值,值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL + * + * @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL + * @throws JSONException 语法错误 + */ + public Object nextValue() throws JSONException { + char c = this.nextClean(); + String string; + + switch (c) { + case '"': + case '\'': + return this.nextString(c); + case '{': + this.back(); + try { + return new JSONObject(this, this.config); + } catch (final StackOverflowError e) { + throw new JSONException("JSONObject depth too large to process.", e); + } + case '[': + this.back(); + try { + return new JSONArray(this, this.config); + } catch (final StackOverflowError e) { + throw new JSONException("JSONArray depth too large to process.", e); + } + } + + /* + * Handle unquoted text. This could be the values true, false, or null, or it can be a number. + * An implementation (such as this one) is allowed to also accept non-standard forms. Accumulate + * characters until we reach the end of the text or a formatting character. + */ + + final StringBuilder sb = new StringBuilder(); + while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { + sb.append(c); + c = this.next(); + } + this.back(); + + string = sb.toString().trim(); + if (0 == string.length()) { + throw this.syntaxError("Missing value"); + } + return InternalJSONUtil.stringToValue(string); + } + + /** + * Skip characters until the next character is the requested character. If the requested character is not found, no characters are skipped. 在遇到指定字符前,跳过其它字符。如果字符未找到,则不跳过任何字符。 + * + * @param to 需要定位的字符 + * @return 定位的字符,如果字符未找到返回0 + */ + public char skipTo(char to) throws JSONException { + char c; + try { + long startIndex = this.index; + long startCharacter = this.character; + long startLine = this.line; + this.reader.mark(1000000); + do { + c = this.next(); + if (c == 0) { + this.reader.reset(); + this.index = startIndex; + this.character = startCharacter; + this.line = startLine; + return c; + } + } while (c != to); + } catch (IOException exception) { + throw new JSONException(exception); + } + this.back(); + return c; + } + + /** + * Make a JSONException to signal a syntax error.
+ * 构建 JSONException 用于表示语法错误 + * + * @param message 错误消息 + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this); + } + + /** + * 转为 {@link JSONArray} + * + * @return {@link JSONArray} + */ + public JSONArray toJSONArray() { + JSONArray jsonArray = new JSONArray(this.config); + if (this.nextClean() != '[') { + throw this.syntaxError("A JSONArray text must start with '['"); + } + if (this.nextClean() != ']') { + this.back(); + while (true) { + if (this.nextClean() == ',') { + this.back(); + jsonArray.add(JSONNull.NULL); + } else { + this.back(); + jsonArray.add(this.nextValue()); + } + switch (this.nextClean()) { + case ',': + if (this.nextClean() == ']') { + return jsonArray; + } + this.back(); + break; + case ']': + return jsonArray; + default: + throw this.syntaxError("Expected a ',' or ']'"); + } + } + } + return jsonArray; + } + + /** + * Make a printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + @Override + public String toString() { + return " at " + this.index + " [character " + this.character + " line " + this.line + "]"; + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONUtil.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONUtil.java new file mode 100755 index 0000000..f954f4e --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/JSONUtil.java @@ -0,0 +1,983 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.io.IORuntimeException; +import aiyh.utils.tool.cn.hutool.core.io.file.FileReader; +import aiyh.utils.tool.cn.hutool.core.lang.TypeReference; +import aiyh.utils.tool.cn.hutool.core.map.MapWrapper; +import aiyh.utils.tool.cn.hutool.core.util.*; +import aiyh.utils.tool.cn.hutool.json.serialize.GlobalSerializeMapping; +import aiyh.utils.tool.cn.hutool.json.serialize.JSONArraySerializer; +import aiyh.utils.tool.cn.hutool.json.serialize.JSONDeserializer; +import aiyh.utils.tool.cn.hutool.json.serialize.JSONObjectSerializer; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.charset.Charset; +import java.sql.SQLException; +import java.time.temporal.TemporalAccessor; +import java.util.*; + +/** + * JSON工具类 + * + * @author Looly + */ +public class JSONUtil { + + // -------------------------------------------------------------------- Pause start + + /** + * 创建JSONObject + * + * @return JSONObject + */ + public static JSONObject createObj() { + return new JSONObject(); + } + + /** + * 创建JSONObject + * + * @param config JSON配置 + * @return JSONObject + * @since 5.2.5 + */ + public static JSONObject createObj(JSONConfig config) { + return new JSONObject(config); + } + + /** + * 创建 JSONArray + * + * @return JSONArray + */ + public static JSONArray createArray() { + return new JSONArray(); + } + + /** + * 创建 JSONArray + * + * @param config JSON配置 + * @return JSONArray + * @since 5.2.5 + */ + public static JSONArray createArray(JSONConfig config) { + return new JSONArray(config); + } + + /** + * JSON字符串转JSONObject对象 + * + * @param jsonStr JSON字符串 + * @return JSONObject + */ + public static JSONObject parseObj(String jsonStr) { + return new JSONObject(jsonStr); + } + + /** + * JSON字符串转JSONObject对象
+ * 此方法会忽略空值,但是对JSON字符串不影响 + * + * @param obj Bean对象或者Map + * @return JSONObject + */ + public static JSONObject parseObj(Object obj) { + return parseObj(obj, null); + } + + /** + * JSON字符串转JSONObject对象
+ * 此方法会忽略空值,但是对JSON字符串不影响 + * + * @param obj Bean对象或者Map + * @param config JSON配置 + * @return JSONObject + * @since 5.3.1 + */ + public static JSONObject parseObj(Object obj, JSONConfig config) { + return new JSONObject(obj, ObjectUtil.defaultIfNull(config, JSONConfig::create)); + } + + /** + * JSON字符串转JSONObject对象 + * + * @param obj Bean对象或者Map + * @param ignoreNullValue 是否忽略空值,如果source为JSON字符串,不忽略空值 + * @return JSONObject + * @since 3.0.9 + */ + public static JSONObject parseObj(Object obj, boolean ignoreNullValue) { + return new JSONObject(obj, ignoreNullValue); + } + + /** + * JSON字符串转JSONObject对象 + * + * @param obj Bean对象或者Map + * @param ignoreNullValue 是否忽略空值,如果source为JSON字符串,不忽略空值 + * @param isOrder 是否有序 + * @return JSONObject + * @since 4.2.2 + * @deprecated isOrder参数不再有效 + */ + @SuppressWarnings("unused") + @Deprecated + public static JSONObject parseObj(Object obj, boolean ignoreNullValue, boolean isOrder) { + return new JSONObject(obj, ignoreNullValue); + } + + /** + * JSON字符串转JSONArray + * + * @param jsonStr JSON字符串 + * @return JSONArray + */ + public static JSONArray parseArray(String jsonStr) { + return new JSONArray(jsonStr); + } + + /** + * JSON字符串转JSONArray + * + * @param arrayOrCollection 数组或集合对象 + * @return JSONArray + * @since 3.0.8 + */ + public static JSONArray parseArray(Object arrayOrCollection) { + return parseArray(arrayOrCollection, null); + } + + /** + * JSON字符串转JSONArray + * + * @param arrayOrCollection 数组或集合对象 + * @param config JSON配置 + * @return JSONArray + * @since 5.3.1 + */ + public static JSONArray parseArray(Object arrayOrCollection, JSONConfig config) { + return new JSONArray(arrayOrCollection, config); + } + + /** + * JSON字符串转JSONArray + * + * @param arrayOrCollection 数组或集合对象 + * @param ignoreNullValue 是否忽略空值 + * @return JSONArray + * @since 3.2.3 + */ + public static JSONArray parseArray(Object arrayOrCollection, boolean ignoreNullValue) { + return new JSONArray(arrayOrCollection, ignoreNullValue); + } + + /** + * 转换对象为JSON,如果用户不配置JSONConfig,则JSON的有序与否与传入对象有关。
+ * 支持的对象: + *
    + *
  • String: 转换为相应的对象
  • + *
  • Array、Iterable、Iterator:转换为JSONArray
  • + *
  • Bean对象:转为JSONObject
  • + *
+ * + * @param obj 对象 + * @return JSON + */ + public static JSON parse(Object obj) { + return parse(obj, null); + } + + /** + * 转换对象为JSON,如果用户不配置JSONConfig,则JSON的有序与否与传入对象有关。
+ * 支持的对象: + *
    + *
  • String: 转换为相应的对象
  • + *
  • Array、Iterable、Iterator:转换为JSONArray
  • + *
  • Bean对象:转为JSONObject
  • + *
+ * + * @param obj 对象 + * @param config JSON配置,{@code null}使用默认配置 + * @return JSON + * @since 5.3.1 + */ + public static JSON parse(Object obj, JSONConfig config) { + if (null == obj) { + return null; + } + JSON json; + if (obj instanceof JSON) { + json = (JSON) obj; + } else if (obj instanceof CharSequence) { + final String jsonStr = StrUtil.trim((CharSequence) obj); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + } else if (obj instanceof MapWrapper) { + // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 + json = parseObj(obj, config); + } else if (obj instanceof Iterable || obj instanceof Iterator || ArrayUtil.isArray(obj)) {// 列表 + json = parseArray(obj, config); + } else {// 对象 + json = parseObj(obj, config); + } + + return json; + } + + /** + * XML字符串转为JSONObject + * + * @param xmlStr XML字符串 + * @return JSONObject + */ + public static JSONObject parseFromXml(String xmlStr) { + return XML.toJSONObject(xmlStr); + } + + // -------------------------------------------------------------------- Parse end + + // -------------------------------------------------------------------- Read start + + /** + * 读取JSON + * + * @param file JSON文件 + * @param charset 编码 + * @return JSON(包括JSONObject和JSONArray) + * @throws IORuntimeException IO异常 + */ + public static JSON readJSON(File file, Charset charset) throws IORuntimeException { + return parse(FileReader.create(file, charset).readString()); + } + + /** + * 读取JSONObject + * + * @param file JSON文件 + * @param charset 编码 + * @return JSONObject + * @throws IORuntimeException IO异常 + */ + public static JSONObject readJSONObject(File file, Charset charset) throws IORuntimeException { + return parseObj(FileReader.create(file, charset).readString()); + } + + /** + * 读取JSONArray + * + * @param file JSON文件 + * @param charset 编码 + * @return JSONArray + * @throws IORuntimeException IO异常 + */ + public static JSONArray readJSONArray(File file, Charset charset) throws IORuntimeException { + return parseArray(FileReader.create(file, charset).readString()); + } + // -------------------------------------------------------------------- Read end + + // -------------------------------------------------------------------- toString start + + /** + * 转为JSON字符串 + * + * @param json JSON + * @param indentFactor 每一级别的缩进 + * @return JSON字符串 + */ + public static String toJsonStr(JSON json, int indentFactor) { + if (null == json) { + return null; + } + return json.toJSONString(indentFactor); + } + + /** + * 转为JSON字符串 + * + * @param json JSON + * @return JSON字符串 + */ + public static String toJsonStr(JSON json) { + if (null == json) { + return null; + } + return json.toJSONString(0); + } + + /** + * 转为JSON字符串,并写出到write + * + * @param json JSON + * @param writer Writer + * @since 5.3.3 + */ + public static void toJsonStr(JSON json, Writer writer) { + if (null != json) { + json.write(writer); + } + } + + /** + * 转为JSON字符串 + * + * @param json JSON + * @return JSON字符串 + */ + public static String toJsonPrettyStr(JSON json) { + if (null == json) { + return null; + } + return json.toJSONString(4); + } + + /** + * 转换为JSON字符串 + * + * @param obj 被转为JSON的对象 + * @return JSON字符串 + */ + public static String toJsonStr(Object obj) { + return toJsonStr(obj, (JSONConfig) null); + } + + /** + * 转换为JSON字符串 + * + * @param obj 被转为JSON的对象 + * @param jsonConfig JSON配置 + * @return JSON字符串 + * @since 5.7.12 + */ + public static String toJsonStr(Object obj, JSONConfig jsonConfig) { + if (null == obj) { + return null; + } + if (obj instanceof CharSequence) { + return StrUtil.str((CharSequence) obj); + } + return toJsonStr(parse(obj, jsonConfig)); + } + + /** + * 转换为JSON字符串并写出到writer + * + * @param obj 被转为JSON的对象 + * @param writer Writer + * @since 5.3.3 + */ + public static void toJsonStr(Object obj, Writer writer) { + if (null != obj) { + toJsonStr(parse(obj), writer); + } + } + + /** + * 转换为格式化后的JSON字符串 + * + * @param obj Bean对象 + * @return JSON字符串 + */ + public static String toJsonPrettyStr(Object obj) { + return toJsonPrettyStr(parse(obj)); + } + + /** + * 转换为XML字符串 + * + * @param json JSON + * @return XML字符串 + */ + public static String toXmlStr(JSON json) { + return XML.toXml(json); + } + // -------------------------------------------------------------------- toString end + + // -------------------------------------------------------------------- toBean start + + /** + * JSON字符串转为实体类对象,转换异常将被抛出 + * + * @param Bean类型 + * @param jsonString JSON字符串 + * @param beanClass 实体类对象 + * @return 实体类对象 + * @since 3.1.2 + */ + public static T toBean(String jsonString, Class beanClass) { + return toBean(parseObj(jsonString), beanClass); + } + + /** + * JSON字符串转为实体类对象,转换异常将被抛出
+ * 通过{@link JSONConfig}可选是否忽略大小写、忽略null等配置 + * + * @param Bean类型 + * @param jsonString JSON字符串 + * @param config JSON配置 + * @param beanClass 实体类对象 + * @return 实体类对象 + * @since 5.8.0 + */ + public static T toBean(String jsonString, JSONConfig config, Class beanClass) { + return toBean(parseObj(jsonString, config), beanClass); + } + + /** + * 转为实体类对象,转换异常将被抛出 + * + * @param Bean类型 + * @param json JSONObject + * @param beanClass 实体类对象 + * @return 实体类对象 + */ + public static T toBean(JSONObject json, Class beanClass) { + return null == json ? null : json.toBean(beanClass); + } + + /** + * JSON字符串转为实体类对象,转换异常将被抛出 + * + * @param Bean类型 + * @param jsonString JSON字符串 + * @param typeReference {@link TypeReference}类型参考子类,可以获取其泛型参数中的Type类型 + * @param ignoreError 是否忽略错误 + * @return 实体类对象 + * @since 4.3.2 + */ + public static T toBean(String jsonString, TypeReference typeReference, boolean ignoreError) { + return toBean(jsonString, typeReference.getType(), ignoreError); + } + + /** + * JSON字符串转为实体类对象,转换异常将被抛出 + * + * @param Bean类型 + * @param jsonString JSON字符串 + * @param beanType 实体类对象类型 + * @param ignoreError 是否忽略错误 + * @return 实体类对象 + * @since 4.3.2 + */ + public static T toBean(String jsonString, Type beanType, boolean ignoreError) { + return parse(jsonString, JSONConfig.create().setIgnoreError(ignoreError)).toBean(beanType); + } + + /** + * 转为实体类对象 + * + * @param Bean类型 + * @param json JSONObject + * @param typeReference {@link TypeReference}类型参考子类,可以获取其泛型参数中的Type类型 + * @param ignoreError 是否忽略转换错误 + * @return 实体类对象 + * @since 4.6.2 + */ + public static T toBean(JSON json, TypeReference typeReference, boolean ignoreError) { + return toBean(json, typeReference.getType(), ignoreError); + } + + /** + * 转为实体类对象 + * + * @param Bean类型 + * @param json JSONObject + * @param beanType 实体类对象类型 + * @param ignoreError 是否忽略转换错误 + * @return 实体类对象 + * @since 4.3.2 + */ + public static T toBean(JSON json, Type beanType, boolean ignoreError) { + if (null == json) { + return null; + } + return json.toBean(beanType, ignoreError); + } + // -------------------------------------------------------------------- toBean end + + /** + * 将JSONArray字符串转换为Bean的List,默认为ArrayList + * + * @param Bean类型 + * @param jsonArray JSONArray字符串 + * @param elementType List中元素类型 + * @return List + * @since 5.5.2 + */ + public static List toList(String jsonArray, Class elementType) { + return toList(parseArray(jsonArray), elementType); + } + + /** + * 将JSONArray转换为Bean的List,默认为ArrayList + * + * @param Bean类型 + * @param jsonArray {@link JSONArray} + * @param elementType List中元素类型 + * @return List + * @since 4.0.7 + */ + public static List toList(JSONArray jsonArray, Class elementType) { + return null == jsonArray ? null : jsonArray.toList(elementType); + } + + /** + * 通过表达式获取JSON中嵌套的对象
+ *
    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ * + * @param json {@link JSON} + * @param expression 表达式 + * @return 对象 + * @see JSON#getByPath(String) + */ + public static Object getByPath(JSON json, String expression) { + return getByPath(json, expression, null); + } + + /** + * 通过表达式获取JSON中嵌套的对象
+ *
    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ * + * @param 值类型 + * @param json {@link JSON} + * @param expression 表达式 + * @param defaultValue 默认值 + * @return 对象 + * @see JSON#getByPath(String) + * @since 5.6.0 + */ + @SuppressWarnings("unchecked") + public static T getByPath(JSON json, String expression, T defaultValue) { + if ((null == json || StrUtil.isBlank(expression))) { + return defaultValue; + } + + if (null != defaultValue) { + final Class type = (Class) defaultValue.getClass(); + return ObjectUtil.defaultIfNull(json.getByPath(expression, type), defaultValue); + } + return (T) json.getByPath(expression); + } + + /** + * 设置表达式指定位置(或filed对应)的值
+ * 若表达式指向一个JSONArray则设置其坐标对应位置的值,若指向JSONObject则put对应key的值
+ * 注意:如果为JSONArray,则设置值得下标不能大于已有JSONArray的长度
+ *
    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ * + * @param json JSON,可以为JSONObject或JSONArray + * @param expression 表达式 + * @param value 值 + */ + public static void putByPath(JSON json, String expression, Object value) { + json.putByPath(expression, value); + } + + /** + * 对所有双引号做转义处理(使用双反斜杠做转义)
+ * 为了能在HTML中较好的显示,会将</转义为<\/
+ * JSON字符串中不能包含控制字符和未经转义的引号和反斜杠 + * + * @param string 字符串 + * @return 适合在JSON中显示的字符串 + */ + public static String quote(String string) { + return quote(string, true); + } + + /** + * 对所有双引号做转义处理(使用双反斜杠做转义)
+ * 为了能在HTML中较好的显示,会将</转义为<\/
+ * JSON字符串中不能包含控制字符和未经转义的引号和反斜杠 + * + * @param string 字符串 + * @param isWrap 是否使用双引号包装字符串 + * @return 适合在JSON中显示的字符串 + * @since 3.3.1 + */ + public static String quote(String string, boolean isWrap) { + StringWriter sw = new StringWriter(); + try { + return quote(string, sw, isWrap).toString(); + } catch (IOException ignored) { + // will never happen - we are writing to a string writer + return StrUtil.EMPTY; + } + } + + /** + * 对所有双引号做转义处理(使用双反斜杠做转义)
+ * 为了能在HTML中较好的显示,会将</转义为<\/
+ * JSON字符串中不能包含控制字符和未经转义的引号和反斜杠 + * + * @param str 字符串 + * @param writer Writer + * @return Writer + * @throws IOException IO异常 + */ + public static Writer quote(String str, Writer writer) throws IOException { + return quote(str, writer, true); + } + + /** + * 对所有双引号做转义处理(使用双反斜杠做转义)
+ * 为了能在HTML中较好的显示,会将</转义为<\/
+ * JSON字符串中不能包含控制字符和未经转义的引号和反斜杠 + * + * @param str 字符串 + * @param writer Writer + * @param isWrap 是否使用双引号包装字符串 + * @return Writer + * @throws IOException IO异常 + * @since 3.3.1 + */ + public static Writer quote(String str, Writer writer, boolean isWrap) throws IOException { + if (StrUtil.isEmpty(str)) { + if (isWrap) { + writer.write("\"\""); + } + return writer; + } + + char c; // 当前字符 + int len = str.length(); + if (isWrap) { + writer.write('"'); + } + for (int i = 0; i < len; i++) { + c = str.charAt(i); + switch (c) { + case '\\': + case '"': + writer.write("\\"); + writer.write(c); + break; + default: + writer.write(escape(c)); + } + } + if (isWrap) { + writer.write('"'); + } + return writer; + } + + /** + * 转义显示不可见字符 + * + * @param str 字符串 + * @return 转义后的字符串 + */ + public static String escape(String str) { + if (StrUtil.isEmpty(str)) { + return str; + } + + final int len = str.length(); + final StringBuilder builder = new StringBuilder(len); + char c; + for (int i = 0; i < len; i++) { + c = str.charAt(i); + builder.append(escape(c)); + } + return builder.toString(); + } + + /** + * 在需要的时候包装对象
+ * 包装包括: + *
    + *
  • {@code null} =》 {@code JSONNull.NULL}
  • + *
  • array or collection =》 JSONArray
  • + *
  • map =》 JSONObject
  • + *
  • standard property (Double, String, et al) =》 原对象
  • + *
  • 来自于java包 =》 字符串
  • + *
  • 其它 =》 尝试包装为JSONObject,否则返回{@code null}
  • + *
+ * + * @param object 被包装的对象 + * @param jsonConfig JSON选项 + * @return 包装后的值,null表示此值需被忽略 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static Object wrap(Object object, JSONConfig jsonConfig) { + if (object == null) { + return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; + } + if (object instanceof JSON // + || ObjectUtil.isNull(object) // + || object instanceof JSONString // + || object instanceof CharSequence // + || object instanceof Number // + || ObjectUtil.isBasicType(object) // + ) { + return object; + } + + try { + // fix issue#1399@Github + if (object instanceof SQLException) { + return object.toString(); + } + + // JSONArray + if (object instanceof Iterable || ArrayUtil.isArray(object)) { + return new JSONArray(object, jsonConfig); + } + // JSONObject + if (object instanceof Map || object instanceof Map.Entry) { + return new JSONObject(object, jsonConfig); + } + + // 日期类型原样保存,便于格式化 + if (object instanceof Date + || object instanceof Calendar + || object instanceof TemporalAccessor + ) { + return object; + } + // 枚举类保存其字符串形式(4.0.2新增) + if (object instanceof Enum) { + return object.toString(); + } + + // Java内部类不做转换 + if (ClassUtil.isJdkClass(object.getClass())) { + return object.toString(); + } + + // 默认按照JSONObject对待 + return new JSONObject(object, jsonConfig); + } catch (final Exception exception) { + return null; + } + } + + /** + * 格式化JSON字符串,此方法并不严格检查JSON的格式正确与否 + * + * @param jsonStr JSON字符串 + * @return 格式化后的字符串 + * @since 3.1.2 + */ + public static String formatJsonStr(String jsonStr) { + return JSONStrFormatter.format(jsonStr); + } + + /** + * 是否为JSON字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} + */ + @Deprecated + public static boolean isJson(String str) { + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); + } + + /** + * 是否为JSONObject字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} + */ + @Deprecated + public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { + if (StrUtil.isBlank(str)) { + return false; + } + return StrUtil.isWrap(StrUtil.trim(str), '{', '}'); + } + + /** + * 是否为JSONArray字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} + */ + @Deprecated + public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { + if (StrUtil.isBlank(str)) { + return false; + } + return StrUtil.isWrap(StrUtil.trim(str), '[', ']'); + } + + /** + * 是否为null对象,null的情况包括: + * + *
+	 * 1. {@code null}
+	 * 2. {@link JSONNull}
+	 * 
+ * + * @param obj 对象 + * @return 是否为null + * @since 4.5.7 + */ + public static boolean isNull(Object obj) { + return null == obj || obj instanceof JSONNull; + } + + /** + * XML转JSONObject
+ * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * + * @param xml XML字符串 + * @return JSONObject + * @since 4.0.8 + */ + public static JSONObject xmlToJson(String xml) { + return XML.toJSONObject(xml); + } + + /** + * 加入自定义的序列化器 + * + * @param type 对象类型 + * @param serializer 序列化器实现 + * @see GlobalSerializeMapping#put(Type, JSONArraySerializer) + * @since 4.6.5 + */ + public static void putSerializer(Type type, JSONArraySerializer serializer) { + GlobalSerializeMapping.put(type, serializer); + } + + /** + * 加入自定义的序列化器 + * + * @param type 对象类型 + * @param serializer 序列化器实现 + * @see GlobalSerializeMapping#put(Type, JSONObjectSerializer) + * @since 4.6.5 + */ + public static void putSerializer(Type type, JSONObjectSerializer serializer) { + GlobalSerializeMapping.put(type, serializer); + } + + /** + * 加入自定义的反序列化器 + * + * @param type 对象类型 + * @param deserializer 反序列化器实现 + * @see GlobalSerializeMapping#put(Type, JSONDeserializer) + * @since 4.6.5 + */ + public static void putDeserializer(Type type, JSONDeserializer deserializer) { + GlobalSerializeMapping.put(type, deserializer); + } + + // --------------------------------------------------------------------------------------------- Private method start + + /** + * 转义不可见字符
+ * 见:https://en.wikibooks.org/wiki/Unicode/Character_reference/0000-0FFF + * + * @param c 字符 + * @return 转义后的字符串 + */ + private static String escape(char c) { + switch (c) { + case '\b': + return "\\b"; + case '\t': + return "\\t"; + case '\n': + return "\\n"; + case '\f': + return "\\f"; + case '\r': + return "\\r"; + default: + if (c < StrUtil.C_SPACE || // + (c >= '\u0080' && c <= '\u00a0') || // + (c >= '\u2000' && c <= '\u2010') || // + (c >= '\u2028' && c <= '\u202F') || // + (c >= '\u2066' && c <= '\u206F')// + ) { + return HexUtil.toUnicodeHex(c); + } else { + return Character.toString(c); + } + } + } + // --------------------------------------------------------------------------------------------- Private method end +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/ObjectMapper.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/ObjectMapper.java new file mode 100755 index 0000000..0831e55 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/ObjectMapper.java @@ -0,0 +1,263 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.bean.BeanUtil; +import aiyh.utils.tool.cn.hutool.core.collection.ArrayIter; +import aiyh.utils.tool.cn.hutool.core.convert.Convert; +import aiyh.utils.tool.cn.hutool.core.io.IoUtil; +import aiyh.utils.tool.cn.hutool.core.lang.Filter; +import aiyh.utils.tool.cn.hutool.core.lang.mutable.Mutable; +import aiyh.utils.tool.cn.hutool.core.lang.mutable.MutablePair; +import aiyh.utils.tool.cn.hutool.core.util.ArrayUtil; +import aiyh.utils.tool.cn.hutool.core.util.StrUtil; +import aiyh.utils.tool.cn.hutool.core.util.TypeUtil; +import aiyh.utils.tool.cn.hutool.json.serialize.GlobalSerializeMapping; +import aiyh.utils.tool.cn.hutool.json.serialize.JSONObjectSerializer; +import aiyh.utils.tool.cn.hutool.json.serialize.JSONSerializer; + +import java.io.InputStream; +import java.io.Reader; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * 对象和JSON映射器,用于转换对象为JSON,支持: + *
    + *
  • Map 转 JSONObject,将键值对加入JSON对象
  • + *
  • Map.Entry 转 JSONObject
  • + *
  • CharSequence 转 JSONObject,使用JSONTokener解析
  • + *
  • {@link Reader} 转 JSONObject,使用JSONTokener解析
  • + *
  • {@link InputStream} 转 JSONObject,使用JSONTokener解析
  • + *
  • JSONTokener 转 JSONObject,直接解析
  • + *
  • ResourceBundle 转 JSONObject
  • + *
  • Bean 转 JSONObject,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • + *
+ * + * @author looly + * @since 5.8.0 + */ +public class ObjectMapper { + + /** + * 创建ObjectMapper + * + * @param source 来源对象 + * @return ObjectMapper + */ + public static ObjectMapper of(Object source) { + return new ObjectMapper(source); + } + + private final Object source; + + /** + * 构造 + * + * @param source 来源对象 + */ + public ObjectMapper(Object source) { + this.source = source; + } + + /** + * 将给定对象转换为{@link JSONObject} + * + * @param jsonObject 目标{@link JSONObject} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public void map(JSONObject jsonObject, Filter> filter) { + final Object source = this.source; + if (null == source) { + return; + } + + // 自定义序列化 + final JSONSerializer serializer = GlobalSerializeMapping.getSerializer(source.getClass()); + if (serializer instanceof JSONObjectSerializer) { + serializer.serialize(jsonObject, source); + return; + } + + if (source instanceof JSONArray) { + // 不支持集合类型转换为JSONObject + throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass()); + } + + if (source instanceof Map) { + // Map + for (final Map.Entry e : ((Map) source).entrySet()) { + jsonObject.set(Convert.toStr(e.getKey()), e.getValue(), filter, jsonObject.getConfig().isCheckDuplicate()); + } + } else if (source instanceof Map.Entry) { + final Map.Entry entry = (Map.Entry) source; + jsonObject.set(Convert.toStr(entry.getKey()), entry.getValue(), filter, jsonObject.getConfig().isCheckDuplicate()); + } else if (source instanceof CharSequence) { + // 可能为JSON字符串 + mapFromStr((CharSequence) source, jsonObject, filter); + } else if (source instanceof Reader) { + mapFromTokener(new JSONTokener((Reader) source, jsonObject.getConfig()), jsonObject, filter); + } else if (source instanceof InputStream) { + mapFromTokener(new JSONTokener((InputStream) source, jsonObject.getConfig()), jsonObject, filter); + } else if (source instanceof byte[]) { + mapFromTokener(new JSONTokener(IoUtil.toStream((byte[]) source), jsonObject.getConfig()), jsonObject, filter); + } else if (source instanceof JSONTokener) { + // JSONTokener + mapFromTokener((JSONTokener) source, jsonObject, filter); + } else if (source instanceof ResourceBundle) { + // JSONTokener + mapFromResourceBundle((ResourceBundle) source, jsonObject, filter); + } else if (BeanUtil.isReadableBean(source.getClass())) { + // 普通Bean + // TODO 过滤器对Bean无效,需补充。 + mapFromBean(source, jsonObject); + } else { + // 不支持对象类型转换为JSONObject + throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass()); + } + } + + /** + * 初始化 + * + * @param jsonArray 目标{@link JSONArray} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + * @throws JSONException 非数组或集合 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public void map(JSONArray jsonArray, Filter> filter) throws JSONException { + final Object source = this.source; + if (null == source) { + return; + } + + final JSONSerializer serializer = GlobalSerializeMapping.getSerializer(source.getClass()); + if (null != serializer && JSONArray.class.equals(TypeUtil.getTypeArgument(serializer.getClass()))) { + // 自定义序列化 + serializer.serialize(jsonArray, source); + } else if (source instanceof CharSequence) { + // JSON字符串 + mapFromStr((CharSequence) source, jsonArray, filter); + } else if (source instanceof Reader) { + mapFromTokener(new JSONTokener((Reader) source, jsonArray.getConfig()), jsonArray, filter); + } else if (source instanceof InputStream) { + mapFromTokener(new JSONTokener((InputStream) source, jsonArray.getConfig()), jsonArray, filter); + } else if (source instanceof byte[]) { + final byte[] bytesSource = (byte[]) source; + // 如果是普通的的byte[], 要避免下标越界 + if (bytesSource.length > 1 && '[' == bytesSource[0] && ']' == bytesSource[bytesSource.length - 1]) { + mapFromTokener(new JSONTokener(IoUtil.toStream(bytesSource), jsonArray.getConfig()), jsonArray, filter); + } else { + // https://github.com/dromara/hutool/issues/2369 + // 非标准的二进制流,则按照普通数组对待 + for (final byte b : bytesSource) { + jsonArray.add(b); + } + } + } else if (source instanceof JSONTokener) { + mapFromTokener((JSONTokener) source, jsonArray, filter); + } else { + Iterator iter; + if (ArrayUtil.isArray(source)) {// 数组 + iter = new ArrayIter<>(source); + } else if (source instanceof Iterator) {// Iterator + iter = ((Iterator) source); + } else if (source instanceof Iterable) {// Iterable + iter = ((Iterable) source).iterator(); + } else { + throw new JSONException("JSONArray initial value should be a string or collection or array."); + } + + final JSONConfig config = jsonArray.getConfig(); + Object next; + while (iter.hasNext()) { + next = iter.next(); + // 检查循环引用 + if (next != source) { + jsonArray.addRaw(JSONUtil.wrap(next, config), filter); + } + } + } + } + + /** + * 从{@link ResourceBundle}转换 + * + * @param bundle ResourceBundle + * @param jsonObject {@link JSONObject} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @since 5.3.1 + */ + private static void mapFromResourceBundle(ResourceBundle bundle, JSONObject jsonObject, Filter> filter) { + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + if (key != null) { + InternalJSONUtil.propertyPut(jsonObject, key, bundle.getString(key), filter); + } + } + } + + /** + * 从字符串转换 + * + * @param source JSON字符串 + * @param jsonObject {@link JSONObject} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + */ + private static void mapFromStr(CharSequence source, JSONObject jsonObject, Filter> filter) { + final String jsonStr = StrUtil.trim(source); + if (StrUtil.startWith(jsonStr, '<')) { + // 可能为XML + XML.toJSONObject(jsonObject, jsonStr, false); + return; + } + mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonObject.getConfig()), jsonObject, filter); + } + + /** + * 初始化 + * + * @param source JSON字符串 + * @param jsonArray {@link JSONArray} + * @param filter 值过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + */ + private void mapFromStr(CharSequence source, JSONArray jsonArray, Filter> filter) { + if (null != source) { + mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonArray.getConfig()), jsonArray, filter); + } + } + + /** + * 从{@link JSONTokener}转换 + * + * @param x JSONTokener + * @param jsonObject {@link JSONObject} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作 + */ + private static void mapFromTokener(JSONTokener x, JSONObject jsonObject, Filter> filter) { + JSONParser.of(x).parseTo(jsonObject, filter); + } + + /** + * 初始化 + * + * @param x {@link JSONTokener} + * @param jsonArray {@link JSONArray} + * @param filter 值过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + */ + private static void mapFromTokener(JSONTokener x, JSONArray jsonArray, Filter> filter) { + JSONParser.of(x).parseTo(jsonArray, filter); + } + + /** + * 从Bean转换 + * + * @param bean Bean对象 + * @param jsonObject {@link JSONObject} + */ + private static void mapFromBean(Object bean, JSONObject jsonObject) { + BeanUtil.beanToMap(bean, jsonObject, InternalJSONUtil.toCopyOptions(jsonObject.getConfig())); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/XML.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/XML.java new file mode 100644 index 0000000..6f1ef8c --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/XML.java @@ -0,0 +1,140 @@ +package aiyh.utils.tool.cn.hutool.json; + +import aiyh.utils.tool.cn.hutool.core.util.CharUtil; +import aiyh.utils.tool.cn.hutool.json.xml.JSONXMLParser; +import aiyh.utils.tool.cn.hutool.json.xml.JSONXMLSerializer; + +/** + * 提供静态方法在XML和JSONObject之间转换 + * + * @author JSON.org, looly + * @see JSONXMLParser + * @see JSONXMLSerializer + */ +public class XML { + + /** + * The Character '&'. + */ + public static final Character AMP = CharUtil.AMP; + + /** + * The Character '''. + */ + public static final Character APOS = CharUtil.SINGLE_QUOTE; + + /** + * The Character '!'. + */ + public static final Character BANG = '!'; + + /** + * The Character '='. + */ + public static final Character EQ = '='; + + /** + * The Character '>'. + */ + public static final Character GT = '>'; + + /** + * The Character '<'. + */ + public static final Character LT = '<'; + + /** + * The Character '?'. + */ + public static final Character QUEST = '?'; + + /** + * The Character '"'. + */ + public static final Character QUOT = CharUtil.DOUBLE_QUOTES; + + /** + * The Character '/'. + */ + public static final Character SLASH = CharUtil.SLASH; + + /** + * 转换XML为JSONObject + * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * Content text may be placed in a "content" member. Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored. + * + * @param string The source string. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(String string) throws JSONException { + return toJSONObject(string, false); + } + + /** + * 转换XML为JSONObject + * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * Content text may be placed in a "content" member. Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored. + * All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document. + * + * @param string The source string. + * @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { + return toJSONObject(new JSONObject(), string, keepStrings); + } + + /** + * 转换XML为JSONObject + * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * + * @param jo JSONObject + * @param xmlStr XML字符串 + * @param keepStrings 如果为{@code true},则值保持String类型,不转换为数字或boolean + * @return A JSONObject 解析后的JSON对象,与传入的jo为同一对象 + * @throws JSONException 解析异常 + * @since 5.3.1 + */ + public static JSONObject toJSONObject(JSONObject jo, String xmlStr, boolean keepStrings) throws JSONException { + JSONXMLParser.parseJSONObject(jo, xmlStr, keepStrings); + return jo; + } + + /** + * 转换JSONObject为XML + * + * @param object JSON对象或数组 + * @return XML字符串 + * @throws JSONException JSON解析异常 + */ + public static String toXml(Object object) throws JSONException { + return toXml(object, null); + } + + /** + * 转换JSONObject为XML + * + * @param object JSON对象或数组 + * @param tagName 可选标签名称,名称为空时忽略标签 + * @return A string. + * @throws JSONException JSON解析异常 + */ + public static String toXml(Object object, String tagName) throws JSONException { + return toXml(object, tagName, "content"); + } + + /** + * 转换JSONObject为XML + * + * @param object JSON对象或数组 + * @param tagName 可选标签名称,名称为空时忽略标签 + * @param contentKeys 标识为内容的key,遇到此key直接解析内容而不增加对应名称标签 + * @return A string. + * @throws JSONException JSON解析异常 + */ + public static String toXml(Object object, String tagName, String... contentKeys) throws JSONException { + return JSONXMLSerializer.toXml(object, tagName, contentKeys); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/XMLTokener.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/XMLTokener.java new file mode 100644 index 0000000..b69da95 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/XMLTokener.java @@ -0,0 +1,362 @@ +package aiyh.utils.tool.cn.hutool.json; + + +/** + * XML分析器,继承自JSONTokener,提供XML的语法分析 + * + * @author JSON.org + */ +public class XMLTokener extends JSONTokener { + + /** + * The table of entity values. + * It initially contains Character values for amp, apos, gt, lt, quot. + */ + public static final java.util.HashMap entity; + + static { + entity = new java.util.HashMap<>(8); + entity.put("amp", XML.AMP); + entity.put("apos", XML.APOS); + entity.put("gt", XML.GT); + entity.put("lt", XML.LT); + entity.put("quot", XML.QUOT); + } + + /** + * Construct an XMLTokener from a string. + * + * @param s A source string. + * @param config JSON配置 + */ + public XMLTokener(CharSequence s, JSONConfig config) { + super(s, config); + } + + /** + * Get the text in the CDATA block. + * + * @return The string up to the {@code ]]>}. + * @throws JSONException If the {@code ]]>} is not found. + */ + public String nextCDATA() throws JSONException { + char c; + int i; + final StringBuilder sb = new StringBuilder(); + for (; ; ) { + c = next(); + if (end()) { + throw syntaxError("Unclosed CDATA"); + } + sb.append(c); + i = sb.length() - 3; + if (i >= 0 && sb.charAt(i) == ']' && sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') { + sb.setLength(i); + return sb.toString(); + } + } + } + + /** + * Get the next XML outer token, trimming whitespace. + * There are two kinds of tokens: the '>' character which begins a markup tag, and the content text between markup tags. + * + * @return A string, or a '>' Character, or null if there is no more source text. + * @throws JSONException JSON + */ + public Object nextContent() throws JSONException { + char c; + final StringBuilder sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == 0) { + return null; + } + if (c == '<') { + return XML.LT; + } + sb = new StringBuilder(); + for (; ; ) { + if (c == '<' || c == 0) { + back(); + return sb.toString().trim(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + c = next(); + } + } + + /** + * Return the next entity. These entities are translated to Characters: {@code & ' > < "}. + * + * @param ampersand An ampersand character. + * @return A Character or an entity String if the entity is not recognized. + * @throws JSONException If missing ';' in XML entity. + */ + public Object nextEntity(char ampersand) throws JSONException { + final StringBuilder sb = new StringBuilder(); + char c; + for (; ; ) { + c = next(); + if (Character.isLetterOrDigit(c) || c == '#') { + sb.append(Character.toLowerCase(c)); + } else if (c == ';') { + break; + } else { + throw syntaxError("Missing ';' in XML entity: &" + sb); + } + } + return unescapeEntity(sb.toString()); + } + + /** + * Unescape an XML entity encoding; + * + * @param e entity (only the actual entity value, not the preceding & or ending ; + * @return Unescape str + */ + static String unescapeEntity(final String e) { + // validate + if (e == null || e.isEmpty()) { + return ""; + } + // if our entity is an encoded unicode point, parse it. + if (e.charAt(0) == '#') { + final int cp; + if (e.charAt(1) == 'x' || e.charAt(1) == 'X') { + // hex encoded unicode + cp = Integer.parseInt(e.substring(2), 16); + } else { + // decimal encoded unicode + cp = Integer.parseInt(e.substring(1)); + } + return new String(new int[]{cp}, 0, 1); + } + final Character knownEntity = entity.get(e); + if (knownEntity == null) { + // we don't know the entity so keep it encoded + return '&' + e + ';'; + } + return knownEntity.toString(); + } + + /** + * Returns the next XML meta token. This is used for skipping over <!...> and <?...?> structures. + * + * @return Syntax characters ({@code < > / = ! ?}) are returned as Character, and strings and names are returned as Boolean. We don't care what the values actually are. + * @throws JSONException 字符串中属性未关闭或XML结构错误抛出此异常。If a string is not properly closed or if the XML is badly structured. + */ + public Object nextMeta() throws JSONException { + char c; + char q; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped meta tag"); + case '<': + return XML.LT; + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + case '"': + case '\'': + q = c; + for (; ; ) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return Boolean.TRUE; + } + } + default: + for (; ; ) { + c = next(); + if (Character.isWhitespace(c)) { + return Boolean.TRUE; + } + switch (c) { + case 0: + case '<': + case '>': + case '/': + case '=': + case '!': + case '?': + case '"': + case '\'': + back(); + return Boolean.TRUE; + } + } + } + } + + /** + * Get the next XML Token. These tokens are found inside of angle brackets.
+ * It may be one of these characters: {@code / > = ! ?} or it may be a string wrapped in single quotes or double + * quotes, or it may be a name. + * + * @return a String or a Character. + * @throws JSONException If the XML is not well formed. + */ + public Object nextToken() throws JSONException { + char c; + char q; + StringBuilder sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped element"); + case '<': + throw syntaxError("Misplaced '<'"); + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + + // Quoted string + + case '"': + case '\'': + q = c; + sb = new StringBuilder(); + for (; ; ) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return sb.toString(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + } + default: + + // Name + + sb = new StringBuilder(); + for (; ; ) { + sb.append(c); + c = next(); + if (Character.isWhitespace(c)) { + return sb.toString(); + } + switch (c) { + case 0: + return sb.toString(); + case '>': + case '/': + case '=': + case '!': + case '?': + case '[': + case ']': + back(); + return sb.toString(); + case '<': + case '"': + case '\'': + throw syntaxError("Bad character in a name"); + } + } + } + } + + /** + * Skip characters until past the requested string. If it is not found, we are left at the end of the source with a result of false. + * + * @param to A string to skip past. + * @return 是否成功skip + * @throws JSONException JSON异常 + */ + public boolean skipPast(String to) throws JSONException { + boolean b; + char c; + int i; + int j; + int offset = 0; + int length = to.length(); + char[] circle = new char[length]; + + /* + * First fill the circle buffer with as many characters as are in the to string. If we reach an early end, bail. + */ + + for (i = 0; i < length; i += 1) { + c = next(); + if (c == 0) { + return false; + } + circle[i] = c; + } + + /* We will loop, possibly for all of the remaining characters. */ + + for (; ; ) { + j = offset; + b = true; + + /* Compare the circle buffer with the to string. */ + + for (i = 0; i < length; i += 1) { + if (circle[j] != to.charAt(i)) { + b = false; + break; + } + j += 1; + if (j >= length) { + j -= length; + } + } + + /* If we exit the loop with b intact, then victory is ours. */ + + if (b) { + return true; + } + + /* Get the next character. If there isn't one, then defeat is ours. */ + + c = next(); + if (c == 0) { + return false; + } + /* + * Shove the character in the circle buffer and advance the circle offset. The offset is mod n. + */ + circle[offset] = c; + offset += 1; + if (offset >= length) { + offset -= length; + } + } + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/package-info.java new file mode 100644 index 0000000..ee6b762 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/package-info.java @@ -0,0 +1,6 @@ +/** + * JSON封装,基于json.org官方库改造 + * + * @author looly + */ +package aiyh.utils.tool.cn.hutool.json; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/GlobalSerializeMapping.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/GlobalSerializeMapping.java new file mode 100644 index 0000000..173ff49 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -0,0 +1,112 @@ +package aiyh.utils.tool.cn.hutool.json.serialize; + +import aiyh.utils.tool.cn.hutool.core.map.SafeConcurrentHashMap; +import aiyh.utils.tool.cn.hutool.json.JSON; + +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 全局的序列化和反序列化器映射
+ * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 + * + * @author Looly + */ +public class GlobalSerializeMapping { + + private static Map> serializerMap; + private static Map> deserializerMap; + + static { + serializerMap = new SafeConcurrentHashMap<>(); + deserializerMap = new SafeConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + + /** + * 加入自定义的序列化器 + * + * @param type 对象类型 + * @param serializer 序列化器实现 + */ + public static void put(Type type, JSONArraySerializer serializer) { + putInternal(type, serializer); + } + + /** + * 加入自定义的序列化器 + * + * @param type 对象类型 + * @param serializer 序列化器实现 + */ + public static void put(Type type, JSONObjectSerializer serializer) { + putInternal(type, serializer); + } + + /** + * 加入自定义的序列化器 + * + * @param type 对象类型 + * @param serializer 序列化器实现 + */ + synchronized private static void putInternal(Type type, JSONSerializer serializer) { + if (null == serializerMap) { + serializerMap = new SafeConcurrentHashMap<>(); + } + serializerMap.put(type, serializer); + } + + /** + * 加入自定义的反序列化器 + * + * @param type 对象类型 + * @param deserializer 反序列化器实现 + */ + synchronized public static void put(Type type, JSONDeserializer deserializer) { + if (null == deserializerMap) { + deserializerMap = new ConcurrentHashMap<>(); + } + deserializerMap.put(type, deserializer); + } + + /** + * 获取自定义的序列化器,如果未定义返回{@code null} + * + * @param type 类型 + * @return 自定义的序列化器或者{@code null} + */ + public static JSONSerializer getSerializer(Type type) { + if (null == serializerMap) { + return null; + } + return serializerMap.get(type); + } + + /** + * 获取自定义的反序列化器,如果未定义返回{@code null} + * + * @param type 类型 + * @return 自定义的反序列化器或者{@code null} + */ + public static JSONDeserializer getDeserializer(Type type) { + if (null == deserializerMap) { + return null; + } + return deserializerMap.get(type); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONArraySerializer.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONArraySerializer.java new file mode 100644 index 0000000..a1a0989 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONArraySerializer.java @@ -0,0 +1,14 @@ +package aiyh.utils.tool.cn.hutool.json.serialize; + + +import aiyh.utils.tool.cn.hutool.json.JSONArray; + +/** + * JSON列表的序列化接口,用于将特定对象序列化为{@link JSONArray} + * + * @param 对象类型 + * @author Looly + */ +@FunctionalInterface +public interface JSONArraySerializer extends JSONSerializer { +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONDeserializer.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONDeserializer.java new file mode 100644 index 0000000..5ebaf98 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONDeserializer.java @@ -0,0 +1,22 @@ +package aiyh.utils.tool.cn.hutool.json.serialize; + + +import aiyh.utils.tool.cn.hutool.json.JSON; + +/** + * JSON反序列话自定义实现类 + * + * @param 反序列化后的类型 + * @author Looly + */ +@FunctionalInterface +public interface JSONDeserializer { + + /** + * 反序列化,通过实现此方法,自定义实现JSON转换为指定类型的逻辑 + * + * @param json {@link JSON} + * @return 目标对象 + */ + T deserialize(JSON json); +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONObjectSerializer.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONObjectSerializer.java new file mode 100644 index 0000000..e94b14f --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONObjectSerializer.java @@ -0,0 +1,14 @@ +package aiyh.utils.tool.cn.hutool.json.serialize; + + +import aiyh.utils.tool.cn.hutool.json.JSONObject; + +/** + * 对象的序列化接口,用于将特定对象序列化为{@link JSONObject} + * + * @param 对象类型 + * @author Looly + */ +@FunctionalInterface +public interface JSONObjectSerializer extends JSONSerializer { +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONSerializer.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONSerializer.java new file mode 100644 index 0000000..3d29428 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONSerializer.java @@ -0,0 +1,24 @@ +package aiyh.utils.tool.cn.hutool.json.serialize; + + +import aiyh.utils.tool.cn.hutool.json.JSON; + +/** + * 序列化接口,通过实现此接口,实现自定义的对象转换为JSON的操作 + * + * @param JSON类型,可以是JSONObject或者JSONArray + * @param 对象类型 + * @author Looly + */ +@FunctionalInterface +public interface JSONSerializer { + + /** + * 序列化实现,通过实现此方法,将指定类型的对象转换为{@link JSON}对象
+ * 转换后的对象可以为JSONObject也可以为JSONArray,首先new一个空的JSON,然后将需要的数据字段put到JSON对象中去即可。 + * + * @param json JSON,可以为JSONObject或者JSONArray + * @param bean 指定类型对象 + */ + void serialize(T json, V bean); +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONWriter.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONWriter.java new file mode 100755 index 0000000..684ed45 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/JSONWriter.java @@ -0,0 +1,431 @@ +package aiyh.utils.tool.cn.hutool.json.serialize; + +import aiyh.utils.tool.cn.hutool.core.convert.Convert; +import aiyh.utils.tool.cn.hutool.core.date.DateUtil; +import aiyh.utils.tool.cn.hutool.core.date.TemporalAccessorUtil; +import aiyh.utils.tool.cn.hutool.core.date.format.GlobalCustomFormat; +import aiyh.utils.tool.cn.hutool.core.io.IORuntimeException; +import aiyh.utils.tool.cn.hutool.core.lang.Filter; +import aiyh.utils.tool.cn.hutool.core.lang.mutable.MutablePair; +import aiyh.utils.tool.cn.hutool.core.util.ArrayUtil; +import aiyh.utils.tool.cn.hutool.core.util.CharUtil; +import aiyh.utils.tool.cn.hutool.core.util.NumberUtil; +import aiyh.utils.tool.cn.hutool.core.util.StrUtil; +import aiyh.utils.tool.cn.hutool.json.*; + +import java.io.IOException; +import java.io.Writer; +import java.time.MonthDay; +import java.time.temporal.TemporalAccessor; +import java.util.Calendar; +import java.util.Date; +import java.util.Iterator; +import java.util.Map; + +/** + * JSON数据写出器
+ * 通过简单的append方式将JSON的键值对等信息写出到{@link Writer}中。 + * + * @author looly + * @since 5.7.3 + */ +public class JSONWriter extends Writer { + + /** + * 缩进因子,定义每一级别增加的缩进量 + */ + private final int indentFactor; + /** + * 本级别缩进量 + */ + private final int indent; + /** + * Writer + */ + private final Writer writer; + /** + * JSON选项 + */ + private final JSONConfig config; + + /** + * 写出当前值是否需要分隔符 + */ + private boolean needSeparator; + /** + * 是否为JSONArray模式 + */ + private boolean arrayMode; + + /** + * 创建JSONWriter + * + * @param writer {@link Writer} + * @param indentFactor 缩进因子,定义每一级别增加的缩进量 + * @param indent 本级别缩进量 + * @param config JSON选项 + * @return JSONWriter + */ + public static JSONWriter of(Writer writer, int indentFactor, int indent, JSONConfig config) { + return new JSONWriter(writer, indentFactor, indent, config); + } + + /** + * 构造 + * + * @param writer {@link Writer} + * @param indentFactor 缩进因子,定义每一级别增加的缩进量 + * @param indent 本级别缩进量 + * @param config JSON选项 + */ + public JSONWriter(Writer writer, int indentFactor, int indent, JSONConfig config) { + this.writer = writer; + this.indentFactor = indentFactor; + this.indent = indent; + this.config = config; + } + + /** + * JSONObject写出开始,默认写出"{" + * + * @return this + */ + public JSONWriter beginObj() { + writeRaw(CharUtil.DELIM_START); + return this; + } + + /** + * JSONArray写出开始,默认写出"[" + * + * @return this + */ + public JSONWriter beginArray() { + writeRaw(CharUtil.BRACKET_START); + arrayMode = true; + return this; + } + + /** + * 结束,默认根据开始的类型,补充"}"或"]" + * + * @return this + */ + public JSONWriter end() { + // 换行缩进 + writeLF().writeSpace(indent); + writeRaw(arrayMode ? CharUtil.BRACKET_END : CharUtil.DELIM_END); + flush(); + arrayMode = false; + // 当前对象或数组结束,当新的 + needSeparator = true; + return this; + } + + /** + * 写出键,自动处理分隔符和缩进,并包装键名 + * + * @param key 键名 + * @return this + */ + public JSONWriter writeKey(String key) { + if (needSeparator) { + writeRaw(CharUtil.COMMA); + } + // 换行缩进 + writeLF().writeSpace(indentFactor + indent); + return writeRaw(JSONUtil.quote(key)); + } + + /** + * 写出值,自动处理分隔符和缩进,自动判断类型,并根据不同类型写出特定格式的值
+ * 如果写出的值为{@code null}或者{@link JSONNull},且配置忽略null,则跳过。 + * + * @param value 值 + * @return this + */ + public JSONWriter writeValue(Object value) { + if (JSONUtil.isNull(value) && config.isIgnoreNullValue()) { + return this; + } + return writeValueDirect(value, null); + } + + /** + * 写出字段名及字段值,如果字段值是{@code null}且忽略null值,则不写出任何内容 + * + * @param key 字段名 + * @param value 字段值 + * @return this + * @since 5.7.6 + * @deprecated 请使用 {@link #writeField(MutablePair, Filter)} + */ + @Deprecated + public JSONWriter writeField(String key, Object value) { + if (JSONUtil.isNull(value) && config.isIgnoreNullValue()) { + return this; + } + return writeKey(key).writeValueDirect(value, null); + } + + /** + * 写出字段名及字段值,如果字段值是{@code null}且忽略null值,则不写出任何内容 + * + * @param pair 键值对 + * @param filter 键值对的过滤器,可以编辑键值对 + * @return this + * @since 5.8.6 + */ + public JSONWriter writeField(MutablePair pair, Filter> filter) { + if (JSONUtil.isNull(pair.getValue()) && config.isIgnoreNullValue()) { + return this; + } + + if (null == filter || filter.accept(pair)) { + if (!arrayMode) { + // JSONArray只写值,JSONObject写键值对 + writeKey(StrUtil.toString(pair.getKey())); + } + return writeValueDirect(pair.getValue(), filter); + } + return this; + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + this.writer.write(cbuf, off, len); + } + + @Override + public void flush() { + try { + this.writer.flush(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + @Override + public void close() throws IOException { + this.writer.close(); + } + + // ------------------------------------------------------------------------------ Private methods + + /** + * 写出值,自动处理分隔符和缩进,自动判断类型,并根据不同类型写出特定格式的值 + * + * @param value 值 + * @param filter 键值对过滤器 + * @return this + */ + private JSONWriter writeValueDirect(Object value, Filter> filter) { + if (arrayMode) { + if (needSeparator) { + writeRaw(CharUtil.COMMA); + } + // 换行缩进 + writeLF().writeSpace(indentFactor + indent); + } else { + writeRaw(CharUtil.COLON).writeSpace(1); + } + needSeparator = true; + return writeObjValue(value, filter); + } + + /** + * 写出JSON的值,根据值类型不同,输出不同内容 + * + * @param value 值 + * @param filter 过滤器 + * @return this + */ + private JSONWriter writeObjValue(Object value, Filter> filter) { + final int indent = indentFactor + this.indent; + if (value == null || value instanceof JSONNull) { + writeRaw(JSONNull.NULL.toString()); + } else if (value instanceof JSON) { + if (value instanceof JSONObject) { + ((JSONObject) value).write(writer, indentFactor, indent, filter); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer, indentFactor, indent, filter); + } + } else if (value instanceof Map || value instanceof Map.Entry) { + new JSONObject(value).write(writer, indentFactor, indent); + } else if (value instanceof Iterable || value instanceof Iterator || ArrayUtil.isArray(value)) { + new JSONArray(value).write(writer, indentFactor, indent); + } else if (value instanceof Number) { + writeNumberValue((Number) value); + } else if (value instanceof Date || value instanceof Calendar || value instanceof TemporalAccessor) { + // issue#2572@Github + if (value instanceof MonthDay) { + writeStrValue(value.toString()); + return this; + } + + final String format = (null == config) ? null : config.getDateFormat(); + writeRaw(formatDate(value, format)); + } else if (value instanceof Boolean) { + writeBooleanValue((Boolean) value); + } else if (value instanceof JSONString) { + writeJSONStringValue((JSONString) value); + } else { + writeStrValue(value.toString()); + } + + return this; + } + + /** + * 写出数字,根据{@link JSONConfig#isStripTrailingZeros()} 配置不同,写出不同数字
+ * 主要针对Double型是否去掉小数点后多余的0
+ * 此方法输出的值不包装引号。 + * + * @param number 数字 + */ + private void writeNumberValue(Number number) { + // since 5.6.2可配置是否去除末尾多余0,例如如果为true,5.0返回5 + final boolean isStripTrailingZeros = null == config || config.isStripTrailingZeros(); + writeRaw(NumberUtil.toStr(number, isStripTrailingZeros)); + } + + /** + * 写出Boolean值,直接写出true或false,不适用引号包装 + * + * @param value Boolean值 + */ + private void writeBooleanValue(Boolean value) { + writeRaw(value.toString()); + } + + /** + * 输出实现了{@link JSONString}接口的对象,通过调用{@link JSONString#toJSONString()}获取JSON字符串
+ * {@link JSONString}按照JSON对象对待,此方法输出的JSON字符串不包装引号。
+ * 如果toJSONString()返回null,调用toString()方法并使用双引号包装。 + * + * @param jsonString {@link JSONString} + */ + private void writeJSONStringValue(JSONString jsonString) { + String valueStr; + try { + valueStr = jsonString.toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (null != valueStr) { + writeRaw(valueStr); + } else { + writeStrValue(jsonString.toString()); + } + } + + /** + * 写出字符串值,并包装引号并转义字符
+ * 对所有双引号做转义处理(使用双反斜杠做转义)
+ * 为了能在HTML中较好的显示,会将</转义为<\/
+ * JSON字符串中不能包含控制字符和未经转义的引号和反斜杠 + * + * @param csq 字符串 + */ + private void writeStrValue(String csq) { + try { + JSONUtil.quote(csq, writer); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 写出空格 + * + * @param count 空格数 + */ + private void writeSpace(int count) { + if (indentFactor > 0) { + for (int i = 0; i < count; i++) { + writeRaw(CharUtil.SPACE); + } + } + } + + /** + * 写出换换行符 + * + * @return this + */ + private JSONWriter writeLF() { + if (indentFactor > 0) { + writeRaw(CharUtil.LF); + } + return this; + } + + /** + * 写入原始字符串值,不做任何处理 + * + * @param csq 字符串 + * @return this + */ + private JSONWriter writeRaw(String csq) { + try { + writer.append(csq); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + /** + * 写入原始字符值,不做任何处理 + * + * @param c 字符串 + * @return this + */ + private JSONWriter writeRaw(char c) { + try { + writer.write(c); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + /** + * 按照给定格式格式化日期,格式为空时返回时间戳字符串 + * + * @param dateObj Date或者Calendar对象 + * @param format 格式 + * @return 日期字符串 + */ + private static String formatDate(Object dateObj, String format) { + if (StrUtil.isNotBlank(format)) { + final String dateStr; + if (dateObj instanceof TemporalAccessor) { + dateStr = TemporalAccessorUtil.format((TemporalAccessor) dateObj, format); + } else { + dateStr = DateUtil.format(Convert.toDate(dateObj), format); + } + + if (GlobalCustomFormat.FORMAT_SECONDS.equals(format) + || GlobalCustomFormat.FORMAT_MILLISECONDS.equals(format)) { + // Hutool自定义的秒和毫秒表示,默认不包装双引号 + return dateStr; + } + // 用户定义了日期格式 + return JSONUtil.quote(dateStr); + } + + // 默认使用时间戳 + long timeMillis; + if (dateObj instanceof TemporalAccessor) { + timeMillis = TemporalAccessorUtil.toEpochMilli((TemporalAccessor) dateObj); + } else if (dateObj instanceof Date) { + timeMillis = ((Date) dateObj).getTime(); + } else if (dateObj instanceof Calendar) { + timeMillis = ((Calendar) dateObj).getTimeInMillis(); + } else { + throw new UnsupportedOperationException("Unsupported Date type: " + dateObj.getClass()); + } + return String.valueOf(timeMillis); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 0000000..0e1cc7c --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,76 @@ +package aiyh.utils.tool.cn.hutool.json.serialize; + + +import aiyh.utils.tool.cn.hutool.json.JSON; +import aiyh.utils.tool.cn.hutool.json.JSONException; +import aiyh.utils.tool.cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class temporalAccessorClass; + + public TemporalAccessorSerializer(Class temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/package-info.java new file mode 100644 index 0000000..59ed011 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/serialize/package-info.java @@ -0,0 +1,6 @@ +/** + * JSON自定义序列化和反序列化接口和默认实现 + * + * @author Looly + */ +package aiyh.utils.tool.cn.hutool.json.serialize; \ No newline at end of file diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/xml/JSONXMLParser.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/xml/JSONXMLParser.java new file mode 100644 index 0000000..0572a31 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/xml/JSONXMLParser.java @@ -0,0 +1,179 @@ +package aiyh.utils.tool.cn.hutool.json.xml; + + +import aiyh.utils.tool.cn.hutool.json.*; + +/** + * XML解析器,将XML解析为JSON对象 + * + * @author JSON.org, looly + * @since 5.7.11 + */ +public class JSONXMLParser { + + /** + * 转换XML为JSONObject + * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * + * @param jo JSONObject + * @param xmlStr XML字符串 + * @param keepStrings 如果为{@code true},则值保持String类型,不转换为数字或boolean + * @throws JSONException 解析异常 + */ + public static void parseJSONObject(JSONObject jo, String xmlStr, boolean keepStrings) throws JSONException { + XMLTokener x = new XMLTokener(xmlStr, jo.getConfig()); + while (x.more() && x.skipPast("<")) { + parse(x, jo, null, keepStrings); + } + } + + /** + * Scan the content following the named tag, attaching it to the context. + * + * @param x The XMLTokener containing the source string. + * @param context The JSONObject that will include the new material. + * @param name The tag name. + * @return true if the close tag is processed. + * @throws JSONException JSON异常 + */ + private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings) throws JSONException { + char c; + int i; + JSONObject jsonobject; + String string; + String tagName; + Object token; + + token = x.nextToken(); + + if (token == XML.BANG) { + c = x.next(); + if (c == '-') { + if (x.next() == '-') { + x.skipPast("-->"); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if ("CDATA".equals(token)) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (string.length() > 0) { + context.accumulate("content", string); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == XML.QUEST) { + + // "); + return false; + } else if (token == XML.SLASH) { + + // Close tag + if (x.nextToken() != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + if (jsonobject.size() > 0) { + context.accumulate(tagName, jsonobject); + } else { + context.accumulate(tagName, ""); + } + return false; + + } else if (token == XML.GT) { + // Content, between <...> and + for (; ; ) { + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } else if (token instanceof String) { + string = (String) token; + if (string.length() > 0) { + jsonobject.accumulate("content", keepStrings ? token : InternalJSONUtil.stringToValue(string)); + } + + } else if (token == XML.LT) { + // Nested element + if (parse(x, jsonobject, tagName, keepStrings)) { + if (jsonobject.size() == 0) { + context.accumulate(tagName, ""); + } else if (jsonobject.size() == 1 && jsonobject.get("content") != null) { + context.accumulate(tagName, jsonobject.get("content")); + } else { + context.accumulate(tagName, jsonobject); + } + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/xml/JSONXMLSerializer.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/xml/JSONXMLSerializer.java new file mode 100755 index 0000000..b6cc585 --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/xml/JSONXMLSerializer.java @@ -0,0 +1,159 @@ +package aiyh.utils.tool.cn.hutool.json.xml; + +import aiyh.utils.tool.cn.hutool.core.util.ArrayUtil; +import aiyh.utils.tool.cn.hutool.core.util.CharUtil; +import aiyh.utils.tool.cn.hutool.core.util.EscapeUtil; +import aiyh.utils.tool.cn.hutool.core.util.StrUtil; +import aiyh.utils.tool.cn.hutool.json.JSONArray; +import aiyh.utils.tool.cn.hutool.json.JSONException; +import aiyh.utils.tool.cn.hutool.json.JSONObject; + +/** + * JSON转XML字符串工具 + * + * @author looly + * @since 5.7.11 + */ +public class JSONXMLSerializer { + /** + * 转换JSONObject为XML + * Convert a JSONObject into a well-formed, element-normal XML string. + * + * @param object A JSONObject. + * @return A string. + * @throws JSONException Thrown if there is an error parsing the string + */ + public static String toXml(Object object) throws JSONException { + return toXml(object, null); + } + + /** + * 转换JSONObject为XML + * + * @param object JSON对象或数组 + * @param tagName 可选标签名称,名称为空时忽略标签 + * @return A string. + * @throws JSONException JSON解析异常 + */ + public static String toXml(Object object, String tagName) throws JSONException { + return toXml(object, tagName, "content"); + } + + /** + * 转换JSONObject为XML + * + * @param object JSON对象或数组 + * @param tagName 可选标签名称,名称为空时忽略标签 + * @param contentKeys 标识为内容的key,遇到此key直接解析内容而不增加对应名称标签 + * @return A string. + * @throws JSONException JSON解析异常 + */ + public static String toXml(Object object, String tagName, String... contentKeys) throws JSONException { + if (null == object) { + return null; + } + + final StringBuilder sb = new StringBuilder(); + if (object instanceof JSONObject) { + + // Emit + appendTag(sb, tagName, false); + + // Loop thru the keys. + ((JSONObject) object).forEach((key, value) -> { + if (ArrayUtil.isArray(value)) { + value = new JSONArray(value); + } + + // Emit content in body + if (ArrayUtil.contains(contentKeys, key)) { + if (value instanceof JSONArray) { + int i = 0; + for (Object val : (JSONArray) value) { + if (i > 0) { + sb.append(CharUtil.LF); + } + sb.append(EscapeUtil.escapeXml(val.toString())); + i++; + } + } else { + sb.append(EscapeUtil.escapeXml(value.toString())); + } + + // Emit an array of similar keys + + } else if (StrUtil.isEmptyIfStr(value)) { + sb.append(wrapWithTag(null, key)); + } else if (value instanceof JSONArray) { + for (Object val : (JSONArray) value) { + if (val instanceof JSONArray) { + sb.append(wrapWithTag(toXml(val, null, contentKeys), key)); + } else { + sb.append(toXml(val, key, contentKeys)); + } + } + } else { + sb.append(toXml(value, key, contentKeys)); + } + }); + + // Emit the close tag + appendTag(sb, tagName, true); + return sb.toString(); + } + + if (ArrayUtil.isArray(object)) { + object = new JSONArray(object); + } + + if (object instanceof JSONArray) { + for (Object val : (JSONArray) object) { + // XML does not have good support for arrays. If an array + // appears in a place where XML is lacking, synthesize an + // element. + sb.append(toXml(val, tagName == null ? "array" : tagName, contentKeys)); + } + return sb.toString(); + } + + return wrapWithTag(EscapeUtil.escapeXml(object.toString()), tagName); + } + + /** + * 追加标签 + * + * @param sb XML内容 + * @param tagName 标签名 + * @param isEndTag 是否结束标签 + * @since 5.7.11 + */ + private static void appendTag(StringBuilder sb, String tagName, boolean isEndTag) { + if (StrUtil.isNotBlank(tagName)) { + sb.append('<'); + if (isEndTag) { + sb.append('/'); + } + sb.append(tagName).append('>'); + } + } + + /** + * 将内容使用标签包装为XML + * + * @param tagName 标签名 + * @param content 内容 + * @return 包装后的XML + * @since 5.7.11 + */ + private static String wrapWithTag(String content, String tagName) { + if (StrUtil.isBlank(tagName)) { + return StrUtil.wrap(content, "\""); + } + + if (StrUtil.isEmpty(content)) { + return "<" + tagName + "/>"; + } else { + return "<" + tagName + ">" + content + ""; + } + } +} diff --git a/src/main/java/aiyh/utils/tool/cn/hutool/json/xml/package-info.java b/src/main/java/aiyh/utils/tool/cn/hutool/json/xml/package-info.java new file mode 100644 index 0000000..7fd780b --- /dev/null +++ b/src/main/java/aiyh/utils/tool/cn/hutool/json/xml/package-info.java @@ -0,0 +1,6 @@ +/** + * JSON与XML相互转换封装,基于json.org官方库改造 + * + * @author looly + */ +package aiyh.utils.tool.cn.hutool.json.xml; diff --git a/src/main/java/weaver/youhong/ai/intellectualproperty/action/CaElectronicSignatureAction.java b/src/main/java/weaver/youhong/ai/intellectualproperty/action/CaElectronicSignatureAction.java index 3bd5223..114b46a 100644 --- a/src/main/java/weaver/youhong/ai/intellectualproperty/action/CaElectronicSignatureAction.java +++ b/src/main/java/weaver/youhong/ai/intellectualproperty/action/CaElectronicSignatureAction.java @@ -91,8 +91,7 @@ public class CaElectronicSignatureAction extends SafeCusBaseAction { Map responseMap = responeVo.getResponseMap(); Map data = (Map) responseMap.get("data"); if (Objects.isNull(data)) { - Util.actionFail(requestInfo.getRequestManager(), URLDecoder.decode(Util.null2String(responseMap.get("ret_msg")), "UTF-8")); - return; + Util.actionFailException(requestInfo.getRequestManager(), URLDecoder.decode(Util.null2String(responseMap.get("ret_msg")), "UTF-8")); } String documentNo = Util.null2String(data.get("document_no")); String pdf = Util.null2String(data.get("ofd")); diff --git a/src/main/java/weaver/youhong/ai/intellectualproperty/cusgetvalue/GetOfdKeywordPageValue.java b/src/main/java/weaver/youhong/ai/intellectualproperty/cusgetvalue/GetOfdKeywordPageValue.java index d65c606..6c3d7fe 100644 --- a/src/main/java/weaver/youhong/ai/intellectualproperty/cusgetvalue/GetOfdKeywordPageValue.java +++ b/src/main/java/weaver/youhong/ai/intellectualproperty/cusgetvalue/GetOfdKeywordPageValue.java @@ -14,6 +14,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.Map; +import java.util.Objects; /** *

获取关键字页码

@@ -28,7 +29,11 @@ public class GetOfdKeywordPageValue implements CusInterfaceGetValue { @Override public Object execute(Map mainMap, Map detailMap, String currentValue, Map pathParam) { try { - DocImageInfo docImageInfo = Util.selectImageInfoByDocId(currentValue); + List docImageInfoList = Util.selectImageInfoByDocIds(currentValue); + DocImageInfo docImageInfo = findMaxImageFileId(docImageInfoList); + if (Objects.isNull(docImageInfo)) { + throw new CustomerException("未找到实体文件数据!"); + } InputStream inputStream = ImageFileManager.getInputStreamById(docImageInfo.getImageFileId()); String filePath = Util.createTempFile(inputStream, docImageInfo.getImageFileName(), "ofd"); String keywordType = pathParam.get("keywordType"); @@ -47,14 +52,28 @@ public class GetOfdKeywordPageValue implements CusInterfaceGetValue { List keywordInfos = reader.findKeywords(); Files.delete(Paths.get(filePath)); if (keywordInfos.isEmpty()) { - throw new CustomerException("关键字定位异常!未找到关键字"); + throw new CustomerException("关键字定位异常!未找到关键字:" + keywordValue); } else { String pageNumber = keywordInfos.get(keywordInfos.size() - 1).getStart().getPageNumber(); return Integer.parseInt(pageNumber) + 1; } } catch (Exception e) { log.error("关键字定位异常: " + Util.getErrString(e)); - throw new CustomerException("关键字定位异常!", e); + throw new CustomerException("关键字定位异常!"); } } + + public DocImageInfo findMaxImageFileId(List list) { + DocImageInfo maxImageFileIdObj = null; + Integer maxImageFileId = null; + + for (DocImageInfo obj : list) { + if (maxImageFileId == null || obj.getImageFileId() > maxImageFileId) { + maxImageFileId = obj.getImageFileId(); + maxImageFileIdObj = obj; + } + } + + return maxImageFileIdObj; + } } diff --git a/src/main/java/weaver/youhong/ai/intellectualproperty/util/OFDReader.java b/src/main/java/weaver/youhong/ai/intellectualproperty/util/OFDReader.java index 09061af..02bc6e1 100644 --- a/src/main/java/weaver/youhong/ai/intellectualproperty/util/OFDReader.java +++ b/src/main/java/weaver/youhong/ai/intellectualproperty/util/OFDReader.java @@ -1,5 +1,6 @@ package weaver.youhong.ai.intellectualproperty.util; +import lombok.ToString; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -98,6 +99,7 @@ public class OFDReader { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); NodeList textNodes = doc.getElementsByTagName("ofd:TextObject"); int startK = 0; + KeywordInfo startNode = null; List keywordInfos = new ArrayList<>(); for (int i = 0; i < textNodes.getLength(); i++) { Node textNode = textNodes.item(i); @@ -111,21 +113,24 @@ public class OFDReader { if (contentChar == keywordChar) { j++; if (j == chars.length) { + if (k == keywordChars.length - 1) { + // 已经找到关键字 + startK = 0; + KeywordInfo keywordNode = createKeywordNode(pageFolder, keywordInfos, textNode); + if (startNode != null) { + keywordInfos.add(startNode); + startNode = null; + } + keywordInfos.add(keywordNode); + break; + } startK = j; - Node boundaryNode = textNode.getAttributes().getNamedItem("Boundary"); - String boundary = boundaryNode.getNodeValue(); - String[] boundarySegments = boundary.split(" "); - double x = Double.parseDouble(boundarySegments[0]); - double y = Double.parseDouble(boundarySegments[1]); - double width = Double.parseDouble(boundarySegments[2]); - double height = Double.parseDouble(boundarySegments[3]); - KeywordInfo keywordInfo = new KeywordInfo(pageFolder, x, y, width, height); - keywordInfos.add(keywordInfo); + startNode = createKeywordNode(pageFolder, keywordInfos, textNode); break; } } else { startK = 0; - keywordInfos.clear(); + startNode = null; break; } } @@ -138,6 +143,18 @@ public class OFDReader { return new KeywordInfoRange(keywordInfos.get(0), keywordInfos.get(keywordInfos.size() - 1)); } + private static KeywordInfo createKeywordNode(String pageFolder, List keywordInfos, Node textNode) { + Node boundaryNode = textNode.getAttributes().getNamedItem("Boundary"); + String boundary = boundaryNode.getNodeValue(); + String[] boundarySegments = boundary.split(" "); + double x = Double.parseDouble(boundarySegments[0]); + double y = Double.parseDouble(boundarySegments[1]); + double width = Double.parseDouble(boundarySegments[2]); + double height = Double.parseDouble(boundarySegments[3]); + return new KeywordInfo(pageFolder, x, y, width, height); + } + + @ToString public static class KeywordInfoRange { private final KeywordInfo start; @@ -157,6 +174,7 @@ public class OFDReader { } } + @ToString public static class KeywordInfo { private final String pageFolder; private final double x; diff --git a/src/test/java/youhong/ai/intellectualproperty/TestAction.java b/src/test/java/youhong/ai/intellectualproperty/TestAction.java index 2366906..86c4dbb 100644 --- a/src/test/java/youhong/ai/intellectualproperty/TestAction.java +++ b/src/test/java/youhong/ai/intellectualproperty/TestAction.java @@ -1,9 +1,17 @@ package youhong.ai.intellectualproperty; import aiyh.utils.GenerateFileUtil; +import aiyh.utils.Util; import basetest.BaseTest; +import com.alibaba.fastjson.JSON; import org.junit.Test; import weaver.youhong.ai.intellectualproperty.action.CaElectronicSignatureAction; +import weaver.youhong.ai.intellectualproperty.util.OFDReader; + +import java.io.*; +import java.util.Base64; +import java.util.List; +import java.util.Map; /** *

测试

@@ -19,4 +27,38 @@ public class TestAction extends BaseTest { public void test() { GenerateFileUtil.createActionDocument(CaElectronicSignatureAction.class); } + + + @Test + public void testet() throws Exception { + String path = "/Users/aoey.oct.22/Downloads/关于反馈《中国(上海)自由贸易试验区十周年总结报告》征求意见的复函(5.29).ofd"; + OFDReader ofdReader = new OFDReader(path, "@Signature_position@"); + List keywords = ofdReader.findKeywords(); + System.out.println(keywords); + Map stringStringMap = Util.parseCusInterfacePathParam("weaver.youhong.ai.intellectualproperty.cusgetvalue.GetOfdKeywordPageValue?keywordType=0&keyword=`@Signature_position@`"); + System.out.println(JSON.toJSONString(stringStringMap)); + } + + + @Test + + public void tesetet() { + + String longString = ""; + try (BufferedReader reader = new BufferedReader(new FileReader("/Users/aoey.oct.22/Downloads/base64.txt"))) { + String line; + while ((line = reader.readLine()) != null) { + longString += line; + } + } catch (IOException e) { + e.printStackTrace(); + } + byte[] decodedBytes = Base64.getDecoder().decode(longString); // 解码为字节数组 + try (OutputStream outputStream = new FileOutputStream("/Users/aoey.oct.22/Downloads/example.ofd")) { + outputStream.write(decodedBytes); // 将字节数组写入文件 + System.out.println("文件已写入成功!"); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/src/test/java/youhong/ai/utiltest/PDFODFTest.java b/src/test/java/youhong/ai/utiltest/PDFODFTest.java index 89cb238..de88203 100644 --- a/src/test/java/youhong/ai/utiltest/PDFODFTest.java +++ b/src/test/java/youhong/ai/utiltest/PDFODFTest.java @@ -40,7 +40,7 @@ public class PDFODFTest extends BaseTest { @Test public void tesetOfdKeyWorkdasdf() throws Exception { - weaver.youhong.ai.intellectualproperty.util.OFDReader reader = new OFDReader("/Users/aoey.oct.22/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/44e92f459afe6c0b31d119efb268a257/Message/MessageTemp/65ac172b9cb23c7967462468b4b5be81/File/1684915381834-ofd-4e579378-0925-43b3-9f89-89b661772521测试文档01.ofd", "签章位置001"); + weaver.youhong.ai.intellectualproperty.util.OFDReader reader = new OFDReader("/Users/aoey.oct.22/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/44e92f459afe6c0b31d119efb268a257/Message/MessageTemp/65ac172b9cb23c7967462468b4b5be81/File/2b6319c0-3395-4a74-8062-07bc2089c607.ofd", "签章位置001"); System.out.println(JSON.toJSONString(reader.findKeywords())); }