用Go实现yaml文件节点动态解析

news2024/11/26 23:47:19

1.摘要

在大多数Go语言项目中, 配置文件通常为yaml文件格式, 在文件中可以设置项目中可灵活配置的各类参数, 通常这类参数都是比较固定的, 可以将其映射为对应的结构体在项目中进行使用, 如果需要调整参数时, 只需要增减结构体参数字段内容即可。

但同时还存在另外一种情况, 例如: 在ubuntu系统中, netplan网卡配置文件同样也是yaml格式文件(配置文件在/etc/netplan目录下), 但该配置文件的节点内容可能是动态变化的, 例如:当dhcp开启或关闭时, 其节点展示的内容差异会很大, 要动态读取和写入对应的节点内容使用结构体已无法满足实际需求。本章节主要分享一种动态解析yaml格式文件节点内容的相关知识。

2.静态解析方法

首先还是回顾一下静态解析的方法, 假如我们的yaml配置文件内容如下:

database:
  mysql:
    db_host: 192.168.201.200
    db_user: root
    db_port: 3306
    db_name: datas

以上是一个关于数据库连接方式的节点定义, 在Go代码中, 我们定义一个综合结构体:

type (
  Config struct {
    Database DatabaseConfig
  }
​
  DatabaseConfig struct {
    Mysql MysqlConfig
  }
​
  MysqlConfig struct {
    DbHost     string `mapstructure:"db_host"`
    DbUser     string `mapstructure:"db_user"`
    DbPassword string
    DbPort     string `mapstructure:"db_port"`
    DbName     string `mapstructure:"db_name"`
  }
)

在以上配置中, database是根节点, 子节点是mysql, 在子节点下有四个叶子节点db_host、db_user、db_port和db_name。在结构体中, MysqlConfig是代表叶子节点结构, 使用mapstructure关联了yaml配置文件中的字段名, 然后使用DatabaseConfig包含MysqlConfig, 使用Config包含DatabaseConfig, 与yaml文件中的节点层级一一对应。

在应用过程中也比较简单, 只需要以下代码即可将yaml配置文件中的内容解析到结构体:

var cfg Config
if err := unmarshal(&cfg); err != nil {
    return nil, err
}

3.动态解析方法

动态解析方法分为读和写两种方式, 在正式使用前,需要在工程中导入和同步:import "gopkg.in/yaml.v3", 然后我们看一下一个简单的网卡配置文件结构,如图:

在该配置文件中, 当dhcp4内容为true时,表示自动获取IP, 那么下面的addresses和name servers将是空的; 当dhcp4为false时, 下面的字段内容跟图中的一致, 并且ens22这个是网卡名称, 这个没有固定的名字, 网卡名称是动态变化的, 因此无法通过定义固定的结构体层级来解析内容。

看一段下面的代码:

import "gopkg.in/yaml.v3"
func main() {
  var err error
  var RootNode yaml.Node
  configs, _ := os.ReadFile(rootPath)
  err = yaml.Unmarshal(configs, &RootNode)
  if err != nil {
    log.Panic(err)
    return
  }
}

在上面的代码片段中, 我们没有定义任何结构体, 只定义了一个变量RootNode, 其类型为: yaml.Node, 然后使用通过os.ReadFile()函数读取网卡配置文件内容, 保存到变量configs中, 接着调用yaml.Unmarshal()函数将configs中的内容反序列化到RootNode变量中,那么RootNode变量中到底是一个怎样的结构呢? 实际上在RootNode变量中已经保存了配置文件所有节点的名称和内容, 通过Key:Value的方式在内存中保存。例如:

网卡配置文件中,根节点名为network, 所以network就作为一个key保存, 而network下面的所有内容就作为value保存, 看下内存的分布情况:

从上图中可以看到, 标号1的部分保存了1个元素, 这个元素实际上就是network, 在yaml节点中, 所有的内容都是保存在Content字段中, 所以在整个结构中, 可以递归的遍历Content内容来查找整个yaml文件中的所有节点名称和内容。

从上图的标号2中,我们可以看到network节点内容实际上被标记为!!map类型, 上面已经讲过, 节点的名称和内容都是通过key:value的类型在内存中保存, 所以其Content内容中有2个元素,一个元素是network节点名称,也就是Key名, 另一个元素就是network的value部分。那么这里有个问题, 在编写程序遍历的过程中, 如何区分key名和值的内容呢? 这里我们继续点开network树状结构,看一下key的内容,如图:

