getRawValue() {
- return rawValue;
- }
-
/**
- * @author 夜雨
- * @version 2024-04-10 00:45
+ * 添加自定义类型转换器
+ *
+ * 用于 YAML 不支持的类型序列化和反序列化,如 JavaFX Property 等
+ *
+ * @param type 字段类型
+ * @param converter 转换器
*/
- private static class RawMapper {
+ public void addConverter(Class> type, BaseConverter, ?> converter) {
+ converters.put(type, converter);
+ }
- Object owner;
+ private K mapToBean(Map, ?> map, Class targetClass) {
+ try {
+ K instance = targetClass.getDeclaredConstructor().newInstance();
+ applyMapToObject(map, instance);
+ return instance;
+ } catch (Exception e) {
+ throw new TimiException(TimiCode.ERROR, "map to bean error: " + targetClass.getName(), e);
+ }
+ }
- RefField refField;
+ private void applyMapToObject(Map, ?> map, Object target) throws IllegalAccessException {
+ Class> currentClass = target.getClass();
+ while (currentClass != null && currentClass != Object.class) {
+ for (Field field : currentClass.getDeclaredFields()) {
+ String fieldName = field.getName();
+ if (!map.containsKey(fieldName)) {
+ continue;
+ }
+ Object rawValue = map.get(fieldName);
+ Object converted = convertFieldValue(field, rawValue);
+ field.setAccessible(true);
+ field.set(target, converted);
+ }
+ currentClass = currentClass.getSuperclass();
+ }
+ }
- String rawStack;
+ private Object convertFieldValue(Field field, Object rawValue) {
+ BaseConverter, ?> converter = converters.get(field.getType());
+ if (converter != null) {
+ return converter.deserialize0(field, rawValue);
+ }
+ if (rawValue == null) {
+ return null;
+ }
+ Class> fieldType = field.getType();
+ if (fieldType.isEnum()) {
+ return toEnumValue(fieldType, rawValue.toString());
+ }
+ if (rawValue instanceof Map, ?> rawMap && shouldMapToBean(fieldType)) {
+ return mapToBean(rawMap, fieldType);
+ }
+ return convertScalarValue(fieldType, rawValue);
+ }
+
+ private boolean shouldMapToBean(Class> fieldType) {
+ if (fieldType.isPrimitive() || fieldType.isEnum()) {
+ return false;
+ }
+ if (fieldType.getName().startsWith("java.")) {
+ return false;
+ }
+ return !fieldType.isInterface();
+ }
+
+ private Object convertScalarValue(Class> fieldType, Object rawValue) {
+ if (fieldType.isInstance(rawValue)) {
+ return rawValue;
+ }
+ String text = rawValue.toString();
+ if (fieldType == String.class) {
+ return text;
+ }
+ if (fieldType == int.class || fieldType == Integer.class) {
+ return rawValue instanceof Number ? ((Number) rawValue).intValue() : Integer.parseInt(text);
+ }
+ if (fieldType == long.class || fieldType == Long.class) {
+ return rawValue instanceof Number ? ((Number) rawValue).longValue() : Long.parseLong(text);
+ }
+ if (fieldType == double.class || fieldType == Double.class) {
+ return rawValue instanceof Number ? ((Number) rawValue).doubleValue() : Double.parseDouble(text);
+ }
+ if (fieldType == float.class || fieldType == Float.class) {
+ return rawValue instanceof Number ? ((Number) rawValue).floatValue() : Float.parseFloat(text);
+ }
+ if (fieldType == short.class || fieldType == Short.class) {
+ return rawValue instanceof Number ? ((Number) rawValue).shortValue() : Short.parseShort(text);
+ }
+ if (fieldType == byte.class || fieldType == Byte.class) {
+ return rawValue instanceof Number ? ((Number) rawValue).byteValue() : Byte.parseByte(text);
+ }
+ if (fieldType == boolean.class || fieldType == Boolean.class) {
+ return rawValue instanceof Boolean ? rawValue : Boolean.parseBoolean(text);
+ }
+ if (fieldType == char.class || fieldType == Character.class) {
+ return text.isEmpty() ? null : text.charAt(0);
+ }
+ return rawValue;
+ }
+
+ @SuppressWarnings("unchecked")
+ private > E toEnumValue(Class> enumType, String value) {
+ Class enumClass = (Class) enumType;
+ for (E constant : enumClass.getEnumConstants()) {
+ if (constant.name().equalsIgnoreCase(value)) {
+ return constant;
+ }
+ }
+ return null;
+ }
+
+ /** 序列化时应用转换器 */
+ @SuppressWarnings("unchecked")
+ private K applyConvertersOnDump(K obj) {
+ if (obj == null || converters.isEmpty()) {
+ return obj;
+ }
+ try {
+ // 创建副本
+ K copy = (K) obj.getClass().getDeclaredConstructor().newInstance();
+ Class> currentClass = obj.getClass();
+ while (currentClass != null && currentClass != Object.class) {
+ for (Field field : currentClass.getDeclaredFields()) {
+ field.setAccessible(true);
+ Object fieldValue = field.get(obj);
+
+ BaseConverter, ?> converter = converters.get(field.getType());
+ if (converter != null) {
+ // 应用转换器
+ Object yamlValue = converter.serialize0(field, fieldValue);
+ field.set(copy, yamlValue);
+ } else {
+ // 直接复制
+ field.set(copy, fieldValue);
+ }
+ }
+ currentClass = currentClass.getSuperclass();
+ }
+ return copy;
+ } catch (Exception e) {
+ throw new TimiException(TimiCode.ERROR, "converter error on dump", e);
+ }
}
}
diff --git a/src/main/java/com/imyeyu/config/ConfigRepresenter.java b/src/main/java/com/imyeyu/config/ConfigRepresenter.java
index acd5822..8a185f1 100644
--- a/src/main/java/com/imyeyu/config/ConfigRepresenter.java
+++ b/src/main/java/com/imyeyu/config/ConfigRepresenter.java
@@ -1,18 +1,53 @@
package com.imyeyu.config;
import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.introspector.Property;
+import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;
/**
+ * 自定义 Representer - SnakeYAML 序列化增强
+ *
+ * - 跳过 null 值字段,保持 YAML 简洁
+ * - 禁用类型标签,输出纯净的 YAML
+ * - 枚举序列化为字符串
+ *
+ *
* @author 夜雨
- * @version 2024-05-01 14:03
+ * @since 2026-01-12 12:02
*/
public class ConfigRepresenter extends Representer {
public ConfigRepresenter(DumperOptions options) {
super(options);
-
+ // null 值表示为空字符串
this.nullRepresenter = data -> representScalar(Tag.NULL, "");
+ // 枚举转字符串(不带类型标签)
+ this.representers.put(Enum.class, data -> {
+ Enum> enumValue = (Enum>) data;
+ return representScalar(Tag.STR, enumValue.name());
+ });
+ }
+
+ @Override
+ protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
+ // 跳过 null 值,保持 YAML 简洁
+ if (propertyValue == null) {
+ return null;
+ }
+ return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
+ }
+
+ @Override
+ protected org.yaml.snakeyaml.nodes.MappingNode representJavaBean(java.util.Set properties, Object javaBean) {
+ Class> clazz = javaBean.getClass();
+ // 对于非 Java 标准库的类,使用 MAP 标签而不是类名标签
+ if (!clazz.getName().startsWith("java.") && !clazz.getName().startsWith("javax.")) {
+ if (this.classTags.get(clazz) == null) {
+ this.addClassTag(clazz, Tag.MAP);
+ }
+ }
+ return super.representJavaBean(properties, javaBean);
}
}
diff --git a/src/test/java/test/TestConfig.java b/src/test/java/test/TestConfig.java
index 0f468db..6c42cf6 100644
--- a/src/test/java/test/TestConfig.java
+++ b/src/test/java/test/TestConfig.java
@@ -1,106 +1,76 @@
package test;
-import com.imyeyu.config.BaseConverter;
import com.imyeyu.config.ConfigLoader;
import com.imyeyu.io.IO;
import org.junit.jupiter.api.Test;
import java.io.File;
-import java.lang.reflect.Field;
-import java.util.Map;
/**
+ * 测试 ConfigLoader - SnakeYAML 简洁封装
+ *
* @author 夜雨
- * @version 2024-04-25 11:13
+ * @version 2026-01-12
*/
public class TestConfig {
private static final String FILE_PATH = "test.yaml";
- public ConfigLoader load(Class clazz) {
- ConfigLoader loader = new ConfigLoader<>(FILE_PATH, clazz);
- loader.load();
- return loader;
- }
-
- public ConfigLoader reload(Class clazz) {
+ @Test
+ public void testSimpleLoad() {
+ // 删除旧文件,从资源重新创建
IO.destroy(new File(FILE_PATH));
- return load(clazz);
+
+ // 创建加载器
+ ConfigLoader loader = new ConfigLoader<>("test.yaml", FILE_PATH, Config.class);
+
+ // 加载配置
+ Config config = loader.load();
+
+ // 验证加载正确
+ assert config.getStr().equals("str value");
+ assert config.getNum() == 123;
+ assert config.isBool() == true;
+ assert config.getEnumTest() == Config.EnumTest.TEST2;
+
+ System.out.println("✓ 加载测试通过");
}
@Test
- public void testLoad() {
- ConfigLoader