Go 处理yaml类型的配置文件

先说一下,这里用到了很多关于反射类型的功能,可能刚开始看代码,如果对反射不熟悉的可能会不是非常清晰,但是同时也是为了更好的理解golang中的反射,同时如果后面想在代码中可以直接从我的git地址get:
go get github.com/pythonsite/config_yaml
直接上代码:

// 可以用于处理读yaml格式的配置文件,同时也可以用于理解golang中的反射
package config_yaml

import (
    "strings"
    "errors"
    "io/ioutil"
    "gopkg.in/yaml.v2"
    "reflect"
    "fmt"
    "strconv"
)

type ConfigEngine struct {
    data map[interface{}]interface{}
}

// 将ymal文件中的内容进行加载
func (c *ConfigEngine) Load (path string) error {
    ext := c.guessFileType(path)
    if ext == "" {
        return errors.New("cant not load" + path + " config")
    }
    return c.loadFromYaml(path)
}

//判断配置文件名是否为yaml格式
func (c *ConfigEngine) guessFileType(path string) string {
    s := strings.Split(path,".")
    ext := s[len(s) - 1]
    switch ext {
    case "yaml","yml":
        return "yaml"
    }
    return ""
}

// 将配置yaml文件中的进行加载
func (c *ConfigEngine) loadFromYaml(path string) error {
    yamlS,readErr := ioutil.ReadFile(path)
    if readErr != nil {
        return readErr
    }
    // yaml解析的时候c.data如果没有被初始化,会自动为你做初始化
    err := yaml.Unmarshal(yamlS, &c.data)
    if err != nil {
        return errors.New("can not parse "+ path + " config" )
    }
    return nil
}

// 从配置文件中获取值
func (c *ConfigEngine) Get(name string) interface{}{
    path := strings.Split(name,".")
    data := c.data
    for key, value := range path {
        v, ok := data[value]
        if !ok {
            break
        }
        if (key + 1) == len(path) {
            return v
        }
        if reflect.TypeOf(v).String() == "map[interface {}]interface {}"{
            data = v.(map[interface {}]interface {})
        }
    }
    return nil
}

// 从配置文件中获取string类型的值
func (c *ConfigEngine) GetString(name string) string {
    value := c.Get(name)
    switch value:=value.(type){
    case string:
        return value
    case bool,float64,int:
        return fmt.Sprint(value)
    default:
        return ""
    }
}

// 从配置文件中获取int类型的值
func (c *ConfigEngine) GetInt(name string) int {
    value := c.Get(name)
    switch value := value.(type){
    case string:
        i,_:= strconv.Atoi(value)
        return i
    case int:
        return value
    case bool:
        if value{
            return 1
        }
        return 0
    case float64:
        return int(value)
    default:
        return 0
    }
}

// 从配置文件中获取bool类型的值
func (c *ConfigEngine) GetBool(name string) bool {
    value := c.Get(name)
    switch value := value.(type){
    case string:
        str,_:= strconv.ParseBool(value)
        return str
    case int:
        if value != 0 {
            return true
        }
        return false
    case bool:
        return value
    case float64:
        if value != 0.0 {
            return true
        }
        return false
    default:
        return false
    }
}

// 从配置文件中获取Float64类型的值
func (c *ConfigEngine) GetFloat64(name string) float64 {
    value := c.Get(name)
    switch value := value.(type){
    case string:
        str,_ := strconv.ParseFloat(value,64)
        return str
    case int:
        return float64(value)
    case bool:
        if value {
            return float64(1)
        }
        return float64(0)
    case float64:
        return value
    default:
        return float64(0)
    }
}

// 从配置文件中获取Struct类型的值,这里的struct是你自己定义的根据配置文件
func (c *ConfigEngine) GetStruct(name string,s interface{}) interface{}{
    d := c.Get(name)
    switch d.(type){
    case string:
        c.setField(s,name,d)
    case map[interface{}]interface{}:
        c.mapToStruct(d.(map[interface{}]interface{}), s)
    }
    return s
}

func (c *ConfigEngine) mapToStruct(m map[interface{}]interface{},s interface{}) interface{}{
    for key, value := range m {
        switch key.(type) {
        case string:
            c.setField(s,key.(string),value)
        }
    }
    return s
}

