Skip to content

Commit f75ba3c

Browse files
committed
Add WithOption for user-specified options
1 parent 0640c11 commit f75ba3c

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

Diff for: internal/jsonopts/options.go

+13
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ type Struct struct {
2222

2323
CoderValues
2424
ArshalValues
25+
26+
// UserValues is a map of user-defined option values
27+
// keyed by [reflect.Type] to the concrete user-defined value.
28+
// The key type is not [reflect.Type] to avoid a dependency on reflect.
29+
UserValues map[any]any
2530
}
2631

2732
type CoderValues struct {
@@ -167,6 +172,14 @@ func (dst *Struct) Join(srcs ...Options) {
167172
dst.Format = src.Format
168173
dst.FormatDepth = src.FormatDepth
169174
}
175+
if len(src.UserValues) > 0 {
176+
if dst.UserValues == nil {
177+
dst.UserValues = make(map[any]any)
178+
}
179+
for k, v := range src.UserValues {
180+
dst.UserValues[k] = v
181+
}
182+
}
170183
default:
171184
JoinUnknownOption(dst, src)
172185
}

Diff for: options.go

+45-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package json
66

77
import (
88
"fmt"
9+
"reflect"
910

1011
"github.com/go-json-experiment/json/internal"
1112
"github.com/go-json-experiment/json/internal/jsonflags"
@@ -245,20 +246,47 @@ func WithUnmarshalers(v *Unmarshalers) Options {
245246
return (*unmarshalersOption)(v)
246247
}
247248

249+
// WithOption constructs a user-defined option value.
250+
// Later occurrences of an option of a particular type overrides
251+
// prior occurrences of an option of the exact same type.
252+
// The type T must be a declared type in a package or
253+
// a pointer to such a type.
254+
//
255+
// A user-defined option can be constructed using:
256+
//
257+
// var v mypkg.MyType = ...
258+
// opts := json.WithOption(v)
259+
//
260+
// The option value can be retrieved using:
261+
//
262+
// v, ok := json.GetOption(opts, json.WithOption[mypkg.MyType])
263+
func WithOption[T any](v T) Options {
264+
t := reflect.TypeFor[T]()
265+
if t.PkgPath() == "" && (t.Kind() != reflect.Pointer || t.Elem().PkgPath() == "") {
266+
panic(fmt.Sprintf("%T must a declared type or a pointer to such a type", v))
267+
}
268+
// TODO: Limit this to non-interface types?
269+
return userOption[T]{v}
270+
}
271+
248272
// These option types are declared here instead of "jsonopts"
249273
// to avoid a dependency on "reflect" from "jsonopts".
250274
type (
251275
marshalersOption Marshalers
252276
unmarshalersOption Unmarshalers
277+
userOption[T any] struct{ v T }
253278
)
254279

255280
func (*marshalersOption) JSONOptions(internal.NotForPublicUse) {}
256281
func (*unmarshalersOption) JSONOptions(internal.NotForPublicUse) {}
282+
func (userOption[T]) JSONOptions(internal.NotForPublicUse) {}
283+
func (userOption[T]) key() any { return reflect.TypeFor[T]() }
284+
func (v userOption[T]) val() any { return v.v }
257285

258286
// Inject support into "jsonopts" to handle these types.
259287
func init() {
260288
jsonopts.GetUnknownOption = func(src *jsonopts.Struct, zero jsonopts.Options) (any, bool) {
261-
switch zero.(type) {
289+
switch zero := zero.(type) {
262290
case *marshalersOption:
263291
if !src.Flags.Has(jsonflags.Marshalers) {
264292
return (*Marshalers)(nil), false
@@ -269,6 +297,14 @@ func init() {
269297
return (*Unmarshalers)(nil), false
270298
}
271299
return src.Unmarshalers.(*Unmarshalers), true
300+
case interface {
301+
key() any
302+
val() any
303+
}: // implemented by [userOption]
304+
if v, ok := src.UserValues[zero.key()]; ok {
305+
return v, true
306+
}
307+
return zero.val(), false
272308
default:
273309
panic(fmt.Sprintf("unknown option %T", zero))
274310
}
@@ -281,6 +317,14 @@ func init() {
281317
case *unmarshalersOption:
282318
dst.Flags.Set(jsonflags.Unmarshalers | 1)
283319
dst.Unmarshalers = (*Unmarshalers)(src)
320+
case interface {
321+
key() any
322+
val() any
323+
}: // implemented by [userOption]
324+
if dst.UserValues == nil {
325+
dst.UserValues = make(map[any]any)
326+
}
327+
dst.UserValues[src.key()] = src.val()
284328
default:
285329
panic(fmt.Sprintf("unknown option %T", src))
286330
}

0 commit comments

Comments
 (0)