User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Java   ›   正文
Java

Java—使用SnakeYAML解析与序列化YAML

2023-06-12 19:01:38
1017  0 1
参考目录 隐藏
1) 入口点
2) 加载YAML文档
3) 基本用法
4) 自定义类型解析
5) 隐式类型
6) 嵌套对象
7) 类型安全的集合
8) 载入多个文件
9) 生成YAML文件
10) 基本用法
11) 定义Java对象
12) 案例

阅读完需:约 9 分钟

Springboot底层-snakeyaml的使用,就是用来操作YAML的

依赖

<dependencies>
  ...
  <dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.3.0</version>
  </dependency>
  ...
</dependencies>

入口点

该YAML类是API的入口点:

Yaml yaml = new Yaml()

由于实现不是线程安全的,因此不同的线程必须具有自己的Yaml实例。

加载YAML文档

基本用法

SnakeYAML支持从String或InputStream加载文档,我们从定义一个简单的YAML文档开始,然后将文件命名为customer.yaml:

firstName: "John"
lastName: "Doe"
age: 20

现在,我们将使用Yaml类来解析上述YAML文档:

Yaml yaml = new Yaml();
InputStream inputStream = this.getClass()
  .getClassLoader()
  .getResourceAsStream("customer.yaml");
Map<String, Object> obj = yaml.load(inputStream);
System.out.println(obj);

上面的代码生成以下输出:

{firstName=John, lastName=Doe, age=20}

默认情况下,load()方法返回一个Map对象。查询Map对象时,我们需要事先知道属性键的名称,否则容易出错。更好的办法是自定义类型。

自定义类型解析

SnakeYAML提供了一种将文档解析为自定义类型的方法

让我们定义一个Customer类,然后尝试再次加载该文档:

public class Customer {
 
    private String firstName;
    private String lastName;
    private int age;
 
    // getters and setters
}

现在来加载:

Yaml yaml = new Yaml();
InputStream inputStream = this.getClass()
 .getClassLoader()
 .getResourceAsStream("customer.yaml");
Customer customer = yaml.load(inputStream);

还有一种方法是使用Constructor:

Yaml yaml = new Yaml(new Constructor(Customer.class));

隐式类型

如果没有为给定属性定义类型,则库会自动将值转换为隐式type。

1.0 -> Float
42 -> Integer
2009-03-30 -> Date

让我们使用一个TestCase来测试这种隐式类型转换:

@Test
public void whenLoadYAML_thenLoadCorrectImplicitTypes() {
   Yaml yaml = new Yaml();
   Map<Object, Object> document = yaml.load("3.0: 2018-07-22");
  
   assertNotNull(document);
   assertEquals(1, document.size());
   assertTrue(document.containsKey(3.0d));   
}

嵌套对象

SnakeYAML 支持嵌套的复杂类型。

让我们向“ customer.yaml”添加“ 联系方式”  和“ 地址” 详细信息,并将新文件另存为customer_with_contact_details_and_address.yaml.。

现在,我们将分析新的YAML文档:

firstName: "John"
lastName: "Doe"
age: 31
contactDetails:
   - type: "mobile"
     number: 123456789
   - type: "landline"
     number: 456786868
homeAddress:
   line: "Xyz, DEF Street"
   city: "City Y"
   state: "State Y"
   zip: 345657

我们来更新java类:

public class Customer {
    private String firstName;
    private String lastName;
    private int age;
    private List<Contact> contactDetails;
    private Address homeAddress;    
    // getters and setters
}

public class Contact {
    private String type;
    private int number;
    // getters and setters
}

public class Address {
    private String line;
    private String city;
    private String state;
    private Integer zip;
    // getters and setters
}

现在,我们来测试下Yaml#load():

