Skip to content

Commit 2acd825

Browse files
committed
Add support for UnmarshalMaxMinDB
1 parent acf7938 commit 2acd825

File tree

8 files changed

+785
-1
lines changed

8 files changed

+785
-1
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
- `IncludeNetworksWithoutData` and `IncludeAliasedNetworks` now return a
77
`NetworksOption` rather than being one themselves. This was done to improve
88
the documentation organization.
9+
- Added `Unmarshaler` interface to allow custom decoding implementations for
10+
performance-critical applications. Types implementing
11+
`UnmarshalMaxMindDB(d *Decoder) error` will automatically use custom
12+
decoding logic instead of reflection, following the same pattern as
13+
`json.Unmarshaler`.
14+
- Added public `Decoder` type with methods for manual decoding including
15+
`DecodeMap()`, `DecodeSlice()`, `DecodeString()`, `DecodeUInt32()`, etc.
916

1017
## 2.0.0-beta.3 - 2025-02-16
1118

decoder.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package maxminddb
2+
3+
import "github.com/oschwald/maxminddb-golang/v2/internal/decoder"
4+
5+
// Decoder provides methods for decoding MaxMind DB data values.
6+
// This interface is passed to UnmarshalMaxMindDB methods to allow
7+
// custom decoding logic that avoids reflection for performance-critical applications.
8+
//
9+
// Types implementing Unmarshaler will automatically use custom decoding logic
10+
// instead of reflection when used with Reader.Lookup, providing better performance
11+
// for performance-critical applications.
12+
//
13+
// Example:
14+
//
15+
// type City struct {
16+
// Names map[string]string `maxminddb:"names"`
17+
// GeoNameID uint `maxminddb:"geoname_id"`
18+
// }
19+
//
20+
// func (c *City) UnmarshalMaxMindDB(d *maxminddb.Decoder) error {
21+
// for key, err := range d.DecodeMap() {
22+
// if err != nil { return err }
23+
// switch string(key) {
24+
// case "names":
25+
// names := make(map[string]string)
26+
// for nameKey, nameErr := range d.DecodeMap() {
27+
// if nameErr != nil { return nameErr }
28+
// value, valueErr := d.DecodeString()
29+
// if valueErr != nil { return valueErr }
30+
// names[string(nameKey)] = value
31+
// }
32+
// c.Names = names
33+
// case "geoname_id":
34+
// geoID, err := d.DecodeUInt32()
35+
// if err != nil { return err }
36+
// c.GeoNameID = uint(geoID)
37+
// default:
38+
// if err := d.SkipValue(); err != nil { return err }
39+
// }
40+
// }
41+
// return nil
42+
// }
43+
type Decoder = decoder.Decoder
44+
45+
// Unmarshaler is implemented by types that can unmarshal MaxMind DB data.
46+
// This follows the same pattern as json.Unmarshaler and other Go standard library interfaces.
47+
type Unmarshaler = decoder.Unmarshaler

internal/decoder/decoder.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,3 +382,16 @@ func (d *Decoder) DecodeSlice() iter.Seq[error] {
382382
d.reset(currentOffset)
383383
}
384384
}
385+
386+
// SkipValue skips over the current value without decoding it.
387+
// This is useful in custom decoders when encountering unknown fields.
388+
// The decoder will be positioned after the skipped value.
389+
func (d *Decoder) SkipValue() error {
390+
// We can reuse the existing nextValueOffset logic by jumping to the next value
391+
nextOffset, err := d.d.nextValueOffset(d.offset, 1)
392+
if err != nil {
393+
return err
394+
}
395+
d.reset(nextOffset)
396+
return nil
397+
}

0 commit comments

Comments
 (0)