从上图可以看到, 实际上不管是Key还是Value,只要是字符串,其Tag内容都为"!!str", 同理,int内容其tag为"!!int", bool内容其tag为"!!bool", 如果某个字段的内容保存了多条内容, 那么该字段的Tag类型是"!!seq",例如配置文件中的DNS内容可以有多个,那么其字段的Tag内容就是!!seq, 如图:

在Go工程中,我们可以写一个递归函数专门查找Content内容,代码如下:

func findNode(n *yaml.Node, key string) *yaml.Node {
  switch n.Kind {
  case yaml.MappingNode:
    for i := 0; i < len(n.Content); i++ {
      if n.Content[i].Value == key {
        return n.Content[i+1]
      }
    }
    for i := 0; i < len(n.Content); i++ {
      if found := findNode(n.Content[i], key); found != nil {
        return found
      }
    }
  case yaml.SequenceNode:
    for i := 0; i < len(n.Content); i++ {
      if found := findNode(n.Content[i], key); found != nil {
        return found
      }
    }
  default:
    for i := 0; i < len(n.Content); i++ {
      if found := findNode(n.Content[i], key); found != nil {
        return found
      }
    }
  }
  return nil
}

在上面的代码中, yaml库使用yaml.MappingNode来识别Tag为"!!map"的内容;使用yaml.SequenceNode来识别Tag为"!!seq"的内容。注意: 上面findNode()函数只负责递归查找, 如果要读取或写入指定的节点内容, 我们仍然要控制节点的操作过程,例如: 我们要保存配置文件中addresses的内容, 当通过findNode函数找到address后,需要继续判断并获取值的内容,代码如下:

if foundObj.Kind != yaml.SequenceNode {
  err = errors.New("检测到网卡配置选项addresses的内容类型有误,请统一格式,IP地址前面加上-符号")
  return nil, err
}
if len(foundObj.Content) == 0 {
  err = errors.New("网卡配置选项addresses的内容为空,请确保网卡配置文件中的该参数至少有一条IP信息")
  return nil, err
}
if len(foundObj.Content) > 1 {
  err = errors.New("网卡配置选项addresses的内容不能存在多个,网卡配置信息存在格式错误")
  return nil, err
}
addressInfo := foundObj.Content[0]
if addressInfo.Tag != "!!str" {
  err = errors.New("检测到网卡配置选项addresses的内容不是字符串,网卡配置信息存在格式错误")
  return nil, err
}
totalInfo = append(totalInfo, addressInfo.Value)

同理, 如果要改变addresses内容, 只需要找到对应的Key, 改变其Value的内容即可,以下是参考代码:

if node.Kind != yaml.SequenceNode {
  err = errors.New("检测到网卡配置选项addresses的内容类型有误,请统一格式,IP地址前面加上-符号")
  return err
}
if len(node.Content) == 0 {
  err = errors.New("网卡配置选项addresses的内容为空,请确保网卡配置文件中的该参数至少有一条IP信息")
  return err
}
if len(node.Content) > 1 {
  err = errors.New("网卡配置选项addresses的内容不能存在多个,网卡配置信息存在格式错误")
  return err
}
addressInfo := node.Content[0]
if addressInfo.Tag != "!!str" {
  err = errors.New("检测到网卡配置选项addresses的内容不是字符串,网卡配置信息存在格式错误")
  return err
}
// NodeContent是从外面传参进来的修改的值,类型为list,默认只有一个元素,将其赋给value即可改变配置文件内容
addressInfo.Value = NodeContent[0].(string)

最终修改后的内容已经保存在我们上面定义的变量RootNode中,最后通过以下代码写回文件即可:

// 打开网卡文件
f, err := os.Create(NetcardFilePath)
if err != nil {
  logging.Log.Errorf("%T %s", err, err)
  return err
}
defer f.Close()
​
// 将节点内容序列化
d, err := yaml.Marshal(&RootNode)
if err != nil {
  logging.Log.Errorf("%T %s", err, err)
  return err
}
​
// 将序列化内容写入文件
if _, err = f.Write(d); err != nil {
  logging.Log.Errorf("%T %s", err, err)
  return err
}
​
if err = f.Sync(); err != nil {
  logging.Log.Errorf("%T %s", err, err)
  return err
}