@Test
public void
  whenLoadYAMLDocumentWithTopLevelClass_thenLoadCorrectJavaObjectWithNestedObjects() {
  
    Yaml yaml = new Yaml(new Constructor(Customer.class));
    InputStream inputStream = this.getClass()
      .getClassLoader()
      .getResourceAsStream("yaml/customer_with_contact_details_and_address.yaml");
    Customer customer = yaml.load(inputStream);
  
    assertNotNull(customer);
    assertEquals("John", customer.getFirstName());
    assertEquals("Doe", customer.getLastName());
    assertEquals(31, customer.getAge());
    assertNotNull(customer.getContactDetails());
    assertEquals(2, customer.getContactDetails().size());
     
    assertEquals("mobile", customer.getContactDetails()
      .get(0)
      .getType());
    assertEquals(123456789, customer.getContactDetails()
      .get(0)
      .getNumber());
    assertEquals("landline", customer.getContactDetails()
      .get(1)
      .getType());
    assertEquals(456786868, customer.getContactDetails()
      .get(1)
      .getNumber());
    assertNotNull(customer.getHomeAddress());
    assertEquals("Xyz, DEF Street", customer.getHomeAddress()
      .getLine());
}

类型安全的集合

当给定Java类的一个或多个属性是泛型集合类时,需要通过TypeDescription来指定泛型类型,以以便可以正确解析。

让我们假设一个 一个Customer拥有多个Contact:

firstName: "John"
lastName: "Doe"
age: 31
contactDetails:
   - { type: "mobile", number: 123456789}
   - { type: "landline", number: 123456789}

为了能正确解析,我们可以在顶级类上为给定属性指定TypeDescription :

Constructor constructor = new Constructor(Customer.class);
TypeDescription customTypeDescription = new TypeDescription(Customer.class);
customTypeDescription.addPropertyParameters("contactDetails", Contact.class);
constructor.addTypeDescription(customTypeDescription);
Yaml yaml = new Yaml(constructor);

载入多个文件

在某些情况下,单个文件中可能有多个YAML文档,而我们想解析所有文档。所述YAML类提供了一个LOADALL()方法来完成这种类型的解析。

假设下面的内容在一个文件中:

---
firstName: "John"
lastName: "Doe"
age: 20
---
firstName: "Jack"
lastName: "Jones"
age: 25

我们可以使用loadAll()方法解析以上内容,如以下代码示例所示:

@Test
public void whenLoadMultipleYAMLDocuments_thenLoadCorrectJavaObjects() {
    Yaml yaml = new Yaml(new Constructor(Customer.class));
    InputStream inputStream = this.getClass()
      .getClassLoader()
      .getResourceAsStream("yaml/customers.yaml");
 
    int count = 0;
    for (Object object : yaml.loadAll(inputStream)) {
        count++;
        assertTrue(object instanceof Customer);
    }
    assertEquals(2,count);
}

生成YAML文件

SnakeYAML 支持 将java对象序列化为yml。

基本用法

我们将从一个将Map <String,Object>的实例转储到YAML文档(String)的简单示例开始:

@Test
public void whenDumpMap_thenGenerateCorrectYAML() {
    Map<String, Object> data = new LinkedHashMap<String, Object>();
    data.put("name", "Silenthand Olleander");
    data.put("race", "Human");
    data.put("traits", new String[] { "ONE_HAND", "ONE_EYE" });
    Yaml yaml = new Yaml();
    StringWriter writer = new StringWriter();
    yaml.dump(data, writer);
    String expectedYaml = "name: Silenthand Olleander\nrace: Human\ntraits: [ONE_HAND, ONE_EYE]\n";
 
    assertEquals(expectedYaml, writer.toString());
}

上面的代码产生以下输出(请注意,使用LinkedHashMap的实例将保留输出数据的顺序):

name: Silenthand Olleander
race: Human
traits: [ONE_HAND, ONE_EYE]

定义Java对象

我们还可以选择将自定义Java类型转储到输出流中。

@Test
public void whenDumpACustomType_thenGenerateCorrectYAML() {
    Customer customer = new Customer();
    customer.setAge(45);
    customer.setFirstName("Greg");
    customer.setLastName("McDowell");
    Yaml yaml = new Yaml();
    StringWriter writer = new StringWriter();
    yaml.dump(customer, writer);        
    String expectedYaml = "!!com.baeldung.snakeyaml.Customer {age: 45, contactDetails: null, firstName: Greg,\n  homeAddress: null, lastName: McDowell}\n";
 
    assertEquals(expectedYaml, writer.toString());
}

