This commit is contained in:
Looly
2024-09-10 20:19:36 +08:00
parent 264d24b54c
commit eec27cdc40
30 changed files with 618 additions and 222 deletions

View File

@@ -20,6 +20,7 @@ import org.dromara.hutool.core.bean.path.BeanPath;
import org.dromara.hutool.core.convert.ConvertException;
import org.dromara.hutool.core.convert.Converter;
import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.core.util.ObjUtil;
import java.io.Serializable;
import java.io.StringWriter;
@@ -43,7 +44,7 @@ public interface JSON extends Converter, Cloneable, Serializable {
JSONConfig config();
/**
* JSON大小对于JSONObject是键值对的多少JSONArray则是元素的个数
* JSON大小对于JSONObject是键值对的多少JSONArray则是元素的个数JSON原始数据为1
*
* @return 大小
*/
@@ -192,6 +193,6 @@ public interface JSON extends Converter, Cloneable, Serializable {
@Override
default Object convert(final Type targetType, final Object value) throws ConvertException {
return config().getConverter().convert(targetType, value);
return ObjUtil.defaultIfNull(config(), JSONConfig::of).getConverter().convert(targetType, value);
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.json.writer.GlobalValueWriters;
import org.dromara.hutool.json.writer.JSONValueWriter;
import org.dromara.hutool.json.writer.JSONWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.function.Predicate;
/**
* JSON原始类型数据封装根据RFC8259规范JSONPrimitive只包含以下类型
* <ul>
* <li>number</li>
* <li>string</li>
* <li>null</li>
* </ul>
*
* @author Looly
* @since 6.0.0
*/
public class JSONPrimitive implements JSON {
private static final long serialVersionUID = -2026215279191790345L;
private Object value;
/**
* 配置项
*/
private JSONConfig config;
/**
* 构造
*
* @param value 值
*/
public JSONPrimitive(final Object value) {
this(value, null);
}
/**
* 构造
*
* @param value 值
* @param config 配置项
*/
public JSONPrimitive(final Object value, final JSONConfig config) {
this.value = Assert.notNull(value);
this.config = config;
}
/**
* 获取值
*
* @return 值
*/
public Object getValue() {
return this.value;
}
/**
* 设置值
*
* @param value 值
* @return this
*/
public JSONPrimitive setValue(final Object value) {
this.value = value;
return this;
}
@Override
public JSONConfig config() {
return this.config;
}
/**
* 设置配置项
*
* @param config 配置项
* @return this
*/
JSONPrimitive setConfig(final JSONConfig config) {
this.config = config;
return this;
}
@Override
public int size() {
return 1;
}
@Override
public Writer write(final Writer writer, final int indentFactor, final int indent, final Predicate<MutableEntry<Object, Object>> predicate) throws JSONException {
final Object value = this.value;
final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config);
// 自定义规则
final JSONValueWriter valueWriter = GlobalValueWriters.get(value);
if (null != valueWriter) {
valueWriter.write(jsonWriter, value);
return writer;
}
// 默认包装字符串
jsonWriter.writeQuoteStrValue(value.toString());
return writer;
}
@Override
public String toString() {
final StringWriter sw = new StringWriter();
return this.write(sw, 0, 0, null).toString();
}
}

View File

@@ -16,7 +16,6 @@
package org.dromara.hutool.json;
import org.dromara.hutool.core.convert.ConvertUtil;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.lang.Assert;
@@ -161,7 +160,7 @@ public class JSONUtil {
* @param obj 对象
* @return JSON
*/
public static Object parse(final Object obj) {
public static JSON parse(final Object obj) {
return parse(obj, null);
}
@@ -178,7 +177,7 @@ public class JSONUtil {
* @param config JSON配置{@code null}使用默认配置
* @return JSONJSONObject or JSONArray
*/
public static Object parse(final Object obj, final JSONConfig config) {
public static JSON parse(final Object obj, final JSONConfig config) {
if (null == config) {
return JSONConverter.INSTANCE.toJSON(obj);
}
@@ -378,26 +377,21 @@ public class JSONUtil {
* 转为实体类对象
*
* @param <T> Bean类型
* @param json JSONObject
* @param obj JSONObject
* @param config JSON配置
* @param type 实体类对象类型
* @return 实体类对象
* @since 4.3.2
*/
public static <T> T toBean(Object json, final JSONConfig config, Type type) {
if (null == json) {
public static <T> T toBean(final Object obj, final JSONConfig config, Type type) {
if (null == obj) {
return null;
}
json = parse(json, config);
if (json instanceof JSON) {
if (type instanceof TypeReference) {
type = ((TypeReference<?>) type).getType();
}
return ((JSON) json).toBean(type);
final JSON json = parse(obj, config);
if (type instanceof TypeReference) {
type = ((TypeReference<?>) type).getType();
}
//issue#I7CW27其他类型使用默认转换
return ConvertUtil.convert(type, json);
return json.toBean(type);
}
// -------------------------------------------------------------------- toBean end

View File

@@ -29,7 +29,6 @@ import org.dromara.hutool.core.reflect.TypeReference;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.reflect.kotlin.KClassUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.*;
import org.dromara.hutool.json.serialize.JSONDeserializer;
import org.dromara.hutool.json.serialize.JSONStringer;
@@ -63,6 +62,7 @@ public class JSONConverter implements Converter, Serializable {
final RegisterConverter converter = RegisterConverter.getInstance();
converter.register(JSONObject.class, INSTANCE);
converter.register(JSONArray.class, INSTANCE);
converter.register(JSONPrimitive.class, INSTANCE);
}
/**
@@ -137,7 +137,7 @@ public class JSONConverter implements Converter, Serializable {
* @return 转换后的对象
* @throws JSONException 转换异常
*/
public Object toJSON(Object obj) throws JSONException {
public JSON toJSON(Object obj) throws JSONException {
if (null == obj) {
return null;
}
@@ -148,10 +148,17 @@ public class JSONConverter implements Converter, Serializable {
obj = ((Opt<?>) obj).getOrNull();
}
if(obj instanceof JSON){
return (JSON) obj;
}
if (obj instanceof Number || obj instanceof Boolean) {
// RFC8259规范的原始类型数据
return new JSONPrimitive(obj, config);
}
final JSON json;
if (obj instanceof JSON || obj instanceof Number || obj instanceof Boolean) {
return obj;
} else if (obj instanceof CharSequence) {
if (obj instanceof CharSequence) {
return toJSON((CharSequence) obj);
} else if (obj instanceof MapWrapper) {
// MapWrapper实现了Iterable会被当作JSONArray此处做修正
@@ -173,7 +180,7 @@ public class JSONConverter implements Converter, Serializable {
* @return 转换后的对象
* @throws JSONException 转换异常
*/
public Object toJSON(final CharSequence str) throws JSONException {
public JSON toJSON(final CharSequence str) throws JSONException {
if (null == str) {
return null;
}
@@ -184,7 +191,7 @@ public class JSONConverter implements Converter, Serializable {
// 未被包装的空串理解为null
return null;
}
final char firstC = jsonStr.charAt(0);
// RFC8259JSON字符串值、number, boolean, or null
final JSONParser jsonParser = JSONParser.of(new JSONTokener(jsonStr), config);
final Object value = jsonParser.nextValue();
@@ -192,17 +199,10 @@ public class JSONConverter implements Converter, Serializable {
// 对于用户提供的未转义字符串导致解析未结束,报错
throw new JSONException("JSON format error: {}", jsonStr);
}
switch (firstC) {
case '"':
case '\'':
return InternalJSONUtil.quote((CharSequence) value);
default:
if (ObjUtil.equals(jsonStr, value)) {
// 对于直接的字符串如abc按照字符串处理
return InternalJSONUtil.quote((CharSequence) value);
}
return value;
if(null == value || value instanceof JSON){
return (JSON) value;
}
return new JSONPrimitive(value, config);
}
// ----------------------------------------------------------- Private method start
@@ -220,16 +220,16 @@ public class JSONConverter implements Converter, Serializable {
*/
@SuppressWarnings("unchecked")
private <T> T toBean(final Type targetType, final JSON json) {
// 自定义对象反序列化
final JSONDeserializer<Object> deserializer = InternalJSONUtil.getDeserializer(targetType);
if (null != deserializer) {
return (T) deserializer.deserialize(json);
}
// 当目标类型不确定时返回原JSON
final Class<T> rawType = (Class<T>) TypeUtil.getClass(targetType);
if (null == rawType) {
// 当目标类型不确定时返回原JSON
return (T) json;
//throw new JSONException("Can not get class from type: {}", targetType);
}
@@ -239,21 +239,29 @@ public class JSONConverter implements Converter, Serializable {
return KClassUtil.newInstance(rawType, new JSONGetterValueProvider<>((JSONGetter<String>) json));
}
final Object value;
// JSON原始类型
if(json instanceof JSONPrimitive){
value = ((JSONPrimitive) json).getValue();
} else {
value = json;
}
// 标准转换器
final Converter converter = RegisterConverter.getInstance().getConverter(targetType, json, true);
final Converter converter = RegisterConverter.getInstance().getConverter(targetType, value, true);
if (null != converter) {
return (T) converter.convert(targetType, json);
return (T) converter.convert(targetType, value);
}
// 特殊类型转换包括Collection、Map、强转、Array等
final T result = (T) SpecialConverter.getInstance().convert(targetType, rawType, json);
final T result = (T) SpecialConverter.getInstance().convert(targetType, rawType, value);
if (null != result) {
return result;
}
// 尝试转Bean
if (BeanUtil.isWritableBean(rawType)) {
return BeanCopier.of(json,
return BeanCopier.of(value,
ConstructorUtil.newInstanceIfPossible(rawType), targetType,
InternalJSONUtil.toCopyOptions(json.config())).copy();
}

View File

@@ -38,14 +38,14 @@ public class HutoolJSONEngine extends AbstractJSONEngine {
@Override
public void serialize(final Object bean, final Writer writer) {
initEngine();
final JSON json = (JSON) JSONUtil.parse(bean, this.hutoolSJONConfig);
final JSON json = JSONUtil.parse(bean, this.hutoolSJONConfig);
json.write(writer, ObjUtil.defaultIfNull(this.config, JSONEngineConfig::isPrettyPrint, false) ? 2 : 0, 0, null);
}
@Override
public <T> T deserialize(final Reader reader, final Object type) {
initEngine();
final JSON json = (JSON) JSONUtil.parse(reader, this.hutoolSJONConfig);
final JSON json = JSONUtil.parse(reader, this.hutoolSJONConfig);
return json.toBean((Type) type);
}

View File

@@ -19,6 +19,7 @@ package org.dromara.hutool.json.engine.gson;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.wrapper.Wrapper;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.JSONException;
import org.dromara.hutool.json.engine.AbstractJSONEngine;
@@ -31,6 +32,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
import java.util.TimeZone;
/**
* Gson引擎实现
@@ -38,7 +40,7 @@ import java.util.Date;
* @author Looly
* @since 6.0.0
*/
public class GsonEngine extends AbstractJSONEngine {
public class GsonEngine extends AbstractJSONEngine implements Wrapper<Gson> {
private Gson gson;
@@ -51,6 +53,12 @@ public class GsonEngine extends AbstractJSONEngine {
Assert.notNull(Gson.class);
}
@Override
public Gson getRaw() {
initEngine();
return this.gson;
}
@Override
public void serialize(final Object bean, final Writer writer) {
initEngine();
@@ -106,7 +114,11 @@ public class GsonEngine extends AbstractJSONEngine {
* @param dateFormat 日期格式
*/
private void registerDate(final GsonBuilder builder, final String dateFormat){
builder.registerTypeAdapter(Date.class, new DateSerDesc(dateFormat));
// java date
builder.registerTypeHierarchyAdapter(Date.class, new DateSerDesc(dateFormat));
builder.registerTypeHierarchyAdapter(TimeZone.class, TimeZoneSerDesc.INSTANCE);
// java.time
builder.registerTypeAdapter(LocalDateTime.class, new TemporalSerDesc(LocalDateTime.class, dateFormat));
builder.registerTypeAdapter(LocalDate.class, new TemporalSerDesc(LocalDate.class, dateFormat));
builder.registerTypeAdapter(LocalTime.class, new TemporalSerDesc(LocalTime.class, dateFormat));

View File

@@ -0,0 +1,46 @@
/*
* 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.engine.gson;
import com.google.gson.*;
import java.lang.reflect.Type;
import java.util.TimeZone;
/**
* 时区序列化描述
*
* @author Looly
* @since 6.0.0
*/
public class TimeZoneSerDesc implements GsonSerDesc<TimeZone>{
/**
* 默认时区格式化描述
*/
public static final TimeZoneSerDesc INSTANCE = new TimeZoneSerDesc();
@Override
public JsonElement serialize(final TimeZone src, final Type typeOfSrc, final JsonSerializationContext context) {
return new JsonPrimitive(src.getID());
}
@Override
public TimeZone deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
return TimeZone.getTimeZone(json.getAsString());
}
}

View File

@@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.SerializationFeature;
import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.wrapper.Wrapper;
import org.dromara.hutool.core.reflect.ConstructorUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.ObjUtil;
@@ -42,7 +43,7 @@ import java.io.Writer;
*
* @author Looly
*/
public class JacksonEngine extends AbstractJSONEngine {
public class JacksonEngine extends AbstractJSONEngine implements Wrapper<ObjectMapper> {
private ObjectMapper mapper;
@@ -60,7 +61,8 @@ public class JacksonEngine extends AbstractJSONEngine {
*
* @return {@link ObjectMapper}对象
*/
public ObjectMapper getMapper() {
@Override
public ObjectMapper getRaw() {
initEngine();
return mapper;
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2013-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.serialize;
import org.dromara.hutool.json.JSONPrimitive;
/**
* 原始对象的序列化接口,用于将特定对象序列化为{@link JSONPrimitive}
*
* @param <V> 对象类型
* @author Looly
*/
@FunctionalInterface
public interface JSONPrimitiveSerializer<V> extends JSONSerializer<JSONPrimitive, V> {
}

View File

@@ -16,36 +16,41 @@
package org.dromara.hutool.json;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class IssueI6LBZATest {
@Test
public void parseJSONStringTest() {
final String a = "\"a\"";
final Object parse = JSONUtil.parse(a);
Assertions.assertEquals(String.class, parse.getClass());
assertEquals(JSONPrimitive.class, parse.getClass());
assertEquals(a, parse.toString());
}
@Test
public void parseJSONStringTest2() {
final String a = "'a'";
final Object parse = JSONUtil.parse(a);
Assertions.assertEquals(String.class, parse.getClass());
assertEquals(JSONPrimitive.class, parse.getClass());
assertEquals("\"a\"", parse.toString());
}
@Test
public void parseJSONErrorTest() {
final String a = "a";
final Object parse = JSONUtil.parse(a);
Assertions.assertEquals(String.class, parse.getClass());
Assertions.assertEquals("\"a\"", parse);
assertEquals(JSONPrimitive.class, parse.getClass());
assertEquals("\"a\"", parse.toString());
}
@Test
public void parseJSONNumberTest() {
final String a = "123";
final Object parse = JSONUtil.parse(a);
Assertions.assertEquals(Integer.class, parse.getClass());
final JSON parse = JSONUtil.parse(a);
assertEquals(JSONPrimitive.class, parse.getClass());
assertEquals(123, ((JSONPrimitive)parse).getValue());
assertEquals(Integer.class, ((JSONPrimitive)parse).getValue().getClass());
}
}

View File

@@ -59,8 +59,8 @@ public class IssueI7M2GZTest {
entity.setList(list);
final String json = JSONUtil.toJsonStr(entity);
//Console.log(json);
final MyEntity<JSONBeanParserImpl> result = JSONUtil.toBean(json, new TypeReference<MyEntity<JSONBeanParserImpl>>() {});
Assertions.assertEquals("new Object", result.getList().get(0).getName());
Assertions.assertNotNull(result.getList().get(0).getParsed());
Assertions.assertEquals(Integer.valueOf(12), result.getList().get(0).getParsed());

View File

@@ -53,29 +53,29 @@ public class JSONUtilTest {
void parseEmptyValue() {
// https://www.rfc-editor.org/rfc/rfc8259#section-7
// 未被包装的空串理解为null
Object parse = JSONUtil.parse("");
JSON parse = JSONUtil.parse("");
assertNull(parse);
parse = JSONUtil.parse("\"\"");
assertEquals("\"\"", parse);
assertEquals("\"\"", parse.toString());
}
@Test
public void parseValueTest() {
Object parse = JSONUtil.parse(123);
assertEquals(123, parse);
JSON parse = JSONUtil.parse(123);
assertEquals(123, ((JSONPrimitive)parse).getValue());
parse = JSONUtil.parse("\"abc\"");
assertEquals("\"abc\"", parse);
assertEquals("\"abc\"", parse.toString());
parse = JSONUtil.parse("\"\\\"bc\"");
assertEquals("\"\\\"bc\"", parse);
assertEquals("\"\\\"bc\"", parse.toString());
parse = JSONUtil.parse("true");
assertEquals(true, parse);
assertEquals(true, ((JSONPrimitive)parse).getValue());
parse = JSONUtil.parse("False");
assertEquals(false, parse);
assertEquals(false, ((JSONPrimitive)parse).getValue());
parse = JSONUtil.parse("null");
assertNull(parse);

View File

@@ -23,6 +23,7 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.TimeZone;
public class JSONEngineTest {
@@ -43,6 +44,12 @@ public class JSONEngineTest {
Arrays.stream(engineNames).forEach(this::assertWriteLocalDateFormat);
}
@Test
void writeTimeZoneTest() {
// TODO Hutool无法序列化TimeZone等特殊对象
Arrays.stream(engineNames).forEach(this::assertWriteTimeZone);
}
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));
@@ -78,4 +85,16 @@ public class JSONEngineTest {
jsonString = engine.toJsonString(bean);
Assertions.assertEquals("{\"date1\":null,\"date2\":null}", jsonString);
}
private void assertWriteTimeZone(final String engineName) {
final TimeZone timeZone = TimeZone.getTimeZone("GMT+08:00");
final JSONEngine engine = JSONEngineFactory.createEngine(engineName);
String jsonString = engine.toJsonString(timeZone);
Assertions.assertEquals("\"GMT+08:00\"", jsonString);
engine.init(JSONEngineConfig.of().setIgnoreNullValue(false));
jsonString = engine.toJsonString(timeZone);
Assertions.assertEquals("\"GMT+08:00\"", jsonString);
}
}