4.总结

从上面的例子我们可以看到, 定义一个变量var RootNode yaml.Node, 无论是读取还是写入, 始终在操控yaml.Node中的树状结构, 树状结构存放map结构的方式是按照: key-value-key-value-key-value...交替顺序的方式存放, 因此在循环遍历的时候要注意,使用以下代码来取出节点名称和对应的值,也可以传入一个节点名称来查找指定节点和对应的值,代码如下:

for i := 0; i < len(found); i += 2 {
    content := found[i].Value
    if content == NodeName {
      node = found[i+1]
      break
    }
}

其中,found是调用上面函数findNode()查找到的节点位置,NodeName是需要查找的节点。其中found[i+1]可以取到所有的Key内容,found[i].Value可以取到对应的值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1207925.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PG】PostgreSQL高可用方案repmgr部署(非常详细)

目录 简介 1 概述 1.1 术语 1.2 组件 1.2.1 repmgr 1.2.2 repmgrd 1.3 Repmgr用户与元数据 2 安装部署 2.0 部署环境 2.1 安装要求 2.1.1 操作系统 2.1.2 PostgreSQL 版本 2.1.3 操作系统用户 2.1.4 安装位置 2.1.5 版本要求 2.2 安装 2.2.1 软件包安装 2.2…

git分支管理以及不同git工作流对比

0、 单人开发场景 单人开发可能会出现的场景之一 如果多人协同开发我们则需要使用更加专业的工具Git&#xff08;分布式版本控制&#xff09; 1、多人协同工作使用git会出现什么问题? 代码冲突&#xff1a; 问题&#xff1a; 当多个开发者同时修改同一文件或同一行代码时…

embedding的综述

1 一文读懂Embedding的概念&#xff0c;以及它和深度学习的关系 one-hot 变成地位稠密的向量&#xff0c;降维 什么是词嵌入&#xff1a;讲词汇表中的词或者词语映射成固定长度的向量。 具体过程&#xff1a; one-hot变成低维连续的向量 语义相近的词语&#xff0c;词语赌…

大模型的实践应用6-百度文心一言的基础模型ERNIE的详细介绍,与BERT模型的比较说明

大家好,我是微学AI,今天给大家讲一下大模型的实践应用6-百度文心一言的基础模型ERNIE的详细介绍,与BERT模型的比较说明。在大规模语料库上预先训练的BERT等神经语言表示模型可以很好地从纯文本中捕获丰富的语义模式,并通过微调的方式一致地提高各种NLP任务的性能。然而,现…

英伟达中国特供芯片是缩水版;华为 Mate60 Pro 国产零件价值占比 47%丨 RTE 开发者日报 Vol.84

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

这个双11,谁赚了?

双11落幕&#xff0c;很多品牌迎来一年中最重要的一次生意爆发&#xff0c;但作为普通消费者&#xff0c;还是能感受到今年双11的消费氛围减弱了&#xff0c;一方面&#xff0c;电商大促驱向常态化&#xff0c;双11不一定是全年最低价&#xff0c;“有需要再买”的心态越来越多…

人工智能学院承办南山区区块链公益职业技能培训

11月4日&#xff0c;南山区人力资源局主办、深圳职业技术大学承办的2023年南山区公益职业技能培训项目——区块链技术应用项目&#xff0c;于当天在深圳职业技术大学西丽湖校区图书馆西厅正式开班。此次培训将持续至11月18日。南山区人力资源局职业能力建设科科长张仁勇、人工智…

微服务简单理解与快速搭建

分布式和微服务 含义 微服务架构 微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法&#xff0c;每个服务运行在自己的进程中&#xff0c;服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服…

解密网络世界的秘密——Wireshark Mac/Win中文版网络抓包工具

在当今数字化时代&#xff0c;网络已经成为了人们生活和工作中不可或缺的一部分。然而&#xff0c;对于网络安全和性能的监控和分析却是一项重要而又复杂的任务。为了帮助用户更好地理解和解决网络中的问题&#xff0c;Wireshark作为一款强大的网络抓包工具&#xff0c;应运而生…

springboot+maven多环境动态配置,以及编译失败的解决方案