生成内容会包含!!com.baeldung.snakeyaml.Customer,为了避免在输出文件中使用标签名,我们可以使用库提供的  dumpAs()方法。

因此,在上面的代码中,我们可以进行以下调整以删除标记:

yaml.dumpAs(customer, Tag.MAP, null);

案例

导出所有的接口

package com.linktopa.corex.auth

import com.linktopa.corex.util.logs
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.ApplicationListener
import org.springframework.context.annotation.Configuration
import org.springframework.context.event.ContextRefreshedEvent
import org.springframework.core.annotation.Order
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.nodes.Tag
import org.yaml.snakeyaml.representer.Representer
import java.io.File
import java.io.FileWriter
import java.util.stream.Collectors


/**
 * 控制器作家
 * @author admin
 * @date 2023/06/12
 * @constructor 创建[ControllerWriter]
 */
@Configuration
@Order(Int.MAX_VALUE)
class ControllerWriter (
    @Value("\${server.servlet.context-path}") var contextPath:String,
): ApplicationListener<ContextRefreshedEvent> {
    override fun onApplicationEvent(event: ContextRefreshedEvent) {
        val listMap= arrayListOf<ControllerSpecification>()
        val path=contextPath.ifEmpty { "" }
        // 去读所有的接口信息
        event.applicationContext.getBean(RequestMappingHandlerMapping::class.java).let {
            // 清除RequestMappingHandlerMapping自带的error接口
            it.handlerMethods.forEach handler@{handler->
                if(handler.key.methodsCondition.toString().length < 3 &&
                    (handler.value.method.name == "error" || handler.value.method.name == "errorHtml")){
                    return@handler
                }
                // 添加接口
                listMap.add(
                    ControllerSpecification(
                        handler.key.name?:"",
                        handler.key.pathPatternsCondition?.firstPattern?.patternString?.toString()?.RemoveSpace.let { condition ->
                            if (!condition.isNullOrEmpty()) {
                                condition
                            } else {
                                "/"
                            }
                        },
                        handler.key.methodsCondition.toString().revise.ifEmpty { "Request" },
                        handler.value.method.name
                    )
                )
            }
        }
        // 接口按规则分类
        val toList = listMap.stream().collect(Collectors.groupingBy { it.path.split("/")[1] }).toList();
        val group = mutableListOf<Group>()
        // 组装信息
        for(list in toList){
            group.add(Group(mutableMapOf<String, MutableList<ControllerSpecification>>().apply {
                this[list.first]=list.second
            }))
        }
        val route = Route(group)
        val global = Global(mutableListOf<Any>().apply {
            this.add(Context(path))
            this.add(route)
        })
        // 配置样式
        val options = DumperOptions()
        options.isExplicitStart = false
        options.isExplicitEnd = false
        options.isPrettyFlow = false
        options.isCanonical = false
        options.isAllowUnicode = true
        options.tags = HashMap()
        options.splitLines = true
        // 设置Representer,去掉类型信息
        val represent = Representer().apply {
            propertyUtils = this.propertyUtils
            // 消除类信息打印
            addClassTag(Context::class.java, Tag.MAP)
            addClassTag(Global::class.java, Tag.MAP)
            addClassTag(Route::class.java, Tag.MAP)
            addClassTag(ControllerSpecification::class.java, Tag.MAP)
        }
        // 写入yml文件
        val yaml = Yaml(represent,options)
        val fileName = "route.yml"
        val paths = this.javaClass.classLoader.getResource("")?.path?.split("target/classes")?.get(0).plus("src/main/resources/").plus(fileName)
        val file = File(paths)
        paths.logs
        // 不存在则写入
        if(!file.exists()){
            yaml.dump(global, FileWriter(paths))
        }
    }
}

/**
 * 参数去空格 去掉空格
 */
val String.RemoveSpace:String
    inline get() = this.replace(" ","")

/**
 * 去掉 [ ]
 */
val String.revise:String
    inline get() = this.RemoveSpace.drop(1).dropLast(1)

/**
 * 控制器规范
 * @author admin
 * @date 2023/06/12
 * @constructor 创建[ControllerSpecification]
 * @param [name] 名字
 * @param [path] 路径
 * @param [condition] 条件
 * @param [method] 方法
 */
data class ControllerSpecification(
    var name:String,
    var path:String,
    var condition:String,
    var method:String
);

/**
 * 集合
 * @author admin
 * @date 2023/06/12
 * @constructor 创建[group]
 * @param [prefix] 前缀
 * @param [list] 列表
 */
data class Group(var prefix:MutableMap<String,MutableList<ControllerSpecification>>)

/**
 * 路线
 * @author admin
 * @date 2023/06/12
 * @constructor 创建[route]
 * @param [route] 路线
 */
data class Route(var routes: MutableList<Group>)


/**
 * 上下文
 * @author admin
 * @date 2023/06/12
 * @constructor 创建[Context]
 * @param [context-path] 上下文路径
 */
data class Context(var `context-path`:String)

/**
 * 全球
 * @author admin
 * @date 2023/06/12
 * @constructor 创建[Global]
 * @param [global] 全球
 */
data class Global(var global: MutableList<Any>)

结果

global:
- {context-path: /api}
- routes:
  - prefix:
      work-permission:
      - {condition: GET, method: getById, name: '', path: '/work-permission/{id}'}
      - {condition: PUT, method: updateForPut, name: '', path: /work-permission}
      - {condition: DELETE, method: delete, name: '', path: /work-permission}
      - {condition: GET, method: page, name: '', path: /work-permission}
      - {condition: PATCH, method: update, name: '', path: /work-permission}
      - {condition: POST, method: create, name: '', path: /work-permission}
  - prefix:
      employee-role-bind:
      - {condition: PATCH, method: update, name: '', path: /employee-role-bind}
      - {condition: PUT, method: updateForPut, name: '', path: /employee-role-bind}
      - {condition: GET, method: getById, name: '', path: '/employee-role-bind/{id}'}
      - {condition: DELETE, method: delete, name: '', path: /employee-role-bind}
      - {condition: POST, method: create, name: '', path: /employee-role-bind}
      - {condition: GET, method: page, name: '', path: /employee-role-bind}
  - prefix:
      role:
      - {condition: PATCH, method: update, name: '', path: /role}
      - {condition: GET, method: page, name: '', path: /role}
      - {condition: DELETE, method: delete, name: '', path: /role}
      - {condition: GET, method: getById, name: '', path: '/role/{id}'}
      - {condition: POST, method: create, name: '', path: /role}
      - {condition: PUT, method: updateForPut, name: '', path: /role}
  - prefix:
      organization:
      - {condition: DELETE, method: delete, name: '', path: /organization}
      - {condition: PUT, method: updateForPut, name: '', path: /organization}
      - {condition: PATCH, method: update, name: '', path: /organization}
      - {condition: GET, method: getById, name: '', path: '/organization/{id}'}
      - {condition: POST, method: create, name: '', path: /organization}
      - {condition: GET, method: page, name: '', path: /organization}
  - prefix:
      employee:
      - {condition: PUT, method: updateForPut, name: '', path: /employee}
      - {condition: DELETE, method: delete, name: '', path: /employee}
      - {condition: PATCH, method: update, name: '', path: /employee}
      - {condition: GET, method: page, name: '', path: /employee}
      - {condition: POST, method: create, name: '', path: /employee}
      - {condition: GET, method: getById, name: '', path: '/employee/{id}'}
  - prefix:
      system-application:
      - {condition: DELETE, method: delete, name: 删除系统应用配置, path: /system-application}
      - {condition: POST, method: create, name: 添加系统应用配置, path: /system-application}
      - {condition: PATCH, method: update, name: 更新系统应用配置, path: /system-application}
      - {condition: GET, method: page, name: 分页查询, path: /system-application}
      - {condition: GET, method: get, name: 通过id查询, path: '/system-application/{id}'}

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

1 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 594187浏览

随机文章
MyBatis笔记7——typeAliases
5年前
Java—并发编程(七)JUC集合 – (5) ConcurrentSkipListMap
3年前
SpringCloud—Hystrix(二)自定义请求命令
5年前
SpringSecurity—OAuth 2(七) 令牌存入 Redis
5年前
LangChain+RAG—构建知识库(一)
1年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 594187 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付