Skip to content

encoding/wkt: preserve EMPTY members when unmarshalling a collection - #178

Open
gaoflow wants to merge 1 commit into
paulmach:masterfrom
gaoflow:fix-wkt-collection-empty-member-roundtrip
Open

encoding/wkt: preserve EMPTY members when unmarshalling a collection#178
gaoflow wants to merge 1 commit into
paulmach:masterfrom
gaoflow:fix-wkt-collection-empty-member-roundtrip

Conversation

@gaoflow

@gaoflow gaoflow commented Jul 28, 2026

Copy link
Copy Markdown

Problem

wkt.Marshal produces spec-valid WKT for a GEOMETRYCOLLECTION whose members are EMPTY geometries, but wkt.Unmarshal silently drops those members, so the library cannot read back its own output:

gc := orb.Collection{orb.Point{1, 2}, orb.LineString{}}
s := wkt.MarshalString(gc)                 // "GEOMETRYCOLLECTION(POINT(1 2),LINESTRING EMPTY)"
got, _ := wkt.Unmarshal(s)
// got is GEOMETRYCOLLECTION(POINT(1 2)) -- the empty member is gone

This reproduces for every empty-capable member type (LINESTRING/POLYGON/MULTIPOINT/MULTILINESTRING/MULTIPOLYGON/GEOMETRYCOLLECTION EMPTY), for mixed empty/non-empty collections, and for nested collections.

Root cause

splitGeometryCollection only appended a member once a ( had been pushed onto its rune stack. An EMPTY geometry has no parentheses, so its token never triggered a yield and stayed stuck in the stack. unmarshalCollection then discarded the resulting "" via if len(g) == 0 { continue }.

For example splitGeometryCollection("(LINESTRING EMPTY)") returned [""] and "(POINT(1 2),LINESTRING EMPTY)" returned ["", "POINT(1 2)"] -- the empty member vanished.

Fix

Rewrite the splitter to split the collection body on top-level commas while tracking bracket depth. This treats empty members (no parens) and nested-collection members (inner commas) uniformly, and removes the len(g)==0 swallow. Non-empty and nested non-empty collections are unaffected.

Tests

TestUnmarshalCollection_roundTrip asserts the Unmarshal(Marshal(gc)) == gc self-oracle over each empty-capable type, mixed empty/non-empty, and nested collections, checking member count, member equality and marshal idempotence. TestSplitGeometryCollection covers the splitter directly, including nested members whose inner commas must not split. go test ./... passes for the affected packages (the pre-existing simplify Visvalingam benchmark failure is unrelated and also fails on master).

Marshal emits spec-valid WKT for a GEOMETRYCOLLECTION whose members are
EMPTY geometries (e.g. GEOMETRYCOLLECTION(LINESTRING EMPTY)), but Unmarshal
silently dropped every empty member, so Unmarshal(Marshal(gc)) != gc.

splitGeometryCollection only yielded a member after seeing a '(' on its
stack; an EMPTY member has no parentheses, so it was never emitted, and
unmarshalCollection then discarded the "" result via len(g)==0. Rewrite the
splitter to split on top-level commas while tracking bracket depth, which
handles empty members and nested collections uniformly, and drop the swallow.

Add round-trip coverage over every empty-capable type, mixed empty/non-empty
and nested collections, plus direct splitter cases.
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

Successfully merging this pull request may close these issues.

1 participant