英文:
How to write to yml files in Go
问题
我正在尝试为Go中的Snowfakery编写一个yml文件,但我对Go非常陌生,对它们的结构体不熟悉。我已经能够使用Python编写自己的脚本来实现这一点,但在Go中遇到了一些困难。我已经查看了一些关于如何在Go中编写yml文件的答案,它们对于入门帮助很大,但由于我特定yml中的不同类型,我感到困惑。
我需要yml文件看起来像这样:
- snowfakery_version: 3
- object: Logistics
  count: 1
  fields:
    Product_ID:
      random_choice:
      - M14860
      - L47181
      - L47182
    Type:
      random_choice:
      - A10
      - C17
      - F16
    Air_Temperature:
      random_number:
        min: 298
        max: 300
    Tool Wear Fail:
      if:
      - choice:
          when: ${{Tool_Wear>2}}
          pick:
            random_choice:
            - 1
            - 0
      - choice:
          when: ${{Tool_Wear<2}}
          pick: 2
所以我尝试创建类似这样的结构体:
package main
import (
	"fmt"
	"io/ioutil"
	"log"
	"gopkg.in/yaml.v3"
)
// TotalSchema represents the YAML schema
type TotalSchema struct {
	Snowfakery_version []string   `yaml:"snowfakery_version"`
	Object             []DataItem `yaml:"objects"`
}
// DataItem represents an object type in the schema
type DataItem struct {
	Object string          `yaml:"object"`
	Count  int             `yaml:"count"`
	Fields LogisticsFields `yaml:"fields"`
}
// Fields represents the fields of an object type
type LogisticsFields struct {
	ProductID     RandomChoice      `yaml:"Product_ID"`
	Type          RandomChoice      `yaml:"Type"`
	AirTemperature RandomNumber     `yaml:"Air_Temperature"`
	ToolWearFail  []ChoiceCondition `yaml:"Tool Wear Fail"`
}
type RandomNumber struct {
	Min int `yaml:"min"`
	Max int `yaml:"max"`
}
type RandomChoice struct {
	RandomChoice []string `yaml:"random_choice"`
}
type ChoiceCondition struct {
	When string `yaml:"when"`
	Pick int    `yaml:"pick"`
}
但是,每当我尝试创建结构体并使用它来写入yml文件时,我得到一个完全空的yml文件。我希望能得到任何帮助。提前谢谢。
编辑
通过使用接口结构和映射,我已经能够完成大部分工作,例如:
package main
import (
	"fmt"
	"io/ioutil"
	"gopkg.in/yaml.v3"
)
// TotalSchema represents the YAML schema
type TotalSchema struct {
	Snowfakery_version int `yaml:"snowfakery_version"`
}
// DataItem represents an object type in the schema
type DataItem struct {
	Object string      `yaml:"object"`
	Count  int         `yaml:"count"`
	Fields interface{} `yaml:"fields"`
}
// Fields represents the fields of an object type
type Fields struct {
	RandomChoices []string          `yaml:"random_choices,omitempty"`
	RandomNumber  RandomNumber      `yaml:"random_number,omitempty"`
	Choice        []ChoiceArray     `yaml:"if,omitempty"`
	Random_Choice []ChoiceProbArray `yaml:"random_choice,omitempty"`
}
type RandomNumber struct {
	Min int `yaml:"min"`
	Max int `yaml:"max"`
}
type RandomChoice struct {
	RandomChoice []string `yaml:"random_choice"`
}
type ChoiceArray struct {
	Choice ChoiceCondition `yaml:"choice"`
}
type ChoiceProbArray struct {
	ChoiceProb ChoiceProbability `yaml:"choice"`
}
type ChoiceCondition struct {
	When string `yaml:"when"`
	Pick int    `yaml:"pick"`
}
type ChoiceProbability struct {
	Probability string `yaml:"probability"`
	Pick        int    `yaml:"pick"`
}
var logisticsDict []interface{}
var totalStructDict TotalSchema
var objArray []DataItem
var object DataItem
func logisticsHandler(number int, objectName string) {
	object.Count = number
	object.Object = objectName
	logisticsDict = []interface{}{
		TotalSchema{Snowfakery_version: 3},
		DataItem{
			Object: "logistics",
			Count:  4,
			Fields: map[string]interface{}{
				"Product ID": Fields{
					RandomChoices: []string{"M14860",
						"L47181",
						"L47182",
					},
				},
				"Air_Temperature": Fields{
					RandomNumber: RandomNumber{
						Min: 298,
						Max: 300,
					},
				},
				"Process_Temperature": Fields{
					RandomNumber: RandomNumber{
						Min: 298,
						Max: 300,
					},
				},
				"Heat Dissipation Fail": Fields{
					Choice: []ChoiceArray{
						{ChoiceCondition{
							When: "${{(Process_Temperature - Air_Temperature)<8.6 and Rotational_Speed<1380}}",
							Pick: 1,
						}},
						{ChoiceCondition{
							When: "${{(Process_Temperature - Air_Temperature)>8.6 or Rotational_Speed>1380}}",
							Pick: 0,
						}},
					},
				},
			},
		},
	}
}
func main() {
	//other funciton I use to populate the rest of the struct
	logisticsHandler(4, "logistics")
	fmt.Println(logisticsDict)
	data, err := yaml.Marshal(logisticsDict)
	if err != nil {
		panic(err)
	}
	err = ioutil.WriteFile("logistics.yml", data, 0)
	if err != nil {
		panic(err)
	}
	fmt.Println("YAML data has been written to 'logistics.yml' file.")
}
但是,每当我运行yaml.Marshal代码时,它会自动对我的代码进行排序。我现在想知道如何使其保持与我创建的顺序完全一致,因为Snowfakery依赖于按顺序定义变量。
英文:
I am trying to write a yml file for snowfakery in go and I am very new to go so I am unfamiliar with their structs. I have been able to write my own script to do this in python but am struggling a bit more with go. I have looked at some of the other answers for how to write to a yml file in go and they helped a lot with starting but because of the different types in my specific yml I got confused
I need the yml file to look something like this
- snowfakery_version: 3
- object: Logistics
count: 1
fields:
Product_ID:
random_choice:
- M14860
- L47181
- L47182
Type:
random_choice:
- A10
- C17
- F16
Air_Temperature:
random_number:
min: 298
max: 300
Tool Wear Fail:
if:
- choice:
when: ${{Tool_Wear>2}}
pick:
random_choice:
- 1
- 0
- choice:
when: ${{Tool_Wear<2}}
pick: 2
so i have tried making structs like
package main
import (
"fmt"
"io/ioutil"
"log"
"gopkg.in/yaml.v3"
// "os"
// "strings"
)
// TotalSchema represents the YAML schema
type TotalSchema struct {
snowfakery_version []string   `yaml:"snowfakery_version"`
object             []DataItem `yaml:"objects"`
}
// DataItem represents an object type in the schema
type DataItem struct {
Object  string          `yaml:"object"`
Count   int             `yaml:"count"`
Fields  LogisticsFields `yaml:"fields"`
}
// Fields represents the fields of an object type
type LogisticsFields struct {
ProductID           RandomChoice        `yaml:"Product_ID"`
Type                RandomChoice        `yaml:"Type"`
ToolWear            RandomNumber        `yaml:"Tool_Wear"`
ToolWearFail        []ChoiceCondition   `yaml:"Tool Wear Fail"`
}
type RandomNumber struct {
Min int `yaml:"min"`
Max int `yaml:"max"`
}
type RandomChoice struct {
RandomChoice []string `yaml:"random_choice"`
}
type ChoiceCondition struct {
When string `yaml:"when"`
Pick int    `yaml:"pick"`
}
but whenever I try to create the struct and use it to write to a yml file
func main() {
totalStructDict.snowfakery_version = []string{"3"}
//other funciton I use to populate the rest of the struct
logisticsHandler(4, "logistics")
fmt.Println(totalStructDict)
data, err := yaml.Marshal(totalStructDict)
if err != nil {
panic(err)
}
fmt.Println( data)
err = ioutil.WriteFile("logistics.yml", data, 0)
if err != nil {
panic(err)
}
fmt.Println("YAML data has been written to 'logistics.yml' file.")
I get a completely empty yml file. I would love any help. Thanks in advance
EDIT
I have been able to get most of the way with by using the interface structure and maps ie.
     package main
import (
"fmt"
"io/ioutil"
"gopkg.in/yaml.v3"
// "os"
// "strings"
)
// TotalSchema represents the YAML schema
type TotalSchema struct {
Snowfakery_version int `yaml:"snowfakery_version"`
}
// DataItem represents an object type in the schema
type DataItem struct {
Object string      `yaml:"object"`
Count  int         `yaml:"count"`
Fields interface{} `yaml:"fields"`
}
// Fields represents the fields of an object type
type Fields struct {
RandomChoices []string          `yaml:"random_choices,omitempty"`
RandomNumber  RandomNumber      `yaml:"random_number,omitempty"`
Choice        []ChoiceArray     `yaml:"if,omitempty"`
Random_Choice []ChoiceProbArray `yaml:"random_choice,omitempty"`
}
type RandomNumber struct {
Min int `yaml:"min"`
Max int `yaml:"max"`
}
type RandomChoice struct {
RandomChoice []string `yaml:"random_choice"`
}
type ChoiceArray struct {
Choice ChoiceCondition `yaml:"choice"`
}
type ChoiceProbArray struct {
ChoiceProb ChoiceProbability `yaml:"choice"`
}
type ChoiceCondition struct {
When string `yaml:"when"`
Pick int    `yaml:"pick"`
}
type ChoiceProbability struct {
Probability string `yaml:"probability"`
Pick        int    `yaml:"pick"`
}
var logisticsDict []interface{}
var totalStructDict TotalSchema
var objArray []DataItem
var object DataItem
func logisticsHandler(number int, objectName string) {
object.Count = number
object.Object = objectName
logisticsDict = []interface{}{
TotalSchema{Snowfakery_version: 3},
DataItem{
Object: "logistics",
Count:  4,
Fields: map[string]interface{}{
"Product ID": Fields{
RandomChoices: []string{"M14860",
"L47181",
"L47182",
},
},
"Air_Temperature": Fields{
RandomNumber: RandomNumber{
Min: 298,
Max: 300,
},
},
"Process_Temperature": Fields{
RandomNumber: RandomNumber{
Min: 298,
Max: 300,
},
},
"Heat Dissipation Fail": Fields{
Choice: []ChoiceArray{
{ChoiceCondition{
When: "${{(Process_Temperature - Air_Temperature)<8.6 and Rotational_Speed<1380}}",
Pick: 1,
}},
{ChoiceCondition{
When: "${{(Process_Temperature - Air_Temperature)>8.6 or Rotational_Speed>1380}}",
Pick: 0,
}},
},
},
},
},
}
}
func main() {
//other funciton I use to populate the rest of the struct
logisticsHandler(4, "logistics")
fmt.Println(logisticsDict)
data, err := yaml.Marshal(logisticsDict)
if err != nil {
panic(err)
}
// fmt.Println(data)
err = ioutil.WriteFile("logistics.yml", data, 0)
if err != nil {
panic(err)
}
fmt.Println("YAML data has been written to 'logistics.yml' file.")
}
But now whenever I run the yaml.Marshel code it is auto sorting my code. I am now wondering how to get it to stay exactly in the order that I created it in because snowfakery relies on variables to be defined in order
答案1
得分: 1
在你的示例中,snowfakery_version是一个字典中唯一的键,其值是标量整数值3。这个字典是列表中的第一项:
- snowfakery_version: 3
上述内容的JSON等效形式是:
[
{"snowfakery_version": 3}
]
这与你的数据结构不匹配,你的数据结构看起来是这样的:
type TotalSchema struct {
snowfakery_version []string   `yaml:"snowfakery_version"`
object             []DataItem `yaml:"objects"`
}
这对应于一个类似以下的YAML文件:
snowfakery_version: [3]
或者作为JSON:
{
"snowfakery_version": [3]
}
考虑到你正在解析一个已建立的格式,我认为将其解析为Go结构可能会有些麻烦:
- 
似乎没有正式的模式文档
 - 
看起来一些字段实际上是“联合”类型。也就是说:
- 字段定义可以是结构,也可以是其他对象的列表。
 pick可以是标量值(整数、字符串)或结构体
等等。
 
在Go中,解析联合类型通常意味着使用interface{},然后通过在运行时检查数据来动态确定要执行的操作。
从顶部开始,你的文件格式是一个“事物”的列表,其中每个事物可以是snowfakery_version或对象定义。因此,我们从以下内容开始:
type SnowfakeryConfig []interface{}
然后,我们为预期的各种类型创建struct定义,例如:
SnowfakeryVersion struct {
SnowfakeryVersion int `yaml:"snowfakery_version"`
}
ObjectSpec struct {
Object string                 `yaml:"object,omitempty"`
Count  int                    `yaml:"count,omitempty"`
Fields map[string]interface{} `yaml:"fields,omitempty"`
}
.
.
.
这些可以附加到SnowfakeryConfig列表中。以下是一个通过程序生成你问题中示例配置的示例:
package main
import (
  "os"
  "gopkg.in/yaml.v3"
)
type (
  SnowfakeryConfig []interface{}
  SnowfakeryVersion struct {
    SnowfakeryVersion int `yaml:"snowfakery_version"`
  }
  ObjectSpec struct {
    Object string                 `yaml:"object,omitempty"`
    Count  int                    `yaml:"count,omitempty"`
    Fields map[string]interface{} `yaml:"fields,omitempty"`
  }
  FieldSpec struct {
    Fake         string        `yaml:"fake,omitempty"`
    RandomChoice RandomChoice  `yaml:"random_choice,omitempty"`
    RandomNumber *RandomNumber `yaml:"random_number,omitempty"`
    IfExpression IfExpression  `yaml:"if,omitempty"`
    Object       string        `yaml:"object,omitempty"`
  }
  RandomChoice []interface{}
  RandomNumber struct {
    Min int
    Max int
  }
  IfExpression []Choice
  Choice struct {
    Choice ChoiceSpec
  }
  ChoiceSpec struct {
    When string
    Pick interface{}
  }
)
func main() {
  config := SnowfakeryConfig{}
  config = append(config, SnowfakeryVersion{SnowfakeryVersion: 3})
  config = append(config, ObjectSpec{
    Object: "Logistics",
    Count:  1,
    Fields: map[string]interface{}{
      "Product_ID": FieldSpec{
        RandomChoice: RandomChoice{
          "M14860",
          "L47181",
          "L47182",
        },
      },
      "Type": FieldSpec{
        RandomChoice: RandomChoice{
          "A10",
          "C17",
          "F16",
        },
      },
      "Air_Temperature": FieldSpec{
        RandomNumber: &RandomNumber{
          Min: 298,
          Max: 300,
        },
      },
      "Tool Wear Fail": FieldSpec{
        IfExpression: IfExpression{
          Choice{
            Choice: ChoiceSpec{
              When: "${{Tool_Wear>2}}",
              Pick: FieldSpec{
                RandomChoice: RandomChoice{
                  1,
                  0,
                },
              },
            },
          },
          Choice{
            Choice: ChoiceSpec{
              When: "${{Tool_Wear<2}}",
              Pick: 2,
            },
          },
        },
      },
    },
  })
  content, err := yaml.Marshal(config)
  if err != nil {
    panic(err)
  }
  if err := os.WriteFile("output.yaml", content, 0666); err != nil {
    panic(err)
  }
}
运行上述代码会在output.yaml中生成以下内容:
- snowfakery_version: 3
- object: Logistics
  count: 1
  fields:
    Air_Temperature:
        random_number:
            min: 298
            max: 300
    Product_ID:
        random_choice:
            - M14860
            - L47181
            - L47182
    Tool Wear Fail:
        if:
            - choice:
                when: ${{Tool_Wear>2}}
                pick:
                    random_choice:
                        - 1
                        - 0
            - choice:
                when: ${{Tool_Wear<2}}
                pick: 2
    Type:
        random_choice:
            - A10
            - C17
            - F16
解析YAML需要我们在运行时动态检测对象类型。我们可以通过查找从文件中读取的每个项中的特定键来实现这一点:
fd, err := os.ReadFile("sample.yaml")
if err != nil {
  panic(err)
}
if err := yaml.Unmarshal(fd, &config); err != nil {
  panic(err)
}
for _, item := range config {
  if _, ok := item.(map[string]interface{})["snowfakery_version"]; ok {
    fmt.Println("found version")
    if err := mapstructure.Decode(item, &version); err != nil {
      panic(err)
    }
    fmt.Printf("version: %d\n", version.SnowfakeryVersion)
    continue
  }
  if _, ok := item.(map[string]interface{})["object"]; ok {
    fmt.Println("found object")
    if err := mapstructure.Decode(item, &objspec); err != nil {
      panic(err)
    }
    fmt.Printf("object name: %s\n", objspec.Object)
    for k := range objspec.Fields {
      fmt.Printf("  field: %s\n", k)
    }
    continue
  }
}
在这个示例中,我使用mapstructure模块将我们的map[string]interface{}转换为我们在确定对象类型后所需的结构。
这里包含了我使用的完整测试代码;你可以调用./example generate来创建output.yaml,或者调用./example parse somefile.yaml来解析somefile.yaml中的YAML。
package main
import (
  "fmt"
  "os"
  "github.com/mitchellh/mapstructure"
  "gopkg.in/yaml.v3"
)
type (
  SnowfakeryConfig []interface{}
  SnowfakeryVersion struct {
    SnowfakeryVersion int `mapstructure:"snowfakery_version"`
  }
  ObjectSpec struct {
    Object string                 `mapstructure:"object,omitempty"`
    Count  int                    `mapstructure:"count,omitempty"`
    Fields map[string]interface{} `mapstructure:"fields,omitempty"`
  }
  FieldSpec struct {
    Fake         string        `mapstructure:"fake,omitempty"`
    RandomChoice RandomChoice  `mapstructure:"random_choice,omitempty"`
    RandomNumber *RandomNumber `mapstructure:"random_number,omitempty"`
    IfExpression IfExpression  `mapstructure:"if,omitempty"`
    Object       string        `mapstructure:"object,omitempty"`
  }
  RandomChoice []interface{}
  RandomNumber struct {
    Min int
    Max int
  }
  IfExpression []Choice
  Choice struct {
    Choice ChoiceSpec
  }
  ChoiceSpec struct {
    When string
    Pick interface{}
  }
)
func main() {
  config := SnowfakeryConfig{}
  if os.Args[1] == "parse" {
    version := SnowfakeryVersion{}
    objspec := ObjectSpec{}
    fd, err := os.ReadFile(os.Args[2])
    if err != nil {
      panic(err)
    }
    if err := yaml.Unmarshal(fd, &config); err != nil {
      panic(err)
    }
    for _, item := range config {
      if _, ok := item.(map[string]interface{})["snowfakery_version"]; ok {
        fmt.Println("found version")
        if err := mapstructure.Decode(item, &version); err != nil {
          panic(err)
        }
        fmt.Printf("version: %d\n", version.SnowfakeryVersion)
        continue
      }
      if _, ok := item.(map[string]interface{})["object"]; ok {
        fmt.Println("found object")
        if err := mapstructure.Decode(item, &objspec); err != nil {
          panic(err)
        }
        fmt.Printf("object name: %s\n", objspec.Object)
        for k := range objspec.Fields {
          fmt.Printf("  field: %s\n", k)
        }
        continue
      }
    }
  } else if os.Args[1] == "generate" {
    config = append(config, SnowfakeryVersion{SnowfakeryVersion: 3})
    config = append(config, ObjectSpec{
      Object: "Logistics",
      Count:  1,
      Fields: map[string]interface{}{
        "Product_ID": FieldSpec{
          RandomChoice: RandomChoice{
            "M14860",
            "L47181",
            "L47182",
          },
        },
        "Type": FieldSpec{
          RandomChoice: RandomChoice{
            "A10",
            "C17",
            "F16",
          },
        },
        "Air_Temperature": FieldSpec{
          RandomNumber: &RandomNumber{
            Min: 298,
            Max: 300,
          },
        },
        "Tool Wear Fail": FieldSpec{
          IfExpression: IfExpression{
            Choice{
              Choice: ChoiceSpec{
                When: "${{Tool_Wear>2}}",
                Pick: FieldSpec{
                  RandomChoice: RandomChoice{
                    1,
                    0,
                  },
                },
              },
            },
            Choice{
              Choice: ChoiceSpec{
                When: "${{Tool_Wear<2}}",
                Pick: 2,
              },
            },
          },
        },
      },
    })
    content, err := yaml.Marshal(config)
    if err != nil {
      panic(err)
    }
    if err := os.WriteFile("output.yaml", content, 0666); err != nil {
      panic(err)
    }
  }
}
英文:
In your example, snowfakery_version is the only key in a  dictionary whose value is the scalar integer value 3. This dictionary is the first item in a list:
- snowfakery_version: 3
The JSON equivalent to the above is:
[
{"snowfakery_version": 3}
]
That doesn't match your data structure, which looks like:
type TotalSchema struct {
snowfakery_version []string   `yaml:"snowfakery_version"`
object             []DataItem `yaml:"objects"`
}
This would correspond to a YAML file that looks like:
snowfakery_version: [3]
Or as JSON:
{
"snowfakery_version": [3]
}
With the understanding that you're parsing an established format, I think it's going to be a bit of pain to parse this into Go structs:
- 
There does not appear to be a formal schema document
 - 
It looks like some of the fields are effectively "union" types. That is:
- A field definition can either be a structure, or it can be a list of additional objects.
 pickcan be a scalar value (integer, string) or structure
etc.
 
In go, parsing a union type usually means using an interface{} and then figuring out what to do dynamically by inspecting the data at runtime.
Starting at the top, your file format is a list of "things", where each thing can be either a snowfakery_version or an object definition. So that means we start with:
type SnowfakeryConfig []interface{}
Then we create the struct definitions for various types that we expect, e.g.:
SnowfakeryVersion struct {
SnowfakeryVersion int `yaml:"snowfakery_version"`
}
ObjectSpec struct {
Object string                 `yaml:"object,omitempty"`
Count  int                    `yaml:"count,omitempty"`
Fields map[string]interface{} `yaml:"fields,omitempty"`
}
.
.
.
These can be appended to a SnowfakeryConfig list. Here's an example that programatically produces the sample config from your question:
package main
import (
"os"
"gopkg.in/yaml.v3"
)
type (
SnowfakeryConfig []interface{}
SnowfakeryVersion struct {
SnowfakeryVersion int `yaml:"snowfakery_version"`
}
ObjectSpec struct {
Object string                 `yaml:"object,omitempty"`
Count  int                    `yaml:"count,omitempty"`
Fields map[string]interface{} `yaml:"fields,omitempty"`
}
FieldSpec struct {
Fake         string        `yaml:"fake,omitempty"`
RandomChoice RandomChoice  `yaml:"random_choice,omitempty"`
RandomNumber *RandomNumber `yaml:"random_number,omitempty"`
IfExpression IfExpression  `yaml:"if,omitempty"`
Object       string        `yaml:"object,omitempty"`
}
RandomChoice []interface{}
RandomNumber struct {
Min int
Max int
}
IfExpression []Choice
Choice struct {
Choice ChoiceSpec
}
ChoiceSpec struct {
When string
Pick interface{}
}
)
func main() {
config := SnowfakeryConfig{}
config = append(config, SnowfakeryVersion{SnowfakeryVersion: 3})
config = append(config, ObjectSpec{
Object: "Logistics",
Count:  1,
Fields: map[string]interface{}{
"Product_ID": FieldSpec{
RandomChoice: RandomChoice{
"M14860",
"L47181",
"L47182",
},
},
"Type": FieldSpec{
RandomChoice: RandomChoice{
"A10",
"C17",
"F16",
},
},
"Air_Temperature": FieldSpec{
RandomNumber: &RandomNumber{
Min: 298,
Max: 300,
},
},
"Tool Wear Fail": FieldSpec{
IfExpression: IfExpression{
Choice{
Choice: ChoiceSpec{
When: "${{Tool_Wear>2}}",
Pick: FieldSpec{
RandomChoice: RandomChoice{
1,
0,
},
},
},
},
Choice{
Choice: ChoiceSpec{
When: "${{Tool_Wear<2}}",
Pick: 2,
},
},
},
},
},
})
content, err := yaml.Marshal(config)
if err != nil {
panic(err)
}
if err := os.WriteFile("output.yaml", content, 0666); err != nil {
panic(err)
}
}
Running the above code produces, in output.yaml:
- snowfakery_version: 3
- object: Logistics
count: 1
fields:
Air_Temperature:
random_number:
min: 298
max: 300
Product_ID:
random_choice:
- M14860
- L47181
- L47182
Tool Wear Fail:
if:
- choice:
when: ${{Tool_Wear>2}}
pick:
random_choice:
- 1
- 0
- choice:
when: ${{Tool_Wear<2}}
pick: 2
Type:
random_choice:
- A10
- C17
- F16
Parsing the YAML requires us to dynamically detect object types at runtime. We can do this by looking for specific keys in each item read from the file:
fd, err := os.ReadFile("sample.yaml")
if err != nil {
panic(err)
}
if err := yaml.Unmarshal(fd, &config); err != nil {
panic(err)
}
for _, item := range config {
if _, ok := item.(map[string]interface{})["snowfakery_version"]; ok {
fmt.Println("found version")
if err := mapstructure.Decode(item, &version); err != nil {
panic(err)
}
fmt.Printf("version: %d\n", version.SnowfakeryVersion)
continue
}
if _, ok := item.(map[string]interface{})["object"]; ok {
fmt.Println("found object")
if err := mapstructure.Decode(item, &objspec); err != nil {
panic(err)
}
fmt.Printf("object name: %s\n", objspec.Object)
for k := range objspec.Fields {
fmt.Printf("  field: %s\n", k)
}
continue
}
}
In this example I'm using the mapstructure module to convert our map[string]interface{} into the desired structure after we've figured out what object type we have.
The complete test code I used here is included below; you can call ./example generate to create output.yaml, or ./example parse somefile.yaml to parse the YAML in somefile.yaml.
package main
import (
"fmt"
"os"
"github.com/mitchellh/mapstructure"
"gopkg.in/yaml.v3"
)
type (
SnowfakeryConfig []interface{}
SnowfakeryVersion struct {
SnowfakeryVersion int `mapstructure:"snowfakery_version"`
}
ObjectSpec struct {
Object string                 `mapstructure:"object,omitempty"`
Count  int                    `mapstructure:"count,omitempty"`
Fields map[string]interface{} `mapstructure:"fields,omitempty"`
}
FieldSpec struct {
Fake         string        `mapstructure:"fake,omitempty"`
RandomChoice RandomChoice  `mapstructure:"random_choice,omitempty"`
RandomNumber *RandomNumber `mapstructure:"random_number,omitempty"`
IfExpression IfExpression  `mapstructure:"if,omitempty"`
Object       string        `mapstructure:"object,omitempty"`
}
RandomChoice []interface{}
RandomNumber struct {
Min int
Max int
}
IfExpression []Choice
Choice struct {
Choice ChoiceSpec
}
ChoiceSpec struct {
When string
Pick interface{}
}
)
func main() {
config := SnowfakeryConfig{}
if os.Args[1] == "parse" {
version := SnowfakeryVersion{}
objspec := ObjectSpec{}
fd, err := os.ReadFile(os.Args[2])
if err != nil {
panic(err)
}
if err := yaml.Unmarshal(fd, &config); err != nil {
panic(err)
}
for _, item := range config {
if _, ok := item.(map[string]interface{})["snowfakery_version"]; ok {
fmt.Println("found version")
if err := mapstructure.Decode(item, &version); err != nil {
panic(err)
}
fmt.Printf("version: %d\n", version.SnowfakeryVersion)
continue
}
if _, ok := item.(map[string]interface{})["object"]; ok {
fmt.Println("found object")
if err := mapstructure.Decode(item, &objspec); err != nil {
panic(err)
}
fmt.Printf("object name: %s\n", objspec.Object)
for k := range objspec.Fields {
fmt.Printf("  field: %s\n", k)
}
continue
}
}
} else if os.Args[1] == "generate" {
config = append(config, SnowfakeryVersion{SnowfakeryVersion: 3})
config = append(config, ObjectSpec{
Object: "Logistics",
Count:  1,
Fields: map[string]interface{}{
"Product_ID": FieldSpec{
RandomChoice: RandomChoice{
"M14860",
"L47181",
"L47182",
},
},
"Type": FieldSpec{
RandomChoice: RandomChoice{
"A10",
"C17",
"F16",
},
},
"Air_Temperature": FieldSpec{
RandomNumber: &RandomNumber{
Min: 298,
Max: 300,
},
},
"Tool Wear Fail": FieldSpec{
IfExpression: IfExpression{
Choice{
Choice: ChoiceSpec{
When: "${{Tool_Wear>2}}",
Pick: FieldSpec{
RandomChoice: RandomChoice{
1,
0,
},
},
},
},
Choice{
Choice: ChoiceSpec{
When: "${{Tool_Wear<2}}",
Pick: 2,
},
},
},
},
},
})
content, err := yaml.Marshal(config)
if err != nil {
panic(err)
}
if err := os.WriteFile("output.yaml", content, 0666); err != nil {
panic(err)
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论