[–] hnlmorg 3 days ago
[–] thayne 3 days ago
And even for unmarshalling you could get a compile time error if you try to unmarshall to a type that doesn't support it.
[–] hnlmorg 3 days ago
Go has this concept baked into the language. For example read and write interfaces so you can write data to an endpoint without having to worry about whether it’s a file system object, TCP/UDP socket, gzip achieve, and a plethora of other options too.
It’s a nice convenience feature and makes up considerably for the restrictions that the Go type system places.
And if you need higher performance code, or compile time safety, or have any other constraints that limit your ability to use these generalised interfaces, then you can use different APIs from the Go standard library to achieve the same thing.
You don’t need to call json.Marshal() if you don’t want to. It’s an option, but it’s just one API into the JSON toolbox.
[–] thayne 3 days ago
Secondly, other languages like rust, haskell, scala, etc. Have json APIs that are typesafe and give you compile time errors if you try to use a type that doesn't support (de) serialization . In the case of rust at least, the API is also general and can be used for other data types as well.
Even in go, you could have some more type safety if Marshall took an interface (such as MarshallerTo) rather than `any`. Although then you have the problem of how you implement that interface. So I guess you probably want some kind of "derive" functionality, or macros that can approximate it for a convenient and type-safe API.
P.S. gophers like to talk about how explicit being better than implicit, but using reflection for marshalling is very implicit.
[–] hnlmorg 3 days ago
If you want type safety then pass a struct to the marshal. Which is actually the idiomatic way to do it anyway. The type safety would be in that.
I’m not going to disagree with you that Gos type system is a rather blunt tool nor that Gos expressiveness is extremely rigid compared to many other popular languages. All of that is completely true. But I don’t agree that the v2 JSON API is a good example of that problem.
[–] thayne 2 days ago
The function is in the `encoding/json/v2` package. It might have a similar design, and maybe they will use the same function signature for other encoders, but it the functions are very much json specific.
> If you want type safety then pass a struct to the marshal.
But if you use a struct that has a field that can't be marshalled, you don't get a compile error, you get a runtime SemanticError. That's the problem.
[–] hnlmorg 2 days ago
But the point you keep missing is that the function signature needs to be encoder agnostic for it to work as an interface. That’s a hard requirement.
And since interfaces are a convenience to allow common abstractions to be reused at runtime, you cannot depend on things like generics (ie compile time abstractions) to generalise the data types for disparate serialisation formats.
I know this limitation well because I’ve built a shell with a typed pipeline and support for different serialisation formats. So make heavy use interfaces to achieve this. And I’ve spent a lot of time investigating alternative approaches. Each time, I’ve found interfaces to be a massive advantage despite any tradeoffs I end up making do to generalising a “class” (to borrow a term from OOP).
I’d honestly love to have a more sophisticated type system in Go. But that’s not a limitation in this specific instance.
> But if you use a struct that has a field that can't be marshalled, you don't get a compile error, you get a runtime SemanticError. That's the problem.
My point here was: if you know you’re creating a struct to pass to the mashaller at compile time, then you’re already going to use JSONAble fields. So it’s not an issue.
And if you don’t know what you’re passing to the mashaller a compile time, then you’re dependent on runtime checks regardless of the expressiveness of Gos type system.
I don’t disagree that Gos type system is inflexible. But in respect to this specific use case, your argument is a storm in a teacup. The type system isn’t a limiting factor here.
[–] awesome_dude 3 days ago
Compile time cannot check for runtime values (not even in Rust) - is your position that fields can only be populated at compile time?
[–] thayne 3 days ago
[–] hnlmorg 3 days ago
I think the same is true for channels too but I’m not near a computer to check.
I’ve abused the v1 API heavily* and haven’t found anything it couldn’t marshal. Though I’m yet to apply the same “fuzzing” (for lack of a better term) to v2.
* the v1 JSON marshaller underpins some core functionality in my alternative $SHELL. So I’ve thrown a lot of exotic type structures (by Gos standards at least) at it.
[–] thayne 2 days ago
https://pkg.go.dev/encoding/json/v2#Marshal says otherwise:
> The Go map key must encode as a JSON string, otherwise this results in a SemanticError
> All other Go types (e.g., complex numbers, channels, and functions) have no default representation and result in a SemanticError.
v1 has the same constraints.
[–] hnlmorg 2 days ago
The doc you linked to literally proves me right.
If you wanted type safety with JSON in Go then don’t use the marshal interface. Period.