This commit is contained in:
Looly 2024-10-02 01:07:15 +08:00
parent e54c0b26f6
commit ea56cc567c
4 changed files with 226 additions and 37 deletions

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) 2024 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.json.support;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
/**
* JSON格式化风格用于格式化JSON字符串
*
* @author looly
* @since 6.0.0
*/
public class JSONFormatStyle {
/**
* 获取格式化风格
*
* @param indentFactor 缩进因子定义每一级别增加的缩进量
* @return JSONFormatStyle
*/
public static JSONFormatStyle getStyle(final int indentFactor){
if(0 == indentFactor){
return JSONFormatStyle.COMPACT;
} else if(2 == indentFactor){
return JSONFormatStyle.PRETTY;
}
return new JSONFormatStyle("\n", StrUtil.repeat(CharUtil.SPACE, indentFactor), indentFactor > 0);
}
/**
* 默认紧凑风格:
*
* <ul>
* <li>无换行</li>
* <li>无缩进</li>
* <li>{@code ','} {@code ':'}后无空格</li>
* </ul>
*/
public static final JSONFormatStyle COMPACT = new JSONFormatStyle("", "", false);
/**
* 默认格式化风格:
*
* <ul>
* <li>换行符{@code "\n"}</li>
* <li>双空格缩进</li>
* <li>{@code ','} {@code ':'}后加空格</li>
* </ul>
*/
public static final JSONFormatStyle PRETTY = new JSONFormatStyle("\n", " ", true);
/**
* 换行符可以是 "\n" "\r\n"的一个或多个
*/
private final String newline;
/**
* 缩进符如空格制表符等
*/
private final String indent;
/**
* {@code ','} {@code ':'}分隔符后是否加空格加空格结果如{"a": 1}
*/
private final boolean spaceAfterSeparators;
/**
* 构造
*
* @param newline 换行符
* @param indent 缩进符
* @param spaceAfterSeparators 分隔符后是否加空格
*/
public JSONFormatStyle(final String newline, final String indent, final boolean spaceAfterSeparators) {
this.newline = Assert.notNull(newline);
this.indent = Assert.notNull(indent);;
this.spaceAfterSeparators = spaceAfterSeparators;
}
/**
* 获取换行符
*
* @return 换行符
*/
public String getNewline() {
return newline;
}
/**
* 获取缩进符
*
* @return 缩进符
*/
public String getIndent() {
return indent;
}
/**
* 分隔符后是否加空格
*
* @return 分隔符后是否加空格
*/
public boolean isSpaceAfterSeparators() {
return spaceAfterSeparators;
}
}

View File