一、前言 在我们的项目开发过程中一般会有多套的环境&#xff0c;比如比较常见的会有三套&#xff1a; dev &#xff08;研发环境&#xff09;&#xff0c;test(测试环境)&#xff0c;prod&#xff08;生产环境&#xff09;。 application.yml 是主配置文件&#xff0c;当在不…

Redis 事务特性、原理、具体命令操作全方位诠释 —— 零基础可学习

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信您对这篇博客也感兴趣o (ˉ▽ˉ&#xff1b;) &#x1f4dc;redis和缓存及相关问题和解决办法 什么是缓存预热、缓存穿透、缓存雪崩、缓存击穿 目录 1、复习 MySQl 事务的特性 2、Redis 事务特性 2.1、原子…

Debug知识点解析!超实用教程

一、Debug简介 二、IDEA中的Debug步骤 2.1 步过调试按钮(F8) 2.2 步入调试按钮(F7) 2.3 强制步入调试按钮(Alt Shift EZ) 2.4 步出调试按钮(Shift F8) 2.5 回退断点 2.6 运行到光标处&#xff08;F9&#xff09; 2.7 计算表达式按钮(Alt F8) 三、条件断点 在断点处右…

Project IDX简介——这是一项改进全栈、多平台应用程序开发的试验

如今&#xff0c;将应用程序从零开发到生产环境&#xff08;尤其是在移动、网络和桌面平台上运行良好的应用程序&#xff09;感觉就像构建一台 Rube Goldberg 机器。您必须在无尽的复杂性海洋中航行&#xff0c;将各种技术堆栈粘合在一起&#xff0c;以引导、编译、测试、部署和…

基于逐次变分模态分解(SVMD)联合小波阈值去噪

代码原理 逐次变分模态分解 (Iterative Variational Mode Decomposition, IVMD) 是一种信号分解方法&#xff0c;它可以将一个时域信号分解为若干个本征模态函数&#xff08;Intrinsic Mode Functions, IMF&#xff09;。它通过迭代寻找信号的本征模态函数和残差部分&#xff…

Ladybug 全景相机, 360°球形成像,带来全方位的视觉体验

360无死角全景照片总能给人带来强烈的视觉震撼&#xff0c;有着大片的既视感。那怎么才能拍出360球形照片呢&#xff1f;它的拍摄原理是通过图片某个点位为中心将图片其他部位螺旋式、旋转式处理&#xff0c;从而达到沉浸式体验的效果。俗话说“工欲善其事&#xff0c;必先利其…

.net core中前端vue HTML5 History 刷新页面404问题

放到启动的应用程序的最后面 app.Run(async (context) > {context.Response.ContentType "text/html";await context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "index.html")); });https://blog.csdn.net/lee576/article/details/88355…

强化学习:原理与Python实战||一分钟秒懂人工智能对齐

文章目录 1.什么是人工智能对齐2.为什么要研究人工智能对齐3.人工智能对齐的常见方法延伸阅读 1.什么是人工智能对齐 人工智能对齐&#xff08;AI Alignment&#xff09;指让人工智能的行为符合人的意图和价值观。 人工智能系统可能会出现“不对齐”&#xff08;misalign&…

647. 回文子串 516.最长回文子序列

647. 回文子串 题目&#xff1a; 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串&#xff0c;即使是由相…

ubuntu18.04配置Java环境与安装RCS库

一、安装包 安装包 二、JAVA环境 java无需安装&#xff0c;只需要下载解压&#xff0c;然后配置正确的路径到环境变量种即可使用。 1.创建文件JAVA mkdir JAVA 2.将安装包复制到该文件夹下&#xff0c;并解压缩 tar -zxvf tar -zxvf jdk1.8.0_191.tar.gz 3.在home路径下…

ubuntu小技巧30--23.10桌面版安装钉钉启动报错undefined symbol: FT_Get_Color_Glyph_Layer

ubuntu小技巧30-- 23.10桌面版安装钉钉启动报错undefined symbol: FT_Get_Color_Glyph_Layer 介绍解決方法说明 介绍 近期在电脑上安装了 ubuntu 23.10桌面版本, 安装最新版钉钉后无法正常打开软件&#xff0c;报错 undefined symbol: FT_Get_Color_Glyph_Layer &#xff0c;具…