小能豆

How to marshal an integer value to hexadecimal using go-yaml?

go

I have a structure with integer fields, the representation of which makes sense to humans in hexadecimal form. For example, let this be the Vendor ID field.

I want to save this data to a YAML file for manual editing and then load it from the file. As I understand it, there are no problems with hexadecimal representation of numbers in YAML itself, but go-yaml (I use v3) encodes integers in decimal form and I have not found a normal way to make it save them in hexadecimal form.

Let’s take the following code as a starting point:

import (
    //...
    "gopkg.in/yaml.v3"
)

type DeviceInfo struct {
    VendorId uint32 `yaml:"vendorid"`
}

func main() {
    deviceInfo := DeviceInfo{VendorId: 0xdeadbeef}

    yml, err := yaml.Marshal(deviceInfo)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(yml))
}

This code generates YAML with a decimal value:

vendorid: 3735928559

Next, go-yaml allows you to create a custom marshaler for your own types. I did this (I deliberately omitted the 0x prefix in the fmt.Sprintf() format string):

type Uint32Hex uint32

func (U Uint32Hex) MarshalYAML() (interface{}, error) {
    return fmt.Sprintf("%x", U), nil
}

type DeviceInfo struct {
    VendorId Uint32Hex `yaml:"vendorid"`
}

func main() {
    // the same code
}

This code generates the value in hexadecimal, but without the 0x prefix (logical for now):

vendorid: deadbeef

But if I add the 0x prefix in the custom marshaler:

func (U Uint32Hex) MarshalYAML() (interface{}, error) {
    return fmt.Sprintf("0x%x", U), nil
}

The value is generated correctly, but not as a number, but as a string:

vendorid: "0xdeadbeef"

And in order to then unmarshal this line into a number, I will also have to write a custom unmarshaler. I don’t like this solution.

In the end, I have the following questions:

  1. Is there any simple way to generate hexadecimal numbers using go-yaml that I haven’t found?

  2. Is it possible to make a custom encoder in terms of go-yaml as an extension of the package without changing the package itself? For me, a more convenient way would be to pass a format tag in the structure description, for example, like this:

golang type DeviceInfo struct { VendorId uint32 `yaml:"vendorid,hex"` }

  1. If this requires changing the package code, what is the Go practice for such cases? Just copy the package files into my project, modify as needed and import it locally?

阅读 71

收藏
2023-12-19

共1个答案

小能豆

Let’s address your questions:

  1. Simple way to generate hexadecimal numbers in YAML using go-yaml: As of my last knowledge update in January 2022, the go-yaml library (v3) does not provide a built-in option to represent integers in hexadecimal form directly. Your approach of using a custom marshaler seems appropriate for the current state of the library.

  2. Custom encoder in terms of go-yaml as an extension: Unfortunately, go-yaml (v3) doesn’t directly support custom encoders at the field level using struct tags. The custom marshaler you’ve implemented for Uint32Hex is a reasonable approach for now.

  3. Go practice for modifying third-party packages: In general, copying and modifying third-party packages should be avoided whenever possible, as it can lead to maintenance challenges and issues with keeping your codebase up to date. However, if you have specific requirements that cannot be achieved through extension points or other means, forking and modifying a package might be a pragmatic solution.

Some alternatives to consider:

  • Custom Marshaling/Unmarshaling Functions: Instead of using struct tags, you can define custom functions for marshaling and unmarshaling that handle the hexadecimal representation.

```
func (d *DeviceInfo) MarshalYAML() (interface{}, error) {
return map[string]interface{}{
“vendorid”: fmt.Sprintf(“0x%x”, d.VendorId),
}, nil
}

func (d DeviceInfo) UnmarshalYAML(value yaml.Node) error {
var hexString string
if err := value.Decode(&hexString); err != nil {
return err
}
// Parse the hexadecimal string into an integer
// (you might want to handle errors here)
d.VendorId, _ = strconv.ParseUint(hexString, 0, 32)
return nil
}
```

  • Pre-processing/Post-processing: You can have a separate step where you convert the integers to hexadecimal strings before marshaling and convert them back after unmarshaling.
  • Custom Encoder (if Necessary): If your requirements are more complex and you need deeper integration, you might consider creating a wrapper around the go-yaml package and modifying it as needed. This approach would give you more control but requires careful maintenance.

Always check for updates to third-party packages, as maintainers may introduce new features or improvements that align with your needs. Additionally, consider exploring alternative YAML libraries for Go that may offer different features or customization options.

2023-12-19