// 这部分代码是重点,需要多看看
func (c *ConfigEngine) setField(obj interface{},name string,value interface{}) error {
    // reflect.Indirect 返回value对应的值
    structValue := reflect.Indirect(reflect.ValueOf(obj))
    structFieldValue := structValue.FieldByName(name)

    // isValid 显示的测试一个空指针
    if !structFieldValue.IsValid() {
        return fmt.Errorf("No such field: %s in obj",name)
    }

    // CanSet判断值是否可以被更改
    if !structFieldValue.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    // 获取要更改值的类型
    structFieldType := structFieldValue.Type()
    val := reflect.ValueOf(value)

    if structFieldType.Kind() == reflect.Struct && val.Kind() == reflect.Map {
        vint := val.Interface()

        switch vint.(type) {
        case map[interface{}]interface{}:
            for key, value := range vint.(map[interface{}]interface{}) {
                c.setField(structFieldValue.Addr().Interface(), key.(string), value)
            }
        case map[string]interface{}:
            for key, value := range vint.(map[string]interface{}) {
                c.setField(structFieldValue.Addr().Interface(), key, value)
            }
        }

    } else {
        if structFieldType != val.Type() {
            return errors.New("Provided value type didn‘t match obj field type")
        }

        structFieldValue.Set(val)
    }

    return nil
}

先写一个对上面这个包的使用例子:
首先是yaml配置文件的内容,这里简单写了一些内容:

Site:
    HttpPort: 8080
    HttpsOn: false
    Domain: "pythonsite.com"
    HttpsPort: 443
Nginx:
    Port: 80
    LogPath:  "/opt/log/nginx.log"
    Path: "/opt/nginx/"
SiteName: "this is my web site"
SiteAddr: "BeiJing"

测试程序的代码为:

package main

import (
    "github.com/pythonsite/config_yaml"
    "fmt"

)

type SiteConfig struct {
    HttpPort    int
    HttpsOn     bool
    Domain      string
    HttpsPort   int
}

type NginxConfig struct {
    Port int
    LogPath string
    Path    string
}

func main() {
    c2 := config_yaml.ConfigEngine{}
    c2.Load("test.yaml")

    siteConf := SiteConfig{}
    res := c2.GetStruct("Site",&siteConf)
    fmt.Println(res)

    nginxConfig := NginxConfig{}
    res2 := c2.GetStruct("Nginx",&nginxConfig)
    fmt.Println(res2)

    siteName := c2.GetString("SiteName")
    siteAddr := c2.GetString("SiteAddr")
    fmt.Println(siteName,siteAddr)

}

效果如下:

感觉挺好用哈

补充一些知识点(参考go圣经)

接口值

接口值有两个部分组成:具体的类型和该类型的值,而这两个概念被称为接口的动态类型和动态值
Go语言中,变量总是被初始化之后我们才能使用,即使接口类型也不例外
对于接口值的零值就是类型的值和值的内容都是nil

对于接口值我们是可以通过==nil 或者!=nil 的方法来判断接口值是否为空
var w io.Writer
w = os.Stdout
当通过w = os.Stdout 进行赋值的时候调用了一个具体类型到接口类型的隐式转换,这和显式的使用io.Writer(os.Stdout)是等价的。
当赋值之后w 这个接口值的动态类型被设置为*os.Stdout指针的类型描述符,它的动态值是os.Stdout的拷贝

通常在编译期,我们不知道接口值的动态类型是什么,所以一个接口上的调用必须使用动态分配。因为
不是直接进行调用,所以编译器必须把代码生成在类型描述符的方法Write上,然后间接调用那个地址。
这个调用的接收者是一个接口动态值的拷贝,os.Stdout。效果和下面这个直接调用一样:
os.Stdout.Write([]byte("hello")) 等价于 w.Write([]byte("hello"))

反射

函数 reflect.TypeOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Type:

t := reflect.TypeOf(3)
fmt.Println(t.String())
fmt.Println(t)

我们可以查看一下reflect.TypeOf的详细方法:

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

