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

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可以取到对应的值。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

文献阅读——Layered Costmaps for Context-Sensitive Navigation

摘要 许多导航系统&#xff0c;包括无处不在的ROS导航堆栈&#xff0c;在单个成本图上执行路径规划&#xff0c;其中大部分信息存储在单个网格中。这种方法在生成最小长度的无碰撞路径方面非常成功&#xff0c;但是当成本图中的值超出已占用或空闲空间时&#xff0c;它在动态的…

ATFX汇市:美国10月CPI数据来袭,通胀率料将进一步走低

ATFX汇市&#xff1a;本周二21:30&#xff0c;美国劳工部将公布10月未季调CPI年率&#xff0c;前值为3.7%&#xff0c;预期值3.3%&#xff1b;9月未季调核心CPI年率将于同一时间公布&#xff0c;前值为4.1%&#xff0c;预期值4.1%。机构预期美国名义通胀率将显著下降&#xff0…

EasyA正在帮助Sui为新一代Web3 App培养构建者

最近&#xff0c;我们采访了Phil和Dom Kwok&#xff0c;他们是兄弟也是Web3教育移动应用EasyA的共同创始人。这个教育app通过学习模块和编码挑战的形式&#xff0c;向开发人员教授有关不同区块链及其独特特性的知识。他们在十月初推出了他们的第一个Sui模块&#xff0c;并在随后…

Pikachu(皮卡丘靶场)初识XSS(常见标签事件及payload总结)

目录 1、反射型xss(get) 2、反射性xss(post) 3、存储型xss 4、DOM型xss 5、DOM型xss-x XSS又叫跨站脚本攻击&#xff0c;是HTML代码注入&#xff0c;通过对网页注入浏览器可执行代码&#xff0c;从而实现攻击。 ​ 1、反射型xss(get) Which NBA player do you like? 由…

hive更改表结构的时候报错

现象 FAILED: ParseException line 1:48 cannot recognize input near ADD COLUMN compete_company_id in alter table statement 23/11/14 17:59:27 ERROR org.apache.hadoop.hive.ql.Driver: FAILED: ParseException line 1:48 cannot recognize input near ADD COLUMN compe…

数据结构与算法【链表:一】Java实现

目录 链表 单向链表 哨兵链表 双向链表 环形链表 链表 链表是数据元素的线性集合&#xff0c;其每个元素都指向下一个元素&#xff0c;元素存储上并不连续。 随机访问性能 根据 index 查找&#xff0c;时间复杂度 O(n) 插入或删除性能 起始位置&#xff1a;O(1)结束位…

一文说清楚Openai的这波更新内容,大地震 一大波套壳公司倒闭

前几天Openai召开了首届的开发者大会&#xff0c;45分钟的会议&#xff0c;让千万用户感到兴奋&#xff0c;但是让万千的套壳的创业公司&#xff0c;却感觉如坐针毡。这次发布会发布了哪些功能&#xff1f;为什么会导致这种情况的发生&#xff1f;让我们接着往下讲 API升级且降…

【业务场景】长列表的处理

长列表的处理 1. 什么是长列表 在前端开发中&#xff0c;经常会遇到列表展示&#xff0c;如果列表项的数量比较多&#xff0c;我们一般选择采用分页的方式来进行处理 但传统的前后翻页方式只适用于后台的管理系统中&#xff0c;而在用户端、尤其是在移动端&#xff0c;为了保…

OSCNet: Orientation-Shared Convolutional Network for CT Metal Artifact Learning

OSCNet: 面向共享的CT金属伪影学习卷积网络 论文链接&#xff1a;https://ieeexplore.ieee.org/document/10237226 项目链接&#xff1a;https://github.com/hongwang01/OSCNet&#xff08;目前不会开源&#xff09; Abstract X射线计算机断层扫描(CT)已广泛应用于疾病诊断和…

计算机毕业设计选题推荐-记录生活微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

GB28181/GB35114国标平台LiveGBS适配国产信创环境,使用国产数据库达梦数据库、高斯数据库、瀚高数据库的配置方法...

1、如何配置切换信创达梦数据库&#xff1f; livecms.ini -> [db]下面添加配置如&#xff1a; ... [db] dialectdm url dm://SYSDBA:Aa12345678localhost:5236/livegbs 2、如何配置切换高斯数据库&#xff1f; livecms.ini -> [db]下面添加配置如&#xff1a; ... [db] d…

洗地机是智商税吗?洗地机有没有必要买?2023洗地机推荐

传统的扫地拖地方式不仅时间长&#xff0c;被毛孩子和萌娃制造的顽固污渍更是让人头痛不已&#xff0c;高效又有效的地面清洁方式成了我们最大的诉求。目前洗地机受到青睐&#xff0c;异常火爆&#xff0c;也成为一众清洁扫地的选择之一&#xff0c;那洗地机到底是不是智商税呢…

物联网AI MicroPython学习之语法 umqtt客户端

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; umqtt 介绍 模块功能: MQTT客户端功能 - 连线、断线、发布消息、订阅主题、KeepAlive等功能。 MQTT协议采用订阅者/发布者模式&#xff0c;协议中定义了消息服务质量&#xff08;Quality of Service&#x…

Winform / WPF 自定义控件 —— IPV4 地址输入框

在开始阅读本文之前&#xff0c;如果您有学习创建自定义控件库并在其他项目中引用的需求&#xff0c;请参考&#xff1a;在Visual Studio中创建自定义Winform控件库并在其他解决方案中引用https://blog.csdn.net/YMGogre/article/details/126508042 0、引言 Winform / WPF 框架…

docker命令大全

1、查看Docker 容器占用的空间 docker ps -s2、查看所有容器 docker ps -a3、启动、关闭、重启一个已存在的容器 docker start <容器ID> docker stop <容器ID> docker restart <容器ID> 4、进入容器&#xff0c;退出终端的时候不会关闭container的ma…

线程池的使用

线程池的作用 降低线程创建和销毁的开销&#xff1a;线程的创建和销毁是比较昂贵的操作。通过使用线程池&#xff0c;可以避免频繁地创建和销毁线程&#xff0c;而是复用线程池中已经存在的线程&#xff0c;从而降低了开销。 控制并发度&#xff1a;通过控制线程池中线程的数量…

(个人实测保熟)记录Tecnomatix Process Simulate 16.1.2官方安装包及授权许可配置教程(Win10环境)

Tecnomatix Process Simulate 16是一款由西门子公司推出的一款工艺仿真解决方案,是虚拟制造仿真领域的领先解决方案,可帮助您数字化制造以及将创新思想和原材料转变为变革性产品的过程。在网上找了一些盗版的安装包&#xff0c;就很离谱。直接提示本"无法打开此安装程序包…

spring-cloud-alibaba-nacos

spring cloud nacos 安装和启动nacos # 解压nacos安装包 # tar -zvxf nacos-server-1.4.1.tar.gz# nacos默认是以集群的模式启动&#xff0c;此处先用单机模式 # cd /usr/local/mysoft/nacos/bin # sh startup.sh -m standalone# nacos 日志 # tail -f /usr/local/mysoft/na…

智慧隧道:TSINGSEE青犀远程视频AI智能监管平台保障隧道施工安全

一、背景与需求分析 随着我国交通运输量的增加以及新基建的不断规划和建设&#xff0c;公路建设工作也在持续开展中。高速公路隧道属于特殊构造段&#xff0c;因为隧道空间小&#xff0c;密闭性强&#xff0c;施工过程中一旦发生火灾、事故等&#xff0c;将带来重大人员伤亡和…