Skip to content
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

MarshalSingleStringAsArray global package variable #415

Open
charlesdaniels opened this issue Oct 21, 2024 · 0 comments
Open

MarshalSingleStringAsArray global package variable #415

charlesdaniels opened this issue Oct 21, 2024 · 0 comments

Comments

@charlesdaniels
Copy link

I have noticed that the package github.com/golang-jwt/jwt/v5 has a global config flag MarshalSingleStringAsArray. This was previously discussed in #277, which was closed as fixed by #278. However, #278 did not actually remove the MarshalSingleStringAsArray as far as I can tell. It seems like folks were in agreement at the time that having global mutable state at the package level was not preferred.

Imagine a program that needs to use this library with different settings in different areas of the program

In fact, I am writing such a program.

I did spend a little time thinking on how this could be accomplished. This is in fact a somewhat tricky problem, because the custom MarshalJSON function has no way to directly receive extra parameters. I think however that the type system could be used to encode this information. I have created a simple proof of concept for how this could work which you can see on the Go Playground here, and which I have reproduced below for posterity:

// You can edit this code!
// Click here and start typing.
package main

import (
	"encoding/json"
	"fmt"
)

type ClaimStrings interface {
	GetClaims() []string
	SetClaims([]string)
}

var _ ClaimStrings = (*ClaimStringsMarshalSingletonAsString)(nil)
var _ ClaimStrings = (*ClaimStringsMarshalSingletonAsArray)(nil)

type ClaimStringsMarshalSingletonAsString struct {
	claims []string
}

func (c *ClaimStringsMarshalSingletonAsString) GetClaims() []string {
	return c.claims
}

func (c *ClaimStringsMarshalSingletonAsString) SetClaims(newClaims []string) {
	c.claims = newClaims
}

func (c *ClaimStringsMarshalSingletonAsString) MarshalJSON() ([]byte, error) {
	if len(c.claims) == 1 {
		return json.Marshal(c.claims[0])
	}
	return json.Marshal(c.claims)
}

type ClaimStringsMarshalSingletonAsArray struct {
	claims []string
}

func (c *ClaimStringsMarshalSingletonAsArray) GetClaims() []string {
	return c.claims
}

func (c *ClaimStringsMarshalSingletonAsArray) SetClaims(newClaims []string) {
	c.claims = newClaims
}

func (c *ClaimStringsMarshalSingletonAsArray) MarshalJSON() ([]byte, error) {
	return json.Marshal(c.claims)
}

func main() {
	fmt.Println("Hello, 世界")

	cases := []ClaimStrings{
		&ClaimStringsMarshalSingletonAsArray{claims: []string{"a", "b", "c"}},
		&ClaimStringsMarshalSingletonAsString{claims: []string{"a", "b", "c"}},
		&ClaimStringsMarshalSingletonAsArray{claims: []string{"x"}},
		&ClaimStringsMarshalSingletonAsString{claims: []string{"x"}},
	}

	for i, c := range cases {
		j, err := json.Marshal(c)
		if err != nil {
			panic(err)
		}
		fmt.Printf("---- case %d\ngo: %+v\njson: %s\n", i, c, j)
	}
}

I think the biggest problem I see is that this will break compatibility for existing API users. Perhaps there might be a way to make the existing ClaimStrings type implement the interface (which would then need to be named something different -- also how would SetClaims() work? maybe it would need to be immutable).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant