mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
add JSONWriter
This commit is contained in:
parent
8b0357da18
commit
57b6c7dff2
@ -13,6 +13,7 @@
|
||||
* 【json 】 用户自定义日期时间格式时,解析也读取此格式
|
||||
* 【core 】 增加可自定义日期格式GlobalCustomFormat
|
||||
* 【jwt 】 JWT修改默认有序,并规定payload日期格式为秒数
|
||||
* 【json 】 增加JSONWriter
|
||||
|
||||
### 🐞Bug修复
|
||||
* 【json 】 修复XML转义字符的问题(issue#I3XH09@Gitee)
|
||||
|
@ -78,13 +78,13 @@ final class InternalJSONUtil {
|
||||
/**
|
||||
* 缩进,使用空格符
|
||||
*
|
||||
* @param writer writer
|
||||
* @param appendable writer
|
||||
* @param indent 缩进空格数
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
protected static void indent(Writer writer, int indent) throws IOException {
|
||||
protected static void indent(Appendable appendable, int indent) throws IOException {
|
||||
for (int i = 0; i < indent; i += 1) {
|
||||
writer.write(CharUtil.SPACE);
|
||||
appendable.append(CharUtil.SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,14 +4,12 @@ import cn.hutool.core.bean.BeanPath;
|
||||
import cn.hutool.core.collection.ArrayIter;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
import cn.hutool.json.serialize.GlobalSerializeMapping;
|
||||
import cn.hutool.json.serialize.JSONSerializer;
|
||||
import cn.hutool.json.serialize.JSONWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -539,54 +537,14 @@ public class JSONArray implements JSON, JSONGetter<Integer>, List<Object>, Rando
|
||||
|
||||
@Override
|
||||
public Writer write(Writer writer, int indentFactor, int indent) throws JSONException {
|
||||
try {
|
||||
return doWrite(writer, indentFactor, indent);
|
||||
} catch (IOException e) {
|
||||
throw new JSONException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 将JSON内容写入Writer
|
||||
*
|
||||
* @param writer writer
|
||||
* @param indentFactor 缩进因子,定义每一级别增加的缩进量
|
||||
* @param indent 本级别缩进量
|
||||
* @return Writer
|
||||
* @throws IOException IO相关异常
|
||||
*/
|
||||
private Writer doWrite(Writer writer, int indentFactor, int indent) throws IOException {
|
||||
writer.write(CharUtil.BRACKET_START);
|
||||
final int newindent = indent + indentFactor;
|
||||
final boolean isIgnoreNullValue = this.config.isIgnoreNullValue();
|
||||
boolean isFirst = true;
|
||||
for (Object obj : this.rawList) {
|
||||
if (ObjectUtil.isNull(obj) && isIgnoreNullValue) {
|
||||
continue;
|
||||
}
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
writer.write(CharUtil.COMMA);
|
||||
}
|
||||
|
||||
if (indentFactor > 0) {
|
||||
writer.write(CharUtil.LF);
|
||||
}
|
||||
InternalJSONUtil.indent(writer, newindent);
|
||||
InternalJSONUtil.writeValue(writer, obj, indentFactor, newindent, this.config);
|
||||
}
|
||||
|
||||
if (indentFactor > 0) {
|
||||
writer.write(CharUtil.LF);
|
||||
}
|
||||
InternalJSONUtil.indent(writer, indent);
|
||||
writer.write(CharUtil.BRACKET_END);
|
||||
final JSONWriter jsonWriter = new JSONWriter(writer, indentFactor, indent, config);
|
||||
jsonWriter.beginArray();
|
||||
this.forEach(jsonWriter::writeValue);
|
||||
jsonWriter.end();
|
||||
return writer;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
|
@ -10,15 +10,14 @@ import cn.hutool.core.map.CaseInsensitiveLinkedMap;
|
||||
import cn.hutool.core.map.CaseInsensitiveMap;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.serialize.GlobalSerializeMapping;
|
||||
import cn.hutool.json.serialize.JSONObjectSerializer;
|
||||
import cn.hutool.json.serialize.JSONSerializer;
|
||||
import cn.hutool.json.serialize.JSONWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
@ -555,64 +554,15 @@ public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object>
|
||||
|
||||
@Override
|
||||
public Writer write(Writer writer, int indentFactor, int indent) throws JSONException {
|
||||
try {
|
||||
return doWrite(writer, indentFactor, indent);
|
||||
} catch (IOException exception) {
|
||||
throw new JSONException(exception);
|
||||
}
|
||||
}
|
||||
final JSONWriter jsonWriter = new JSONWriter(writer, indentFactor, indent, this.config);
|
||||
jsonWriter.beginObj();
|
||||
this.forEach((key, value)-> jsonWriter.writeKey(key).writeValue(value));
|
||||
jsonWriter.end();
|
||||
|
||||
// ------------------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 将JSON内容写入Writer
|
||||
*
|
||||
* @param writer writer
|
||||
* @param indentFactor 缩进因子,定义每一级别增加的缩进量
|
||||
* @param indent 本级别缩进量
|
||||
* @return Writer
|
||||
* @throws JSONException JSON相关异常
|
||||
*/
|
||||
private Writer doWrite(Writer writer, int indentFactor, int indent) throws IOException {
|
||||
writer.write(CharUtil.DELIM_START);
|
||||
boolean isFirst = true;
|
||||
final boolean isIgnoreNullValue = this.config.isIgnoreNullValue();
|
||||
final int newIndent = indent + indentFactor;
|
||||
for (Entry<String, Object> entry : this.entrySet()) {
|
||||
if (ObjectUtil.isNull(entry.getKey()) || (ObjectUtil.isNull(entry.getValue()) && isIgnoreNullValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
// 键值对分隔
|
||||
writer.write(CharUtil.COMMA);
|
||||
}
|
||||
// 换行缩进
|
||||
if (indentFactor > 0) {
|
||||
writer.write(CharUtil.LF);
|
||||
}
|
||||
|
||||
InternalJSONUtil.indent(writer, newIndent);
|
||||
writer.write(JSONUtil.quote(entry.getKey()));
|
||||
writer.write(CharUtil.COLON);
|
||||
if (indentFactor > 0) {
|
||||
// 冒号后的空格
|
||||
writer.write(CharUtil.SPACE);
|
||||
}
|
||||
InternalJSONUtil.writeValue(writer, entry.getValue(), indentFactor, newIndent, this.config);
|
||||
}
|
||||
|
||||
// 结尾符
|
||||
if (indentFactor > 0) {
|
||||
writer.write(CharUtil.LF);
|
||||
}
|
||||
InternalJSONUtil.indent(writer, indent);
|
||||
writer.write(CharUtil.DELIM_END);
|
||||
return writer;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* Bean对象转Map
|
||||
*
|
||||
|
@ -393,7 +393,7 @@ public class JSONTokener {
|
||||
* @return A JSONException object, suitable for throwing
|
||||
*/
|
||||
public JSONException syntaxError(String message) {
|
||||
return new JSONException(message + this.toString());
|
||||
return new JSONException(message + this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
338
hutool-json/src/main/java/cn/hutool/json/serialize/JSONWriter.java
Executable file
338
hutool-json/src/main/java/cn/hutool/json/serialize/JSONWriter.java
Executable file
@ -0,0 +1,338 @@
|
||||
package cn.hutool.json.serialize;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.TemporalAccessorUtil;
|
||||
import cn.hutool.core.date.format.GlobalCustomFormat;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSON;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONConfig;
|
||||
import cn.hutool.json.JSONException;
|
||||
import cn.hutool.json.JSONNull;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONString;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JSON数据写出器<br>
|
||||
* 通过简单的append方式将JSON的键值对等信息写出到{@link Writer}中。
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.3
|
||||
*/
|
||||
public class JSONWriter {
|
||||
|
||||
/**
|
||||
* 缩进因子,定义每一级别增加的缩进量
|
||||
*/
|
||||
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;
|
||||
|
||||
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);
|
||||
try {
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出值,自动处理分隔符和缩进,自动判断类型,并根据不同类型写出特定格式的值
|
||||
*
|
||||
* @param value 值
|
||||
* @return this
|
||||
*/
|
||||
public JSONWriter writeValue(Object value) {
|
||||
if (arrayMode) {
|
||||
if (needSeparator) {
|
||||
writeRaw(CharUtil.COMMA);
|
||||
}
|
||||
// 换行缩进
|
||||
writeLF().writeSpace(indentFactor + indent);
|
||||
} else {
|
||||
writeRaw(CharUtil.COLON).writeSpace(1);
|
||||
}
|
||||
needSeparator = true;
|
||||
return writeObjValue(value);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------ Private methods
|
||||
/**
|
||||
* 写出JSON的值,根据值类型不同,输出不同内容
|
||||
*
|
||||
* @param value 值
|
||||
* @return this
|
||||
*/
|
||||
private JSONWriter writeObjValue(Object value) {
|
||||
final int indent = indentFactor + this.indent;
|
||||
if (value == null || value instanceof JSONNull) {
|
||||
writeRaw(JSONNull.NULL.toString());
|
||||
} else if (value instanceof JSON) {
|
||||
((JSON) value).write(writer, indentFactor, indent);
|
||||
} else if (value instanceof Map) {
|
||||
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) {
|
||||
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()} 配置不同,写出不同数字<br>
|
||||
* 主要针对Double型是否去掉小数点后多余的0<br>
|
||||
* 此方法输出的值不包装引号。
|
||||
*
|
||||
* @param number 数字
|
||||
* @return this
|
||||
*/
|
||||
private JSONWriter writeNumberValue(Number number) {
|
||||
// since 5.6.2可配置是否去除末尾多余0,例如如果为true,5.0返回5
|
||||
final boolean isStripTrailingZeros = null == config || config.isStripTrailingZeros();
|
||||
return writeRaw(NumberUtil.toStr(number, isStripTrailingZeros));
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出Boolean值,直接写出true或false,不适用引号包装
|
||||
*
|
||||
* @param value Boolean值
|
||||
* @return this
|
||||
*/
|
||||
private JSONWriter writeBooleanValue(Boolean value) {
|
||||
return writeRaw(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出实现了{@link JSONString}接口的对象,通过调用{@link JSONString#toJSONString()}获取JSON字符串<br>
|
||||
* {@link JSONString}按照JSON对象对待,此方法输出的JSON字符串不包装引号。<br>
|
||||
* 如果toJSONString()返回null,调用toString()方法并使用双引号包装。
|
||||
*
|
||||
* @param jsonString {@link JSONString}
|
||||
* @return this
|
||||
*/
|
||||
private JSONWriter 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());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出字符串值,并包装引号并转义字符<br>
|
||||
* 对所有双引号做转义处理(使用双反斜杠做转义)<br>
|
||||
* 为了能在HTML中较好的显示,会将</转义为<\/<br>
|
||||
* JSON字符串中不能包含控制字符和未经转义的引号和反斜杠
|
||||
*
|
||||
* @param csq 字符串
|
||||
* @return this
|
||||
*/
|
||||
private JSONWriter writeStrValue(String csq) {
|
||||
try {
|
||||
JSONUtil.quote(csq, writer);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出空格
|
||||
*
|
||||
* @param count 空格数
|
||||
* @return this
|
||||
*/
|
||||
private JSONWriter writeSpace(int count) {
|
||||
if (indentFactor > 0) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeRaw(CharUtil.SPACE);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出换换行符
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
@ -64,6 +64,7 @@ public class JSONUtilTest {
|
||||
map.put("rows", list);
|
||||
|
||||
String str = JSONUtil.toJsonPrettyStr(map);
|
||||
JSONUtil.parse(str);
|
||||
Assert.assertNotNull(str);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user