@ -27,6 +27,7 @@ import org.dromara.hutool.json.support.InternalJSONUtil;
import org.dromara.hutool.json.JSON;
import org.dromara.hutool.json.JSONConfig;
import org.dromara.hutool.json.JSONException;
import org.dromara.hutool.json.support.JSONFormatStyle;
import java.io.Closeable;
import java.io.Flushable;
@ -65,17 +66,17 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
*
* @param appendable {@link Appendable}
* @param indentFactor 缩进因子定义每一级别增加的缩进量
* @param indent 本级别缩进量
* @param level 层级
* @param config JSON选项
* @param predicate predicate 字段过滤器
* @return JSONWriter
*/
public static JSONWriter of(final Appendable appendable,
final int indentFactor,
final int indent,
final int level,
final JSONConfig config,
final Predicate<MutableEntry<Object, Object>> predicate) {
return new JSONWriter(appendable, indentFactor, indent, config, predicate);
return new JSONWriter(appendable, JSONFormatStyle.getStyle(indentFactor), level, config, predicate);
}
/**
@ -96,9 +97,9 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
private final JSONConfig config;
/**
* 缩进因子定义每一级别增加的缩进量
* 格式化风格
*/
private final int indentFactor;
private final JSONFormatStyle formatStyle;
/**
* 键值对过滤器用于修改键值对
*/
@ -110,27 +111,27 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
*/
private boolean needSeparator;
/**
* 本级别缩进量
* 层级
*/
private int indent;
private int level;
/**
* 构造
*
* @param appendable {@link Appendable}
* @param indentFactor 缩进因子定义每一级别增加的缩进量
* @param indent 本级别缩进量
* @param formatStyle 格式化风格
* @param level 层级
* @param config JSON选项
* @param predicate 字段过滤器
*/
public JSONWriter(final Appendable appendable,
final int indentFactor,
final int indent,
final JSONFormatStyle formatStyle,
final int level,
final JSONConfig config,
final Predicate<MutableEntry<Object, Object>> predicate) {
this.appendable = appendable;
this.indentFactor = indentFactor;
this.indent = indent;
this.formatStyle = formatStyle;
this.level = level;
this.config = ObjUtil.defaultIfNull(config, JSONConfig::of);
this.predicate = predicate;
}
@ -153,7 +154,7 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
public JSONWriter beginObj() {
append(CharUtil.DELIM_START);
needSeparator = false;
indent += indentFactor;
level += 1;
return this;
}
@ -175,7 +176,7 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
public JSONWriter beginArray() {
append(CharUtil.BRACKET_START);
needSeparator = false;
indent += indentFactor;
level += 1;
return this;
}
@ -231,7 +232,7 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
append(CharUtil.COMMA);
}
// 换行缩进
writeLF().writeSpace(indent);
writeNewLine().writeIndent(level);
writeQuoteStrValue(key);
return this;
}
@ -271,15 +272,28 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
}
/**
* 写出空格
* 写出空白字符默认写出一个空格
*
* @return this
*/
public JSONWriter writeSpaceAfterSeparators() {
if (this.formatStyle.isSpaceAfterSeparators()) {
return append(CharUtil.SPACE);
}
return this;
}
/**
* 写出缩进
*
* @param count 空格数
*/
public void writeSpace(final int count) {
if (indentFactor > 0) {
public void writeIndent(final int count) {
final String indentStr = this.formatStyle.getIndent();
if (StrUtil.isNotEmpty(indentStr)) {
for (int i = 0; i < count; i++) {
//noinspection resource
append(CharUtil.SPACE);
append(indentStr);
}
}
}
@ -289,10 +303,11 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
*
* @return this
*/
public JSONWriter writeLF() {
if (indentFactor > 0) {
public JSONWriter writeNewLine() {
final String newline = this.formatStyle.getNewline();
if (StrUtil.isNotEmpty(newline)) {
//noinspection resource
append(CharUtil.LF);
append(newline);
}
return this;
}
@ -326,16 +341,16 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
final String numberStr = NumberUtil.toStr(number, isStripTrailingZeros);
// 检查有效性
if(!ReUtil.isMatch(JSON_NUMBER_PATTERN, numberStr)){
if (!ReUtil.isMatch(JSON_NUMBER_PATTERN, numberStr)) {
throw new JSONException("Invalid RFC8259 JSON format number: " + numberStr);
}
final NumberWriteMode numberWriteMode = (null == config) ? NumberWriteMode.NORMAL : config.getNumberWriteMode();
switch (numberWriteMode){
switch (numberWriteMode) {
case JS:
if(number.longValue() > JS_MAX_NUMBER){
if (number.longValue() > JS_MAX_NUMBER) {
writeQuoteStrValue(numberStr);
} else{
} else {
return writeRaw(numberStr);
}
break;
@ -394,9 +409,9 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
@SuppressWarnings("resource")
private JSONWriter end(final boolean arrayMode) {
// 结束子缩进
indent -= indentFactor;
level -= 1;
// 换行缩进
writeLF().writeSpace(indent);
writeNewLine().writeIndent(level);
append(arrayMode ? CharUtil.BRACKET_END : CharUtil.DELIM_END);
flush();
// 当前对象或数组结束当新的
@ -419,10 +434,10 @@ public class JSONWriter implements Appendable, Flushable, Closeable {
}
// 换行缩进
//noinspection resource
writeLF().writeSpace(indent);
writeNewLine().writeIndent(level);
} else {
//noinspection resource
append(CharUtil.COLON).writeSpace(1);
append(CharUtil.COLON).writeSpaceAfterSeparators();
}
needSeparator = true;
return writeObjValue(value);

View File

@ -18,6 +18,7 @@ package org.dromara.hutool.json;
import lombok.Data;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.date.DateTime;
import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.json.test.bean.Price;
@ -144,14 +145,16 @@ public class JSONUtilTest {
}
@Test
public void toJsonStrTest() {
public void toJsonPrettyStrTest() {
final DateTime date = DateUtil.date(1727800929605L);
final UserA a1 = new UserA();
a1.setA("aaaa");
a1.setDate(DateUtil.now());
a1.setDate(date);
a1.setName("AAAAName");
final UserA a2 = new UserA();
a2.setA("aaaa222");
a2.setDate(DateUtil.now());
a2.setDate(date);
a2.setName("AAAA222Name");
final ArrayList<UserA> list = ListUtil.of(a1, a2);
@ -159,9 +162,22 @@ public class JSONUtilTest {
map.put("total", 13);
map.put("rows", list);
final String str = JSONUtil.toJsonPrettyStr(map);
JSONUtil.parse(str);
Assertions.assertNotNull(str);
final String jsonStr = JSONUtil.toJsonPrettyStr(map);
Assertions.assertEquals("{\n" +
" \"total\": 13,\n" +
" \"rows\": [\n" +
" {\n" +
" \"name\": \"AAAAName\",\n" +
" \"a\": \"aaaa\",\n" +
" \"date\": 1727800929605\n" +
" },\n" +
" {\n" +
" \"name\": \"AAAA222Name\",\n" +
" \"a\": \"aaaa222\",\n" +
" \"date\": 1727800929605\n" +
" }\n" +
" ]\n" +
"}", jsonStr);
}
@Test

View File

@ -16,12 +16,16 @@
package org.dromara.hutool.json.engine;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.hutool.core.date.DateTime;
import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.core.date.TimeUtil;
import org.junit.jupiter.api.Test;
import java.io.StringReader;
import java.util.Arrays;
import java.util.TimeZone;
@ -56,6 +60,11 @@ public class JSONEngineTest {
Arrays.stream(engineNames).forEach(this::assertEmptyBeanToJson);
}
@Test
void toStringOrFromStringTest() {
Arrays.stream(engineNames).forEach(this::assertToStringOrFromString);
}
private void assertWriteDateFormat(final String engineName) {
final DateTime date = DateUtil.parse("2024-01-01 01:12:21");
final BeanWithDate bean = new BeanWithDate(date, TimeUtil.of(date));
@ -110,8 +119,37 @@ public class JSONEngineTest {
assertEquals("{}", jsonString);
}
private void assertToStringOrFromString(final String engineName) {
final JSONEngine engine = JSONEngineFactory.createEngine(engineName);
final TestBean testBean = new TestBean("张三", 18, true);
final String jsonStr = "{\"name\":\"张三\",\"age\":18,\"gender\":true}";
if("moshi".equals(engineName)){
// TODO Moshi无法指定key的顺序
assertEquals("{\"age\":18,\"gender\":true,\"name\":\"张三\"}", engine.toJsonString(testBean));
}else{
assertEquals(jsonStr, engine.toJsonString(testBean));
}
final TestBean testBean1 = engine.deserialize(new StringReader(jsonStr), TestBean.class);
assertEquals(testBean, testBean1);
}
@Data
private static class EmptyBean{
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class TestBean {
// 解决输出顺序问题
@JSONField(ordinal = 1)
private String name;
@JSONField(ordinal = 2)
private int age;
@JSONField(ordinal = 3)
private boolean gender;
}
}