如何使用Java读取YAML文件中的Map of Lists
我明白你的问题——你期望Jackson把YAML里每个键对应的条目都解析成List,但默认行为下,单个元素的列表会被直接解析成元素本身,导致你的Map里混合了单个值和列表,不是统一的Map<String, List<?>>结构。下面我来帮你解决这个问题:
首先先确认你的YAML内容(方便参考):
accept: - "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8" accept-encoding: - "gzip, deflate" accept-language: - "en-GB,en-US;q=0.9,en;q=0.8" - "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7,lt;q=0.6" connection: - close - keep-alive dnt: - 1 referer: - "https://www.google.com/" - "https://www.yahoo.com" - "https://www.bing.com/" upgrade-insecure-requests: - 1 - 0 x-real-ip: ~
你的原始代码用了Map.class作为目标类型,这是问题的关键:Jackson没法知道Map的值应该是List类型,所以对于只有单个元素的列表(比如accept字段),它会直接把这个元素(字符串)放到Map的值里,而不是封装成List。
解决方案1:使用TypeReference明确指定泛型类型
通过TypeReference告诉Jackson,我们要解析成Map<String, List<Object>>类型,这样它就会把所有条目都解析成List,哪怕只有一个元素:
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.io.File; import java.util.List; import java.util.Map; public class YamlParser { public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); // 用TypeReference指定泛型类型 Map<String, List<Object>> user = mapper.readValue( new File("/home/a/headers.yaml"), new TypeReference<Map<String, List<Object>>>() {} ); System.out.println(ReflectionToStringBuilder.toString(user, ToStringStyle.MULTI_LINE_STYLE)); } }
这个方法的好处是类型安全,明确告诉Jackson我们期望的结构,不会有意外的类型转换问题。
解决方案2:全局开启ACCEPT_SINGLE_VALUE_AS_ARRAY特性
如果你希望Jackson全局把所有单个元素的数组/列表都解析成List,可以开启DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY配置:
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.io.File; import java.util.List; import java.util.Map; public class YamlParser { public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper(new YAMLFactory()) // 开启特性:将单个值视为数组/列表 .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); // 这里可以用Map.class,但更推荐还是用TypeReference确保类型正确 Map<String, List<Object>> user = mapper.readValue( new File("/home/a/headers.yaml"), new TypeReference<Map<String, List<Object>>>() {} ); System.out.println(ReflectionToStringBuilder.toString(user, ToStringStyle.MULTI_LINE_STYLE)); } }
这个方法适合你有很多地方需要这种解析行为的场景,但要注意它会影响所有的反序列化操作,所以如果有其他地方不需要把单个值当成列表,可能会出问题。
关于x-real-ip: ~的处理
YAML里的~代表null,解析后对应的Map值会是null(因为原字段就是null,不是一个空列表),这是符合预期的行为。如果你希望把null转换成空列表,可以额外配置Jackson的ACCEPT_NULL_AS_EMPTY_COLLECTION特性,不过这是可选的,根据你的需求来。
内容的提问来源于stack exchange,提问作者Prostitutor

