add JSONWriter

This commit is contained in:
Looly 2021-06-26 16:56:47 +08:00
parent 8b0357da18
commit 57b6c7dff2
7 changed files with 356 additions and 108 deletions

View File

@ -13,6 +13,7 @@
* 【json 】 用户自定义日期时间格式时,解析也读取此格式
* 【core 】 增加可自定义日期格式GlobalCustomFormat
* 【jwt 】 JWT修改默认有序并规定payload日期格式为秒数
* 【json 】 增加JSONWriter
### 🐞Bug修复
* 【json 】 修复XML转义字符的问题issue#I3XH09@Gitee

View File

@ -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);
}
}

View File

@ -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
/**
* 初始化
*

View File

@ -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
*

View File

@ -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);
}
/**

View 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中较好的显示会将&lt;/转义为&lt;\/<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);
}
}

View File

@ -64,6 +64,7 @@ public class JSONUtilTest {
map.put("rows", list);
String str = JSONUtil.toJsonPrettyStr(map);
JSONUtil.parse(str);
Assert.assertNotNull(str);
}