Description
Proposal Details
What's wrong with the current Value.String
behaviour?
Nothing's really wrong it's just that it doesn't quite fit the expected pattern at first glance. Value.Float
, Value.Int
, etc. all return or panic. Value.String
coerces. This can be a problem in cases like this:
form := js.Global().Get("myform") // get <form id=myform> HTMLFormElement
formdata := js.Global().Get("FormData").New(form) // js FormData class
value := formdata.Get("chosen_os")
if slices.Contains([]string{"windows", "darwin", "linux"}, value.String()) {
fmt.Println("Ok! Good choice!") // "why won't this work" I asked...
} else {
fmt.Println("Bad choice.")
}
The problem? formdata.chosen_os
doesn't exist. You need to call the formdata.get("chosen_os")
JS method to actually get the formdata value. This is confusing because there's also a Go method called "Get". This was then compounded by the fact that this undefined value was silently coerced to a string by Value.String()
without panicking like I would expect. Obviously <undefined>
(which is what it stringified to) is not in the {"windows", "darwin", "linux}
list and so it always returned false.
Now could this issue have even solved had I just printed fmt.Println(value.String())
to the console to see what it was? yes. But it could also be solved if the method helped me out and enforced some runtime type safety by panicking just like .Int()
would have lol
I know it still needs to work when printing
I get that. I want this to ALWAYS work:
jsValue := js.ValueOf(100)
fmt.Println(jsValue)
jsValue = js.ValueOf(map[string]any{
"hello": []any{100, "a string", true},
})
fmt.Printf("value: %v\n", jsValue)
What I propose: use the fmt.Formatter
interface to hijack formatting before fmt.Stringer
is even considered. Let Value.String()
panic and leave formatting to Value.Format()
. (it would also be good to define a Value.GoString()
that you can easily call directly while we're at it)
func (v Value) Format(f fmt.State, verb rune) {
// some logic here
}
func (v Value) GoString() string {
// current Value.String logic here
}
This would have the added benefit of letting %d %f and other specifiers be recognizable and possibly automatically call .Int() or .Float() and then formatting them appropriately.
Something like this maybe? You might have to do some shenanigans to get around circular importing the "fmt" package idk
func (v Value) Format(f fmt.State, verb rune) {
if verb == 'v' || verb == 's' { // ⭐ handle %v and %s just like right now with .String()!
fmt.Fprintf(f, fmt.FormatString(f, verb), v.GoString())
} else if verb == 't' {
// fmt.Fprintf(f, "%t", v) where v isn't a Go bool would panic anyway.
// v.Bool() is OK to panic itself instead.
fmt.Fprintf(f, fmt.FormatString(f, verb), v.Bool())
} else if verb == 'b' || verb == 'c' || verb == 'd' || verb == 'o' ... {
fmt.Fprintf(f, fmt.FormatString(f, verb), v.Int()) // OK to panic
} else if verb == 'b' || verb == 'e' || verb == 'f' ... {
fmt.Fprintf(f, fmt.FormatString(f, verb), v.Float()) // OK to panic
} else if ... // etc for as many "act like a $TYPE" cases as you want
} else {
// fall back to default by wrapping in a newtype without a .Format()
type valueAlias = Value
type Value valueAlias // same name so that %T shows "js.Value"
fmt.Fprintf(f, fmt.FormatString(f, verb), Value(v))
}
}
anyways the core idea: use .Format() to let everything behaviour-wise stay the same with regards to fmt.Print() and friends so that .String() can really mean "get this as a string or else panic if it's not really a string".
DOWNSIDES TO THIS CHANGE: It's a change. Any change in behaviour can be bad. In this case though, syscall/js is experimental and this shouldn't change behaviour that much. The one case where things have to change is something like this:
// print a generic js value to the console
fmt.Printf("%s\n", jsValue.String())
...which you can't really do much about. semantically, at first glance I would think that means "this jsValue is for sure a string type. get it as a go string" but that's NOT what it means. it means "get this jsValue of any type as a human-readable string repr". I think that belongs in .GoString() or .Format() but that's a change from how it is now. And that means that this could beak such code if it's written like above.
Basically, I want some type-checking method to get a js.Value as a string or else panic similar to .Int() and .Float() and .Bool(). I would like to revive #29642 discussion to find a solution to this so that someone else doesn't spend multiple hours debugging something like I was.
Alternatives
#29642 had some discussion about this. .Format() was never mentioned though.
- Add a MustString()
- Add a AsString()
- Do nothing
Metadata
Metadata
Assignees
Type
Projects
Status