Java 查到内存中的List<Map<String,Object>> 中的字段属性是下划线,需要转成另一个实体类,怎么处理?


转换的工具类 ConvertUtil

package com.mhd.springbootinit.utils;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Description:
 * @title: ConvertUtil
 * @Author mhd
 * @Date: 2025/4/15 22:08
 * @Version 1.0
 */
@Slf4j
public class ConvertUtil {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    static {
        // 允许访问私有字段
        objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        // 忽略未知属性
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 启用不区分大小写的属性匹配
        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
    }

    /**
     * 将 List<Map<String, Object>> 映射到实体类的List
     * @param mapList
     * @param clazz
     * @return
     * @param <T>
     */
    public static <T> List<T> convertMapListToObjectList(List<Map<String, Object>> mapList, Class<T> clazz) {
        List<T> res = new ArrayList<>();
        ObjectMapper mapper = new ObjectMapper();
        // 启用不区分大小写的属性匹配
        mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
        for (Map<String, Object> map : mapList) {
            try {
                T instance = mapper.convertValue(map, clazz);
                res.add(instance);
            } catch (IllegalArgumentException e) {
                // 处理转换异常
                e.printStackTrace();
            }
        }
        return res;
    }

}

测试类

@SpringBootTest
public class Test01 {

    @Test
    void test02() {

        List<Map<String, Object>> mapList = new ArrayList<>();
        Map<String, Object> map = new HashMap<>();
        map.put("SEC_CODE", "testName");
        map.put("A_COUNT", 1);
        map.put("ID", 1);
        mapList.add(map);

        List<UserTestVO> userList = ConvertUtil.convertMapListToObjectList(mapList, UserTestVO.class);

    }
}

踩坑注意

由于查到的原始数据的字段名是确定了的,大小写固定的,但是我们要转换的目标类 Class 对象默认使用 getter/setter 方法推断属性名,导致字段名与 Map 中的键不匹配。

踩坑代码
@Test
    void test02() {

        List<Map<String, Object>> mapList = new ArrayList<>();
        Map<String, Object> map = new HashMap<>();
        map.put("SEC_CODE", "testName");
        map.put("A_COUNT", 1);
        map.put("ID", 1);
        mapList.add(map);

        List<UserTestVO> userList = ConvertUtil.convertMapListToObjectList(mapList, UserTestVO.class);

    }


@Data
public class UserTestVO implements Serializable {
    private static final long serialVersionUID = 7004030192266543336L;
    /**
     * id
     */
    private Long ID;

    /**
     * 用户昵称
     */
    private String USER_NAME;

    private String SEC_CODE;

    private Integer A_COUNT;
}
报错信息:
java.lang.IllegalArgumentException: Unrecognized field "SEC_CODE" (class com.mhd.springbootinit.model.vo.UserTestVO), not marked as ignorable (4 known properties: "a_COUNT", "id", "user_NAME", "sec_CODE"])
 at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: com.mhd.springbootinit.model.vo.UserTestVO["SEC_CODE"])
	at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4393)
	at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:4324)
	at com.mhd.springbootinit.utils.ConvertUtil.convertMapListToObjectList(ConvertUtil.java:24)
	at com.mhd.springbootinit.Test01.test02(Test01.java:75)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "SEC_CODE" (class com.mhd.springbootinit.model.vo.UserTestVO), not marked as ignorable (4 known properties: "a_COUNT", "id", "user_NAME", "sec_CODE"])
 at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: com.mhd.springbootinit.model.vo.UserTestVO["SEC_CODE"])
	at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:1127)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:2023)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1700)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1678)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:319)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176)
	at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4388)
	... 72 more

问题分析

要解决将 Map 转换为对象时出现的字段无法识别问题,需确保字段名称正确映射。

问题原因

Jackson 在反序列化时默认使用 getter/setter 方法推断属性名,导致字段名与 Map 中的键不匹配。例如,SEC_CODE 字段生成的 getter 为 getSEC_CODE(),Jackson 可能将其属性名视为 sec_CODE,而 Map 中的键为 SEC_CODE,导致不匹配。

解决方案

方法 1:使用 @JsonProperty 注解

UserTestVO 的字段上添加 @JsonProperty 注解,明确指定 JSON 字段名。

@Data
public class UserTestVO implements Serializable {
    private static final long serialVersionUID = 7004030192266543336L;
    
    @JsonProperty("ID")
    private Long ID;

    @JsonProperty("USER_NAME")
    private String USER_NAME;

    @JsonProperty("SEC_CODE")
    private String SEC_CODE;

    @JsonProperty("A_COUNT")
    private Integer A_COUNT;
}

方法 2:配置 ObjectMapper 使用字段访问并忽略大小写

修改 ConvertUtil 中的 ObjectMapper 配置,使其直接使用字段名并忽略大小写敏感。

public class ConvertUtil {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    static {
        // 允许访问私有字段
        objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        // 忽略未知属性
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 启用不区分大小写的属性匹配
        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
    }

    public static <T> List<T> convertMapListToObjectList(List<Map<String, Object>> mapList, Class<T> clazz) {
        return mapList.stream()
                .map(map -> objectMapper.convertValue(map, clazz))
                .collect(Collectors.toList());
    }
}

说明

  • 方法 1 通过注解显式指定映射关系,确保键名精确匹配,推荐用于明确字段映射的场景。
  • 方法 2 调整全局配置,使 Jackson 更灵活地处理字段名,适用于多个类或无法修改实体类的情况。

选择其中一种方法即可解决问题。推荐使用 方法 1,因为它更明确且不易受全局配置影响。

SpringBoot
JAVA-技能点