go-plist icon indicating copy to clipboard operation
go-plist copied to clipboard

Embedded structs' fields are not omitted

Open khpeek opened this issue 2 years ago • 2 comments

Unlike Go's encoding/json, it would appear that go-plist still marshals the fields of embedded structs even if those structs are nil and the omitempty tag is specified. Consider the following example program:

package main

import (
	"fmt"
	"log"

	"howett.net/plist"
)

type Location struct {
	Name        string
	Type        string
	*Restaurant `plist:",omitempty"`
	*Store      `plist:",omitempty"`
}

type Restaurant struct {
	Menu string
}

type Store struct {
	Merchandise string
}

func main() {
	location := Location{
		Name: "Taqueria Cancun",
		Type: "Restaurant",
		Restaurant: &Restaurant{
			Menu: "Tacos",
		},
	}

	locationPlist, err := plist.MarshalIndent(&location, plist.XMLFormat, "\t")
	if err != nil {
		log.Fatalf("marshal location: %v", err)
	}

	fmt.Println(string(locationPlist))
}

The resulting property list is

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
		<key>Menu</key>
		<string>Tacos</string>
		<key>Merchandise</key>
		<string/>
		<key>Name</key>
		<string>Taqueria Cancun</string>
		<key>Type</key>
		<string>Restaurant</string>
	</dict>
</plist>

This plist representation contains an empty Merchandise string which is not pertinent to a Restaurant and which I'd like to omit. The encoding/json library does this as seen from the example below:

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Location struct {
	Name string
	Type string
	*Restaurant
	*Store
}

type Restaurant struct {
	Menu string
}

type Store struct {
	Merchandise string
}

func main() {
	location := Location{
		Name: "Taqueria Cancun",
		Type: "Restaurant",
		Restaurant: &Restaurant{
			Menu: "Tacos",
		},
	}

	locationJSON, err := json.Marshal(&location)
	if err != nil {
		log.Fatalf("marshal location: %v", err)
	}

	fmt.Println(string(locationJSON))
}

Running this (and piping the output to jq) results in

{
  "Name": "Taqueria Cancun",
  "Type": "Restaurant",
  "Menu": "Tacos"
}

where the Merchandise key is absent. Should the go-plist library not also have this behavior?

khpeek avatar Jan 23 '23 21:01 khpeek

One way to work around this (which is probably better pattern in general) is to embed the Location struct into the Restaurant and Store structs rather than vice versa:

package main

import (
	"fmt"
	"log"

	"howett.net/plist"
)

type Location struct {
	Name string
	Type string
}

type Restaurant struct {
	Location
	Menu string
}

type Store struct {
	Location
	Merchandise string
}

func main() {
	restaurant := Restaurant{
		Location: Location{
			Name: "Taqueria Cancun",
			Type: "Restaurant",
		},
		Menu: "Tacos",
	}

	locationPlist, err := plist.MarshalIndent(&restaurant, plist.XMLFormat, "\t")
	if err != nil {
		log.Fatalf("marshal location: %v", err)
	}

	fmt.Println(string(locationPlist))
}

This results in the desired output,

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
		<key>Menu</key>
		<string>Tacos</string>
		<key>Name</key>
		<string>Taqueria Cancun</string>
		<key>Type</key>
		<string>Restaurant</string>
	</dict>
</plist>

However, my point still stands that go-plist's behavior deviates from that of encoding/json in this regard.

khpeek avatar Jan 23 '23 21:01 khpeek

Thanks for the report, and comprehensive investigation! I'm a bit behind on my personal repositories, but I'll try to get to this this week. You're right - this should work like encoding/json. There's a chance that this behavior has changed over time and I missed it, or I never mimicked encoding/json properly. :smile:

DHowett avatar Jan 30 '23 04:01 DHowett