-
Notifications
You must be signed in to change notification settings - Fork 18
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
Add WithOption for user-specified options #138
base: master
Are you sure you want to change the base?
Conversation
@mikeschinkel, this is an alternative API approach to user-defined options compared to #17. My prototype is highly unoptimized (allocates excessively) as I was more concerned with whether "is it possible" over "how fast can we make it". The advantage of
Another reasonable approach is to loosen the interface for
Yet another reasonable approach is to do something similar to
|
I skimmed through the PR and the links here, but it would be great to have a little more context on the rationale for this. As a maintainer, I like how simple this addition is, but as a user, even with the example, I have no idea how to correctly use the API. I saw the comparisons to |
Exactly. It's most useful for a type author who has types that implement For example: package geo
// Coordinate represents a position on the earth.
type Coordinate struct { ... }
func (Coordinate) MarshalJSONTo(enc *jsontext.Encoder, opts json.Options) error {
format, _ := json.GetOption(opts, json.WithOption[Format])
switch format {
case FormatDecimalDegrees: ...
case FormatPlusCodes: ...
}
}
// Format controls the format used to serialize the [Coordinate].
// Use [json.WithOption] to pass this to [json.Marshal] or [json.Unmarshal].
// The default format is in [DecimalDegrees].
type Format int
const (
// FormatDecimalDegrees formats the coordinate in decimal degrees.
// E.g., "40.7128, -74.0060"
FormatDecimalDegrees Format = iota
// FormatPlusCodes formats the coordinate using plus code.
// E.g., "87C8P3MM+XX"
FormatPlusCodes
...
) Thus, usage could be something like: json.Marshal(v, json.WithOption(geo.FormatPlusCodes)) where Originally, I thought it would be useful for protojson where the package could provide: func MarshalEncode(*jsontext.Encoder, opts Options) error and you could take the Thus, it would look something like: json.Marshal(v,
json.WithMarshaler(json.MarshalFuncV2(protojson.MarshalEncode)),
json.WithOption(protojson.MarshalOptions{
UseProtoNames: true,
})
) However, this could technically be achieved purely within json.Marshal(v,
json.WithMarshaler(json.MarshalFuncV2(protojson.MarshalOptions{
UseProtoNames: true,
}.MarshalEncode)),
) Thus, the use case for caller-specified marshalers is not particularly strong since the marshaler function provided to |
I have another wild idea. With v2 today, you can do something like: json.Marshal(struct {
T time.Time `json:",format:unix"`
}{time.Now()}) and have it format as: {"T":"1738048977.409709315"} But custom formatting of timestamps only works in the context of a singular struct field. What if I want to format the representation of time in a What if there was a way to specify the format for particular types? Consider the following type: // Format can be parameterized on a particular concrete type T
// to specify that T should be formatted in a particular manner.
type Format[T any] string This is a seemingly odd string type that is parameterized on a concrete type, but the parameterized type signals exactly which type this format is applicable for. If we had this, then you could combine this with var v []time.Time = ...
json.Marshal(v, json.WithOption(json.Format[time.Time]("unix"))) which could serialize as:
The internal implementation of the v, ok := json.GetOption(opts, json.WithOption[time.Time])
switch v {
case "unix": ...
case "milli": ...
case ...:
} Calling func WithFormat[T any](v Format[T]) Options {
return WithOption(v)
} in which case, usage would look like: json.WithFormat[time.Time]("unix") which reads fairly naturally in English as:
|
@dsnet is it possible to get the format options from once we define this to marshal
we need extract json schema for all it could be defined wrap function to build the my case to get json-schema: |
Thanks for the great work, this feature can greatly simplify the work in data cropping. package main
import (
"fmt"
"github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
)
type Lang string
type Content struct {
Text map[string]string
}
func (c Content) MarshalJSONTo(enc *jsontext.Encoder, opts json.Options) error {
lang, _ := json.GetOption(opts,
json.WithOption[Lang])
switch string(lang) {
case "*":
return json.MarshalEncode(enc, c.Text, opts)
default:
return json.MarshalEncode(enc,
map[string]string{
string(lang): c.Text[string(lang)],
}, opts,)
}
}
func main() {
c := Content{
Text: map[string]string{
"en-US": "hello",
"de-DE": "hallo",
},
}
text1, err := json.Marshal(&c,
json.WithOption(Lang("*")))
if err != nil {
panic(err)
}
fmt.Println(string(text1))
text2, err := json.Marshal(&c,
json.WithOption(Lang("en-US")))
if err != nil {
panic(err)
}
fmt.Println(string(text2))
text3, err := json.Marshal(&c,
json.WithOption(Lang("de-DE")))
if err != nil {
panic(err)
}
fmt.Println(string(text3))
} |
DO NOT SUBMIT: For experimental prototyping only.
The Options type is now plumbed up/down the call stack of
MarshalerTo and UnmarshalerFrom method calls.
There is value in allowing user-defined option values that
can alter the behavior of user-defined methods.
The WithOption function is parameterized on a particular T
and constructs a user-defined option of type T.
That single function can also be used to retrieve the option.