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.
go-yaml
v3
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):
0x
fmt.Sprintf()
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:
Is there any simple way to generate hexadecimal numbers using go-yaml that I haven’t found?
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"` }
Let’s address your questions:
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.
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.
Uint32Hex
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:
``` 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 } ```
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.