Skip to content

Add MarshalINI and UnmarshalINI for custom key values - #427

Merged
unknwon merged 8 commits into
v2from
v2-marshal-ini
Nov 11, 2026
Merged

unknwon merged 8 commits into
v2from
v2-marshal-ini

Conversation

@unknwon

@unknwon unknwon commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Describe the pull request

Types can now format and parse their own key values:

type Marshaler interface {
	MarshalINI() ([]byte, error)
}

type Unmarshaler interface {
	UnmarshalINI([]byte) error
}

Where they apply

  • Reading: MapTo and StrictMapTo call UnmarshalINI with the value as Key.String returns it, for a plain field, a pointer field, and each element of a slice field. List, ValidList, StrictList, In and Range call it too for a Scalar type, so ini.List[Permission](k, ",") works for a named int enum.
  • Writing: ReflectFrom calls MarshalINI for the same kinds of field, with either a value or a pointer receiver.
  • Errors: an error from UnmarshalINI is handled like a value that doesn't parse. MapTo leaves a field unchanged and makes a failing slice element the zero value. StrictMapTo and StrictList return the error. ReflectFrom returns an error from MarshalINI.

Rules

  1. A string, boolean or number type uses whichever method it implements, and its kind handles the other direction. A named int enum with UnmarshalINI reads Perm = write instead of failing on a non-number.
  2. Any other type, such as a struct, slice or map type, maps to one key only when it implements both methods, so a type such as a host:port pair can be one value. With only one of them, a struct still maps to a section, a slice to a list, and other types stay unsupported, in both directions. A struct that embeds a type with both methods gets them, and so maps to one key too. Where sections are asked for explicitly, by a nonunique field or a value passed to a section of a file with AllowNonUniqueSections, any struct type still maps to sections.
  3. time.Time keeps its own handling.

Tests

  • TestMarshalerUnmarshaler covers an enum and a struct type: reading, writing and reading back, errors in MapTo, StrictMapTo and ReflectFrom, and the generic accessors. A fifth subtest checks that slice, struct and map types with only one method keep their usual mapping. Each subtest fails without the change it covers.
  • TestStructMappingMatrix maps a number, struct, slice, map and array type, each with no methods, either method, or both, in every field shape: plain, pointer, pointer to pointer, slice, array, interface, allowshadow, delim, in a section, nonunique, embedded, extends, and passed for a section. Each must round trip through ReflectFrom, Write and MapTo, or be unsupported in both directions. The rows for types without both methods match v2. Two cases are written but not read, as on v2, and the test expects exactly those: a pointer to a slice, and a pointer to a pointer.
  • FuzzStructMapping now has enum, custom struct, slice and map fields, and custom nonunique sections. A 45-second run found nothing.

Link to the issue: #353

Checklist

  • I agree to follow the Code of Conduct by submitting this pull request.
  • I have read and acknowledge the Contributing guide.
  • I have added test cases to cover the new code.

AI usage

Claude Code ran the self-review (bugs, docs, security and repository rules, each by a separate review agent) and wrote the follow-up commits 68f90c3, d679f6c, 346a07c, f838383, 2a7d2c1, 9a4d4b8 and the revert of the last two's read support, which fix the one-method slice, struct, map and array cases, custom struct types where sections are asked for, and the doc inaccuracies those reviews found, and add the matrix test.

@unknwon
unknwon force-pushed the v2-marshal-ini branch 2 times, most recently from 6b8170d to f3f52e5 Compare November 10, 2026 21:21
@unknwon
unknwon added this pull request to stack #428 November 11, 2026 02:16
@unknwon
unknwon force-pushed the v2-marshal-ini branch 3 times, most recently from d9cbca5 to f41421d Compare November 11, 2026 03:37
Base automatically changed from v2-next to v2 November 11, 2026 04:59
A type with MarshalINI or UnmarshalINI formats or parses its own key
value in ReflectFrom, MapTo and StrictMapTo, as a field, a pointer field
or a slice element. List, ValidList, StrictList, In and Range use
UnmarshalINI too. An error leaves the field unchanged, and StrictMapTo,
StrictList and ReflectFrom return it.

A struct type that implements either interface maps to a key instead of
a section, so types such as a host:port pair can be one value.
A slice type with only MarshalINI lost list reading, and one with only
UnmarshalINI lost list writing. A struct type with only one of them
could no longer be read or written as a section. Both now keep their
usual mapping unless they implement both methods.

Also correct the docs of errors in slice elements, the generic
accessors, which take Scalar types, and Scalar types with UnmarshalINI.
@unknwon unknwon changed the title v2: MarshalINI and UnmarshalINI for custom key values Add MarshalINI and UnmarshalINI for custom key values Nov 11, 2026
A map or array type with only MarshalINI was written by ReflectFrom but
skipped by MapTo without an error. Only string, boolean and number
types, whose kind handles the other direction, now map to a key with one
method.
Section.ReflectFrom on a file with AllowNonUniqueSections rejected a
pointer to a struct type with MarshalINI and UnmarshalINI, while MapTo
accepted it. A nonunique field or a value passed for such a section now
maps any struct type but time.Time to sections, in both directions.
TestStructMappingMatrix maps types of each kind that struct mapping
treats differently, with no methods, either method, or both, in every
field shape: plain, pointer, slice, allowshadow, delim, in a section,
nonunique, embedded, extends, and passed for a section. Each must
round trip through ReflectFrom, Write and MapTo, or be unsupported in
both directions. Rows for types without both methods match v2.

It found one more mismatch, which v2 already had: a pointer to a slice
was written as the slice but couldn't be read. It now reads as one.

FuzzStructMapping also gets custom struct, slice and map fields, a
pointer to a slice, and custom nonunique sections.
ReflectFrom wrote a **T field as the value it points to, but MapTo
rejected it, as it already did on v2. MapTo now reads any depth of
pointers, keeping how a single pointer reads: a value that doesn't
parse leaves it as it is.

The matrix test gets pointer to pointer, array and interface fields,
and array types with and without the methods.
They were written but not read on v2 too, and nobody asked for them, so
they stay that way. The matrix test expects exactly those cases to be
written but not read, and the fuzzer drops their fields.
@unknwon
unknwon merged commit 0be62c7 into v2 Nov 11, 2026
7 checks passed
@unknwon
unknwon deleted the v2-marshal-ini branch November 11, 2026 14:50
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