可以看到参数类型是一个interface{},就像上面在接口值中说的这里会将这个具体的值即我们传入的3进行一个隐式的转换
会创建一个包含两个信息的接口值:3这个变量的动态类型,这里是int; 3这个变量的动态值,这里是3

reflect.ValueOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Value. 和
reflect.TypeOf 类似, reflect.ValueOf 返回的结果也是对于具体的类型, 但是 reflect.Value 也可
以持有一个接口值

v := reflect.ValueOf(3)
fmt.Println(v)
fmt.Println(v.String())

v2 := reflect.ValueOf("abc")
fmt.Println(v2)
fmt.Println(v2.String())

这里需要注意除非 Value 持有的是字符串, 否则 String 只是返回具体的类型.
Value 的 Type 方法将返回具体类型所对应的 reflect.Type:

t = v.Type()
fmt.Println(t)
fmt.Println(t.String())

当然我们也可以逆操作回去reflect.Value.Interface,然后通过断言的方式实现
一个 reflect.Value 和 interface{} 都能保存任意的值. 所不同的是, 一个空的接口隐藏了值对应的 表示方式和所有的公开的方法, 因此只有我们知道具体的动态类型才能使用类型断言来访问内部的值, 对于内部值并没有特别可做的事情. 相比之下, 一个 Value 则有很多方法来检查其内容, 无论它的具体类型是什么

使用 reflect.Value 的 Kind
kinds类型却是有限的: Bool, String 和 所有数字类型的基础类型; Array 和 Struct 对应的聚合 类型; Chan, Func, Ptr, Slice, 和 Map 对应的引用类似; 接口类型; 还有表示空值的无效类型. (空 的 reflect.Value 对应 Invalid 无效类型.)

通过reflect.Value修改值

有一些reflect.Values是可取地址的;其它一些则不可以,例子如下:

package main

import (
    "reflect"
    "fmt"
)

func main() {
    x := 2
    a := reflect.ValueOf(2)
    b := reflect.ValueOf(x)
    c := reflect.ValueOf(&x)
    d := c.Elem()
    fmt.Println(a.CanAddr()) // false
    fmt.Println(b.CanAddr()) // false
    fmt.Println(c.CanAddr()) // false
    fmt.Println(d.CanAddr()) // true

}

所有通过reflect.ValueOf(x)返回的 reflect.Value都是不可取地址的。但是对于d,它是c的解引用方式生成的,指向另一个变量,因此是可 取地址的。我们可以通过调用reflect.ValueOf(&x).Elem(),来获取任意变量x对应的可取地址的 Value。

要从变量对应的可取地址的reflect.Value来访问变量需要三个步骤。第一步是调用Addr()方法,它返回 一个Value,里面保存了指向变量的指针。然后是在Value上调用Interface()方法,也就是返回一个 interface{},里面通用包含指向变量的指针。最后,如果我们知道变量的类型,我们可以使用类型的断 言机制将得到的interface{}类型的接口强制环为普通的类型指针。这样我们就可以通过这个普通指针来 更新变量了:

package main

import (
    "reflect"
    "fmt"
)

func main() {
    x := 2
    d := reflect.ValueOf(&x).Elem()
    px := d.Addr().Interface().(*int)
    *px = 3
    fmt.Print(x)
}

当然这里也可以通过另外一种方法更改:

package main

import (
    "reflect"
    "fmt"
)

func main() {
    x := 2
    d := reflect.ValueOf(&x).Elem()
    //px := d.Addr().Interface().(*int)
    //*px = 3
    d.Set(reflect.ValueOf(4))
    fmt.Print(x)
}

但是直接Set是会报错的,因为这里我们正好用的赋值也是int类型,如果我们用字符串的肯定就panic了

原文地址:https://www.cnblogs.com/zhaof/p/8955332.html

时间: 2024-10-11 10:06:25

Go 处理yaml类型的配置文件的相关文章

python3比较ini类型的配置文件方案

ini类型的配置文件有个特点,就是配置是分组的,每组有个section,section下面是键值对的形式,python3比较升级前和升级后的配置改变方案:第一步:将section和下面的键值对进行绑定file1 file2 列表,列表中每一项是每组section构成的字典section写成:tag=section名字的形式遍历file2 列表中的每组的tag每次取到一组的tag就去file1 列表中去找file1列表可以先把tag的value先收集为一个列表只要取file2的的tag遍历是不是在

