add JSONEngineConfig

This commit is contained in:
Looly 2024-08-10 11:27:51 +08:00
parent 6171f51251
commit 40fbed9173
12 changed files with 301 additions and 58 deletions

View File

@ -25,13 +25,43 @@ import org.dromara.hutool.core.text.StrUtil;
public class JSONStrFormatter {
/**
* 单位缩进字符串
* 默认实例
*/
private static final String SPACE = " ";
public static final JSONStrFormatter INSTANCE = new JSONStrFormatter(4, CharUtil.LF);
private int indentFactor;
private char newLineChar;
/**
* 换行符
* 构造
*
* @param indentFactor 缩进因子即每个缩进空格数
* @param newLineChar 换行符
*/
private static final char NEW_LINE = CharUtil.LF;
public JSONStrFormatter(final int indentFactor, final char newLineChar){
this.indentFactor = indentFactor;
this.newLineChar = newLineChar;
}
/**
* 设置缩进因子即每个缩进空格数
* @param indentFactor 缩进因子即每个缩进空格数
* @return this
*/
public JSONStrFormatter setIndentFactor(final int indentFactor) {
this.indentFactor = indentFactor;
return this;
}
/**
* 设置换行符
* @param newLineChar 换行符
* @return this
*/
public JSONStrFormatter setNewLineChar(final char newLineChar) {
this.newLineChar = newLineChar;
return this;
}
/**
* 返回格式化JSON字符串
@ -39,7 +69,7 @@ public class JSONStrFormatter {
* @param json 未格式化的JSON字符串
* @return 格式化的JSON字符串
*/
public static String format(final String json) {
public String format(final String json) {
final StringBuilder result = new StringBuilder();
Character wrapChar = null;
@ -94,26 +124,21 @@ public class JSONStrFormatter {
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(newLineChar).append(indent(number));
}
result.append(key);
//前方括号前花括号的后面必须换行打印换行
result.append(NEW_LINE);
result.append(key).append(newLineChar);
//每出现一次前方括号前花括号缩进次数增加一次打印新行缩进
number++;
result.append(indent(number));
result.append(indent(++number));
continue;
}
// 3如果当前字符是后方括号后花括号做如下处理
if ((key == CharUtil.BRACKET_END) || (key == CharUtil.DELIM_END)) {
// 1后方括号后花括号的前面必须换行打印换行
result.append(NEW_LINE);
result.append(newLineChar);
// 2每出现一次后方括号后花括号缩进次数减少一次打印缩进
number--;
result.append(indent(number));
result.append(indent(--number));
// 3打印当前字符
result.append(key);
// 4如果当前字符后面还有字符并且字符不为打印换行
@ -125,10 +150,8 @@ public class JSONStrFormatter {
}
// 4如果当前字符是逗号逗号后面换行并缩进不改变缩进次数
if ((key == ',')) {
result.append(key);
result.append(NEW_LINE);
result.append(indent(number));
if ((key == CharUtil.COMMA)) {
result.append(key).append(newLineChar).append(indent(number));
continue;
}
@ -149,7 +172,7 @@ public class JSONStrFormatter {
* @param number 缩进次数
* @return 指定缩进次数的字符串
*/
private static String indent(final int number) {
return StrUtil.repeat(SPACE, number);
private String indent(final int number) {
return StrUtil.repeat(StrUtil.SPACE, number * indentFactor);
}
}

View File

@ -493,7 +493,7 @@ public class JSONUtil {
* @since 3.1.2
*/
public static String formatJsonStr(final String jsonStr) {
return JSONStrFormatter.format(jsonStr);
return JSONStrFormatter.INSTANCE.format(jsonStr);
}
/**

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.json.engine;
/**
* JSONEngine抽象实现
*/
public abstract class AbstractJSONEngine implements JSONEngine {
/**
* JSON引擎配置{@code null}表示默认配置
*/
protected JSONEngineConfig config;
@Override
public JSONEngine init(final JSONEngineConfig config) {
this.config = config;
reset();
return this;
}
/**
* 重置引擎
*/
protected abstract void reset();
/**
* 初始化引擎实现逻辑中如果初始化完成不再重新初始化
*/
protected abstract void initEngine();
}

View File

@ -17,10 +17,14 @@ import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.reader.ObjectReader;
import com.alibaba.fastjson2.writer.ObjectWriter;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.util.ObjUtil;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.List;
/**
* FastJSON2引擎实现
@ -28,21 +32,23 @@ import java.lang.reflect.Type;
* @author Looly
* @since 6.0.0
*/
public class FastJSON2Engine implements JSONEngine {
public class FastJSON2Engine extends AbstractJSONEngine {
private final JSONWriter.Context writerContext;
private final JSONReader.Context readerContext;
private JSONReader.Context readerContext;
private JSONWriter.Context writerContext;
/**
* 构造
*/
public FastJSON2Engine() {
this.writerContext = JSONFactory.createWriteContext();
this.readerContext = JSONFactory.createReadContext();
// issue#IABWBL JDK8下在IDEA旗舰版加载Spring boot插件时启动应用不会检查字段类是否存在
// 此处构造时调用下这个类以便触发类是否存在的检查
Assert.notNull(JSONFactory.class);
}
@Override
public void serialize(final Object bean, final Writer writer) {
initEngine();
try (final JSONWriter jsonWriter = JSONWriter.of(this.writerContext)) {
if (bean == null) {
jsonWriter.writeNull();
@ -60,6 +66,7 @@ public class FastJSON2Engine implements JSONEngine {
@SuppressWarnings("unchecked")
@Override
public <T> T deserialize(final Reader reader, final Object type) {
initEngine();
final ObjectReader<T> objectReader = this.readerContext.getObjectReader((Type) type);
try (final JSONReader jsonReader = JSONReader.of(reader, this.readerContext)) {
@ -72,4 +79,24 @@ public class FastJSON2Engine implements JSONEngine {
return object;
}
}
@Override
protected void reset() {
this.readerContext = null;
this.writerContext = null;
}
@Override
protected void initEngine() {
if(null == this.readerContext){
this.readerContext = JSONFactory.createReadContext();
}
if(null == this.writerContext){
final List<JSONWriter.Feature> features = ListUtil.of();
if(ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isPrettyPrint, false)){
features.add(JSONWriter.Feature.PrettyFormat);
}
this.writerContext = JSONFactory.createWriteContext(features.toArray(new JSONWriter.Feature[0]));
}
}
}

View File

@ -14,6 +14,8 @@ package org.dromara.hutool.json.engine;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.JSONException;
import java.io.Reader;
@ -26,25 +28,29 @@ import java.lang.reflect.Type;
* @author Looly
* @since 6.0.0
*/
public class GsonEngine implements JSONEngine{
public class GsonEngine extends AbstractJSONEngine{
private final Gson gson;
private Gson gson;
/**
* 构造
*/
public GsonEngine() {
this.gson = new GsonBuilder().create();
// issue#IABWBL JDK8下在IDEA旗舰版加载Spring boot插件时启动应用不会检查字段类是否存在
// 此处构造时调用下这个类以便触发类是否存在的检查
Assert.notNull(Gson.class);
}
@Override
public void serialize(final Object bean, final Writer writer) {
initEngine();
gson.toJson(bean, writer);
}
@SuppressWarnings("unchecked")
@Override
public <T> T deserialize(final Reader reader, final Object type) {
initEngine();
if(type instanceof Class){
return gson.fromJson(reader, (Class<T>)type);
} else if(type instanceof Type){
@ -53,4 +59,22 @@ public class GsonEngine implements JSONEngine{
throw new JSONException("Unsupported type: {}", type.getClass());
}
@Override
protected void reset() {
this.gson = null;
}
@Override
protected void initEngine() {
if(null != this.gson){
return;
}
final GsonBuilder builder = new GsonBuilder();
if(ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isPrettyPrint, false)){
builder.setPrettyPrinting();
}
this.gson = builder.create();
}
}

View File

@ -12,7 +12,9 @@
package org.dromara.hutool.json.engine;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.JSON;
import org.dromara.hutool.json.JSONConfig;
import org.dromara.hutool.json.JSONUtil;
import java.io.Reader;
@ -25,16 +27,35 @@ import java.lang.reflect.Type;
* @author Looly
* @since 6.0.0
*/
public class HutoolJSONEngine implements JSONEngine {
public class HutoolJSONEngine extends AbstractJSONEngine {
private JSONConfig hutoolSJONConfig;
@Override
public void serialize(final Object bean, final Writer writer) {
final JSON json = (JSON) JSONUtil.parse(bean);
json.write(writer);
initEngine();
final JSON json = (JSON) JSONUtil.parse(bean, this.hutoolSJONConfig);
json.write(writer, ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isPrettyPrint, false) ? 4 : 0, 0, null);
}
@Override
public <T> T deserialize(final Reader reader, final Object type) {
final JSON json = (JSON) JSONUtil.parse(reader);
initEngine();
final JSON json = (JSON) JSONUtil.parse(reader, this.hutoolSJONConfig);
return json.toBean((Type) type);
}
@Override
protected void reset() {
hutoolSJONConfig = null;
}
@Override
protected void initEngine() {
if(null != hutoolSJONConfig){
return;
}
hutoolSJONConfig = JSONConfig.of();
}
}

View File

@ -26,7 +26,17 @@ import java.io.Writer;
public interface JSONEngine {
/**
* 生成JSON数据序列化
* 初始化配置<br>
* 在引擎被加载时如果需要自定义引擎可以首先调用此方法<br>
* 调用此方法前需要清除已经生成的引擎内容
*
* @param config 配置
* @return this
*/
JSONEngine init(JSONEngineConfig config);
/**
* 生成JSON数据序列化用于将指定的Bean对象通过Writer写出为JSON字符串
*
* @param bean Java BeanPOJO对象
* @param writer 写出到的Writer
@ -34,7 +44,8 @@ public interface JSONEngine {
void serialize(Object bean, Writer writer);
/**
* 解析JSON数据反序列化
* 解析JSON数据反序列化用于从Reader中读取JSON字符串转换为Bean对象<br>
* type此处定义为Object因为不同引擎对Type的定义不同尤其是出现泛型定义时需要传入引擎自身实现的TypeReference
*
* @param <T> Java Bean对象类型
* @param reader 读取JSON的Reader
@ -58,7 +69,7 @@ public interface JSONEngine {
/**
* 将JSON字符串转换为Java BeanPOJO对象
*
* @param <T> Java Bean对象类型
* @param <T> Java Bean对象类型
* @param jsonStr JSON字符串
* @param type Java BeanPOJO对象类型可以为{@code Class<T>}或者TypeReference
* @return Java BeanPOJO对象

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.json.engine;
import java.io.Serializable;
/**
* JSON引擎配置
*
* @author Looly
* @since 6.0.0
*/
public class JSONEngineConfig implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 是否格式化输出
*/
private boolean prettyPrint;
/**
* 获取是否启用格式化输出
*
* @return true如果配置了格式化输出否则返回false
*/
public boolean isPrettyPrint() {
return prettyPrint;
}
/**
* 设置是否启用格式化输出
*
* @param prettyPrint 布尔值指示是否启用格式化输出
* @return this
*/
public JSONEngineConfig setPrettyPrint(final boolean prettyPrint) {
this.prettyPrint = prettyPrint;
return this;
}
}

View File

@ -38,11 +38,16 @@ public class JSONEngineFactory {
/**
* 创建自定义引擎
*
* @param engineName 引擎名称忽略大小写`HttpClient4``HttpClient5``OkHttp``JdkClient`
* @param engineName 引擎名称忽略大小写`FastJSON2``Jackson``Gson``HutoolJSON`
* @return 引擎
* @throws JSONException 无对应名称的引擎
*/
public static JSONEngine createEngine(String engineName) throws JSONException {
// fastjson名字兼容
if(StrUtil.equalsIgnoreCase("fastjson", engineName)){
engineName = "FastJSON2";
}
if (!StrUtil.endWithIgnoreCase(engineName, "Engine")) {
engineName = engineName + "Engine";
}
@ -55,11 +60,22 @@ public class JSONEngineFactory {
throw new JSONException("No such engine named: " + engineName);
}
/**
* 根据用户引入的JSON引擎jar自动创建对应的HTTP客户端引擎对象<br>
* 推荐创建的引擎单例使用此方法每次调用会返回新的引擎
*
* @param config JSON引擎配置
* @return {@code JSONEngine}
*/
public static JSONEngine createEngine(final JSONEngineConfig config) {
return createEngine().init(config);
}
/**
* 根据用户引入的JSON引擎jar自动创建对应的JSON引擎对象<br>
* 推荐创建的引擎单例使用此方法每次调用会返回新的引擎
*
* @return {@code ClientEngine}
* @return {@code JSONEngine}
*/
public static JSONEngine createEngine() {
// HutoolJSONEngine托底始终不空

View File

@ -17,7 +17,8 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.JSONException;
import java.io.IOException;
@ -29,23 +30,24 @@ import java.io.Writer;
*
* @author Looly
*/
public class JacksonEngine extends SimpleWrapper<ObjectMapper> implements JSONEngine{
public class JacksonEngine extends AbstractJSONEngine {
private ObjectMapper mapper;
/**
* 构造
*/
public JacksonEngine() {
super(new ObjectMapper());
// 允许出现单引号
raw.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// 允许没有引号的字段名(非标准)
raw.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// issue#IABWBL JDK8下在IDEA旗舰版加载Spring boot插件时启动应用不会检查字段类是否存在
// 此处构造时调用下这个类以便触发类是否存在的检查
Assert.notNull(ObjectMapper.class);
}
@Override
public void serialize(final Object bean, final Writer writer) {
initEngine();
try {
raw.writeValue(writer, bean);
mapper.writeValue(writer, bean);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
@ -54,13 +56,14 @@ public class JacksonEngine extends SimpleWrapper<ObjectMapper> implements JSONEn
@SuppressWarnings("unchecked")
@Override
public <T> T deserialize(final Reader reader, final Object type) {
initEngine();
try {
if(type instanceof Class){
return raw.readValue(reader, (Class<T>)type);
} else if(type instanceof TypeReference){
return raw.readValue(reader, (TypeReference<T>)type);
} else if(type instanceof JavaType){
return raw.readValue(reader, (JavaType)type);
if (type instanceof Class) {
return mapper.readValue(reader, (Class<T>) type);
} else if (type instanceof TypeReference) {
return mapper.readValue(reader, (TypeReference<T>) type);
} else if (type instanceof JavaType) {
return mapper.readValue(reader, (JavaType) type);
}
} catch (final IOException e) {
throw new IORuntimeException(e);
@ -68,4 +71,31 @@ public class JacksonEngine extends SimpleWrapper<ObjectMapper> implements JSONEn
throw new JSONException("Unsupported type: {}", type.getClass());
}
@Override
protected void reset() {
this.mapper = null;
}
@Override
protected void initEngine() {
if(null != this.mapper){
return;
}
this.mapper = new ObjectMapper();
// 默认配置
this.mapper.enable(
// 允许出现单引号
JsonParser.Feature.ALLOW_SINGLE_QUOTES,
// 允许没有引号的字段名(非标准)
JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES
);
// 自定义配置
if(ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isPrettyPrint, false)){
this.mapper.writerWithDefaultPrettyPrinter();
}
}
}

View File

@ -27,21 +27,21 @@ public class JSONStrFormatterTest {
@Test
public void formatTest() {
final String json = "{'age':23,'aihao':['pashan','movies'],'name':{'firstName':'zhang','lastName':'san','aihao':['pashan','movies','name':{'firstName':'zhang','lastName':'san','aihao':['pashan','movies']}]}}";
final String result = JSONStrFormatter.format(json);
final String result = JSONStrFormatter.INSTANCE.format(json);
Assertions.assertNotNull(result);
}
@Test
public void formatTest2() {
final String json = "{\"abc\":{\"def\":\"\\\"[ghi]\"}}";
final String result = JSONStrFormatter.format(json);
final String result = JSONStrFormatter.INSTANCE.format(json);
Assertions.assertNotNull(result);
}
@Test
public void formatTest3() {
final String json = "{\"id\":13,\"title\":\"《标题》\",\"subtitle\":\"副标题z'c'z'xv'c'xv\",\"user_id\":6,\"type\":0}";
final String result = JSONStrFormatter.format(json);
final String result = JSONStrFormatter.INSTANCE.format(json);
Assertions.assertNotNull(result);
}
@ -49,6 +49,6 @@ public class JSONStrFormatterTest {
@Disabled
public void formatTest4(){
final String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}";
Console.log(JSONUtil.formatJsonStr(jsonStr));
Console.log(JSONStrFormatter.INSTANCE.format(jsonStr));
}
}

View File

@ -79,7 +79,7 @@ public class JSONEngineFactoryTest {
@Test
void fastJSON2Test() {
final JSONEngine engine = JSONEngineFactory.createEngine("fastjson2");
final JSONEngine engine = JSONEngineFactory.createEngine("fastjson");
assertEquals(FastJSON2Engine.class, engine.getClass());
final StringWriter stringWriter = new StringWriter();