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

View File

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

View File

@ -16,12 +16,16 @@
package org.dromara.hutool.json.engine; package org.dromara.hutool.json.engine;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.hutool.core.date.DateTime; import org.dromara.hutool.core.date.DateTime;
import org.dromara.hutool.core.date.DateUtil; import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.core.date.TimeUtil; import org.dromara.hutool.core.date.TimeUtil;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.StringReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.TimeZone; import java.util.TimeZone;
@ -56,6 +60,11 @@ public class JSONEngineTest {
Arrays.stream(engineNames).forEach(this::assertEmptyBeanToJson); Arrays.stream(engineNames).forEach(this::assertEmptyBeanToJson);
} }
@Test
void toStringOrFromStringTest() {
Arrays.stream(engineNames).forEach(this::assertToStringOrFromString);
}
private void assertWriteDateFormat(final String engineName) { private void assertWriteDateFormat(final String engineName) {
final DateTime date = DateUtil.parse("2024-01-01 01:12:21"); final DateTime date = DateUtil.parse("2024-01-01 01:12:21");
final BeanWithDate bean = new BeanWithDate(date, TimeUtil.of(date)); final BeanWithDate bean = new BeanWithDate(date, TimeUtil.of(date));
@ -110,8 +119,37 @@ public class JSONEngineTest {
assertEquals("{}", jsonString); 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 @Data
private static class EmptyBean{ 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;
}
} }