Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XMLFormat: any(*time.Time) incorrectly encoded as string within structure #82

Closed
grahammiln opened this issue Oct 25, 2024 · 1 comment

Comments

@grahammiln
Copy link
Contributor

A *time.Time placed in an any typed field is incorrectly encoded as a string in an XML formatted plist

type Structure struct {
     UntypedPtr any
}
now := time.Date(2003, 2, 3, 9, 15, 30, 0, time.UTC)
s := &Structure{ UntypedPtr: &now }

Below is a test showing the current behaviour and expected output. The test shows that directly encoding the values works as expected. Only when the same values are embedded within a structure does the encoding bug appear:

func TestPlistDates(t *testing.T) {
	var (
		date       time.Time = time.Date(2003, 2, 3, 9, 15, 30, 0, time.UTC)
		datePtr              = &date
		untyped    any       = date
		untypedPtr any       = &date
	)

	expectDateEncoding := `<?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">
  <date>2003-02-03T09:15:30Z</date>
</plist>`

	if b, err := plist.MarshalIndent(date, plist.XMLFormat, "  "); err != nil {
		t.Error(err)
	} else if string(b) != expectDateEncoding {
		t.Errorf("expect:\n%s\ngot:\n%s", expectDateEncoding, string(b))
	}

	if b, err := plist.MarshalIndent(datePtr, plist.XMLFormat, "  "); err != nil {
		t.Error(err)
	} else if string(b) != expectDateEncoding {
		t.Errorf("expect:\n%s\ngot:\n%s", expectDateEncoding, string(b))
	}

	if b, err := plist.MarshalIndent(untyped, plist.XMLFormat, "  "); err != nil {
		t.Error(err)
	} else if string(b) != expectDateEncoding {
		t.Errorf("expect:\n%s\ngot:\n%s", expectDateEncoding, string(b))
	}

	if b, err := plist.MarshalIndent(untypedPtr, plist.XMLFormat, "  "); err != nil {
		t.Error(err)
	} else if string(b) != expectDateEncoding {
		t.Errorf("expect:\n%s\ngot:\n%s", expectDateEncoding, string(b))
	}

	type Structure struct {
		Date       time.Time
		DatePtr    *time.Time
		Untyped    any
		UntypedPtr any
	}
	s := &Structure{
		Date:       date,
		DatePtr:    datePtr,
		Untyped:    untyped,
		UntypedPtr: untypedPtr,
	}

	expectStructureEncoding := `<?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>Date</key>
    <date>2003-02-03T09:15:30Z</date>
    <key>DatePtr</key>
    <date>2003-02-03T09:15:30Z</date>
    <key>Untyped</key>
    <date>2003-02-03T09:15:30Z</date>
    <key>UntypedPtr</key>
    <date>2003-02-03T09:15:30Z</date>
  </dict>
</plist>`

	if b, err := plist.MarshalIndent(s, plist.XMLFormat, "  "); err != nil {
		t.Error(err)
	} else if string(b) != expectStructureEncoding {
		t.Errorf("expect:\n%s\ngot:\n%s", expectStructureEncoding, string(b))
	}
}

The current incorrect output is below. The final key/value pair should be a date, not a string:

<?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>Date</key>
	<date>2003-02-03T09:15:30Z</date>
	<key>DatePtr</key>
	<date>2003-02-03T09:15:30Z</date>
	<key>Untyped</key>
	<date>2003-02-03T09:15:30Z</date>
	<key>UntypedPtr</key>
	<string>2003-02-03T09:15:30Z</string>
  </dict>
</plist>

This is an esoteric bug that is unlikely to have a significant impact. I am documenting the problem here in case others stumble upon it.

@DHowett
Copy link
Owner

DHowett commented Oct 26, 2024

This is an excellent catch, thank you!

DHowett added a commit that referenced this issue Dec 27, 2024
Working on #82, I realized that this is probably a case we should
support. Testing encoding/json confirmed that.

The marshaler uses the `innermostValue` helper introduced in the last
commit; the unmarshaler just iterates and creates successive new nested
elements until it's out of pointer types.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants