From afd6268c5f2617fa7d644f9a0f35ee44e46eed2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20K=C3=A4stner?= Date: Wed, 16 Sep 2026 09:41:40 +0200 Subject: [PATCH] Use encoding.TextMarshaler for string-valued types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace json.Marshaler/json.Unmarshaler with the encoding package TextMarshaler/TextUnmarshaler interfaces on provider types that serialize as a plain string value. TextMarshaler is more semantically correct for these types: json.Marshaler declares "I can be any JSON value" (object, array, number), while TextMarshaler declares "I am a string" which matches what these types actually are. encoding/json calls MarshalText/UnmarshalText automatically, so JSON encoding continues to work unchanged. Additionally, the text interfaces are recognized by other encoders (YAML, XML, CSV), making the types usable in more contexts. UnmarshalText receives the raw string bytes with JSON quotes already stripped, removing the need to json.Unmarshal into an intermediate string variable first. Signed-off-by: Felix Kästner --- internal/provider/cisco/iosxr/bgp_peer.go | 18 +++++++---------- internal/provider/cisco/nxos/bgp.go | 17 +++++++--------- internal/provider/cisco/nxos/intf.go | 24 ++++++++++------------- internal/provider/cisco/nxos/provider.go | 10 ++++++---- internal/provider/cisco/nxos/vlan.go | 18 +++++++---------- internal/provider/openconfig/banner.go | 17 ++++++++++------ 6 files changed, 48 insertions(+), 56 deletions(-) diff --git a/internal/provider/cisco/iosxr/bgp_peer.go b/internal/provider/cisco/iosxr/bgp_peer.go index e90a63385..f612a5e69 100644 --- a/internal/provider/cisco/iosxr/bgp_peer.go +++ b/internal/provider/cisco/iosxr/bgp_peer.go @@ -4,7 +4,7 @@ package iosxr import ( - "encoding/json" + "encoding" "errors" "fmt" "math" @@ -20,8 +20,8 @@ var ( _ gnmiext.DataElement = (*BGPPeer)(nil) _ gnmiext.DataElement = (*BGPPeerOperStatus)(nil) - _ json.Marshaler = BGPPeerOperStatus{} - _ json.Unmarshaler = (*BGPPeerOperStatus)(nil) + _ encoding.TextMarshaler = BGPPeerOperStatus{} + _ encoding.TextUnmarshaler = (*BGPPeerOperStatus)(nil) ) type AfName string @@ -191,16 +191,12 @@ func (p *BGPPeerOperStatus) XPath() string { return "Cisco-IOS-XR-ipv4-bgp-oper:bgp/instances/instance[instance-name=" + BGPDefaultInstance + "]/instance-active/vrfs/vrf[vrf-name=" + p.Name + "]/sessions/session/connection-state" } -func (p BGPPeerOperStatus) MarshalJSON() ([]byte, error) { - return json.Marshal(p.State) +func (p BGPPeerOperStatus) MarshalText() ([]byte, error) { + return []byte(p.State), nil } -func (p *BGPPeerOperStatus) UnmarshalJSON(data []byte) error { - var t string - if err := json.Unmarshal(data, &t); err != nil { - return err - } - p.State = BGPPeerOperSt(t) +func (p *BGPPeerOperStatus) UnmarshalText(data []byte) error { + p.State = BGPPeerOperSt(data) return nil } diff --git a/internal/provider/cisco/nxos/bgp.go b/internal/provider/cisco/nxos/bgp.go index 92996735e..ab8dbf6fd 100644 --- a/internal/provider/cisco/nxos/bgp.go +++ b/internal/provider/cisco/nxos/bgp.go @@ -4,6 +4,7 @@ package nxos import ( + "encoding" "encoding/json" "errors" "strings" @@ -323,8 +324,8 @@ func (*MultisitePeerItems) XPath() string { } var ( - _ json.Marshaler = MultisitePeer{} - _ json.Unmarshaler = (*MultisitePeer)(nil) + _ encoding.TextMarshaler = MultisitePeer{} + _ encoding.TextUnmarshaler = (*MultisitePeer)(nil) ) type MultisitePeer struct { @@ -336,16 +337,12 @@ func (p *MultisitePeer) XPath() string { return "System/bgp-items/inst-items/dom-items/Dom-list[name=default]/peer-items/Peer-list[addr=" + p.Addr + "]/peerType" } -func (p MultisitePeer) MarshalJSON() ([]byte, error) { - return json.Marshal(p.PeerType) +func (p MultisitePeer) MarshalText() ([]byte, error) { + return []byte(p.PeerType), nil } -func (p *MultisitePeer) UnmarshalJSON(b []byte) error { - var t string - if err := json.Unmarshal(b, &t); err != nil { - return err - } - p.PeerType = BorderGatewayPeerType(t) +func (p *MultisitePeer) UnmarshalText(b []byte) error { + p.PeerType = BorderGatewayPeerType(b) return nil } diff --git a/internal/provider/cisco/nxos/intf.go b/internal/provider/cisco/nxos/intf.go index ba223f47d..48b9767ec 100644 --- a/internal/provider/cisco/nxos/intf.go +++ b/internal/provider/cisco/nxos/intf.go @@ -6,6 +6,7 @@ package nxos import ( "cmp" "context" + "encoding" "encoding/json" "errors" "fmt" @@ -670,20 +671,15 @@ const ( ) var ( - _ fmt.Stringer = UserFlags(0) - _ json.Marshaler = UserFlags(0) - _ json.Unmarshaler = (*UserFlags)(nil) + _ fmt.Stringer = UserFlags(0) + _ encoding.TextMarshaler = UserFlags(0) + _ encoding.TextUnmarshaler = (*UserFlags)(nil) ) -// UnmarshalJSON implements json.Unmarshaler. -func (f *UserFlags) UnmarshalJSON(b []byte) error { - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - +// UnmarshalText implements encoding.TextUnmarshaler. +func (f *UserFlags) UnmarshalText(b []byte) error { var flags UserFlags - for flag := range strings.SplitSeq(s, ",") { + for flag := range strings.SplitSeq(string(b), ",") { switch strings.TrimSpace(flag) { case "admin_state": flags |= UserFlagAdminState @@ -705,9 +701,9 @@ func (f *UserFlags) UnmarshalJSON(b []byte) error { return nil } -// MarshalJSON implements json.Marshaler. -func (f UserFlags) MarshalJSON() ([]byte, error) { - return json.Marshal(f.String()) +// MarshalText implements encoding.TextMarshaler. +func (f UserFlags) MarshalText() ([]byte, error) { + return []byte(f.String()), nil } // String implements fmt.Stringer. diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go index 8e1a49a42..9322f36fd 100644 --- a/internal/provider/cisco/nxos/provider.go +++ b/internal/provider/cisco/nxos/provider.go @@ -10,6 +10,7 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" + "encoding" "encoding/base64" "encoding/hex" "encoding/json" @@ -445,17 +446,18 @@ func (d *Directory) UnmarshalJSON(b []byte) error { return nil } +var _ encoding.TextUnmarshaler = (*dirTime)(nil) + // dirTime handles the non-standard timestamp format returned by NX-OS dir output. type dirTime struct { time.Time } -func (t *dirTime) UnmarshalJSON(b []byte) error { - s := strings.Trim(string(b), `"`) - if s == "" || s == "null" { +func (t *dirTime) UnmarshalText(b []byte) error { + if len(b) == 0 { return nil } - parsed, err := time.Parse("Jan 02 15:04:05 2006", s) + parsed, err := time.Parse("Jan 02 15:04:05 2006", string(b)) if err != nil { return err } diff --git a/internal/provider/cisco/nxos/vlan.go b/internal/provider/cisco/nxos/vlan.go index 46a94833f..4b446d618 100644 --- a/internal/provider/cisco/nxos/vlan.go +++ b/internal/provider/cisco/nxos/vlan.go @@ -4,7 +4,7 @@ package nxos import ( - "encoding/json" + "encoding" "github.com/ironcore-dev/network-operator/internal/transport/gnmiext" ) @@ -101,8 +101,8 @@ func (b *BDItems) GetByVXLAN(v string) *VXLAN { } var ( - _ json.Marshaler = VXLAN{} - _ json.Unmarshaler = (*VXLAN)(nil) + _ encoding.TextMarshaler = VXLAN{} + _ encoding.TextUnmarshaler = (*VXLAN)(nil) ) // VXLAN represents VXLAN encapsulation settings for a VLAN. @@ -116,15 +116,11 @@ func (v *VXLAN) XPath() string { return "System/bd-items/bd-items/BD-list[fabEncap=" + v.FabEncap + "]/accEncap" } -func (v VXLAN) MarshalJSON() ([]byte, error) { - return json.Marshal(v.AccEncap) +func (v VXLAN) MarshalText() ([]byte, error) { + return []byte(v.AccEncap), nil } -func (v *VXLAN) UnmarshalJSON(b []byte) error { - var encap string - if err := json.Unmarshal(b, &encap); err != nil { - return err - } - v.AccEncap = encap +func (v *VXLAN) UnmarshalText(b []byte) error { + v.AccEncap = string(b) return nil } diff --git a/internal/provider/openconfig/banner.go b/internal/provider/openconfig/banner.go index aa71bae1c..c5c4ff0cc 100644 --- a/internal/provider/openconfig/banner.go +++ b/internal/provider/openconfig/banner.go @@ -5,7 +5,7 @@ package openconfig import ( "context" - "encoding/json" + "encoding" "fmt" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" @@ -55,7 +55,11 @@ func toBannerType(t v1alpha1.BannerType) (BannerType, error) { } // Compile-time assertions. -var _ gnmiext.DataElement = (*Banner)(nil) +var ( + _ gnmiext.DataElement = (*Banner)(nil) + _ encoding.TextMarshaler = (*Banner)(nil) + _ encoding.TextUnmarshaler = (*Banner)(nil) +) // Banner targets a single banner leaf in the system config. type Banner struct { @@ -68,10 +72,11 @@ func (b *Banner) XPath() string { return fmt.Sprintf("openconfig-system:system/config/%s", b.Type) } -func (b *Banner) MarshalJSON() ([]byte, error) { - return json.Marshal(b.Message) +func (b Banner) MarshalText() ([]byte, error) { + return []byte(b.Message), nil } -func (b *Banner) UnmarshalJSON(data []byte) error { - return json.Unmarshal(data, &b.Message) +func (b *Banner) UnmarshalText(data []byte) error { + b.Message = string(data) + return nil }