Struts2的result中各种type类型以及配置文件的一些细节

Struts2支持的不同类型的返回结果为: Chain Result-->type="chain"用来处理Action链 Dispatcher Result -->type="dispatcher"用来转向页面,通常处理JSPFreeMarker Result -->type="freemarker"处理FreeMarker模板HttpHeader Result -->type="httpheader"

ftp用户类型、配置文件详解以及“425 Security:Bad IP connection”解决方式

一:ftp用户类型 1:匿名用户anonymous,实际登陆访问文件目录时,是使用本地系统用户ftp登陆,访问ftp的家目录即/var/ftp,默认此目录只有root才有写权限(ftp的安全机制),推荐在此目录下创建目录并赋予ftp写权限 2:本地系统用户,登陆时会直接访问此用户的家目录,为了安全起见在操作系统创建用户时添加"-d /bin/nologin",设置此用户为不可登陆操作系统 3:虚拟用户,使用mysql等数据库管理用户,需在配置文件上将用户映射到某一本地系统用户 二:ft

MongoDB YAML格式的配置文件

根据官网的提示内容,默认的配置文件内容为 systemLog: verbosity: 0 quiet: false # traceAllExceptions: <boolean> syslogFacility: user path: "/data/mongodb/log" logAppend: false logRotate: rename destination: file timeStampFormat: iso8601-local component: accessC

Dropwizard简单入门

Dropwizard:一个简洁的RESTful Web框架 Dropwizard跨越了开发库与框架的界限,旨在为Web应用所需的功能提供高性能.可靠的实现.Dropwizard将这些功能抽象为可重用的开发库,因此应用程序可以保持精简与专注,从而大大减少产品面世的时间以及维护负担. Jetty HTTP库 Web应用都离不开HTTP,Dropwizard使用Jetty HTTP库为项目嵌入HTTP服务器.与复杂的应用服务器不同,Dropwizard项目通过main方法加快HTTP服务器处理.在生产

log4cplus:一个按天为单位、按不同类型归类的配置文件

log4cplus按天为单位.按不同类型归类配置文件. #配置文件(其它日志级别配置相同): log4cplus.rootLogger=TRACE, DEBUG_MSGS, ERROR_MSGS, WARN_MSGS #DEBUG #设置日志追加到文件尾 log4cplus.appender.DEBUG_MSGS=log4cplus::RollingFileAppender #设置每天记录一个日志文件 log4cplus.appender.DEBUG_MSGS.Schedule=DAILY lo

python接口自动化测试二十九:yaml配置文件的写和读

# 先安装ruamel.yaml模块 写入配置文件: import os# 先安装ruamel.yaml模块from ruamel import yaml # 将字典写入到yamldict = { 'host1': '123', 'host2': '456', 'host3': '789', 'host4': '147', 'host5': {'asd': '123'} } curpath = os.path.dirname(os.path.realpath(__file__))yamlpath

Python3比较ini类型配置文件的异同(升级版)

应用场景:ini类型配置文件由于升级改动了,我想看看升级后的配置文件相对于之前的改动了哪些配置项ini类型的配置文件的特点:就像这样子:[isamchk]key_buffer = 128Msort_buffer_size = 128Mread_buffer = 2Mwrite_buffer = 2M[myity]key_buffer = 128Msort_buffer_size = 128Mread_buffer = 2Mwrite_buffer = 2M每个配置块内容前有一个[xxx]的sec

flutter如何使用配置文件pubspec.yaml(位于项目根目录)来管理第三方依赖包

官方文档 在软件开发中,很多时候有一些公共的库或SDK可能会被很多项目用到,因此,将这些代码单独抽到一个独立模块,然后哪个项目需要使用时再直接集成这个模块,便可大大提高开发效率.很多编程语言或开发工具都支持这种“模块共享”机制,如Java语言中这种独立模块会被打成一个jar包,Android中的aar包,Web开发中的npm包等.为了方便表述,我们将这种可共享的独立模块统一称为“包”( Package). 一个APP在实际开发中往往会依赖很多包,而这些包通常都有交叉依赖关系.版本依赖等,如果由开