99designs / gqlgen

go generate based graphql server library
https://gqlgen.com
MIT License
9.92k stars 1.16k forks source link

Final solution for the eternal null VS undefined problem #1416

Open frederikhors opened 3 years ago

frederikhors commented 3 years ago

I think this topic could win the award as the most debated topic on web all along (since that damn day null was invented, at least :smile:)!

I would like to summarize here everything I have read and tried on the subject, in the hope that Santa Claus will give us the robust solution we deserve this year (at least for gqlgen).

THE PROBLEM

As a developer I would like a quick way to identify in my resolvers if the user has deliberately chosen null as the value of the field (string/int/time/...) or if he is using sparse updates and has omitted those fields entirely.

PROPOSALS READ AROUND

  1. the official solution proposed by Gqlgen team (@vektah at least), use map[string]interface{} (https://gqlgen.com/reference/changesets/); but of course there are several counter-arguments

  2. nullable types (proposed many times, one for all here); but as @jszwedko said it seems to not work either

  3. checking ArgumentMap from context [as suggested here]()https://github.com/99designs/gqlgen/issues/505#issuecomment-586584763; the solution I'm using although it is very verbose and I need things like code generation

  4. methods for setters proposed by the amazing @danilobuerger which is still a WIP

THREADS ON THE TOPIC

  1. https://github.com/99designs/gqlgen/issues/505

  2. https://github.com/99designs/gqlgen/issues/506

  3. https://github.com/99designs/gqlgen/issues/977

  4. https://github.com/99designs/gqlgen/issues/866

  5. https://github.com/99designs/gqlgen/issues/646

WE ARE NOT ALONE

  1. https://webdevstation.com/post/Work-with-nullable-strings-in-gqlgen-GraphQL-Server-0x4e28

  2. https://www.calhoun.io/how-to-determine-if-a-json-key-has-been-set-to-null-or-not-provided/

  3. https://github.com/guregu/null/issues/39

  4. https://github.com/graphql-go/graphql/pull/401

  5. https://github.com/graphql-go/graphql/issues/178

  6. https://github.com/datastax/cassandra-data-apis/issues/6

  7. https://github.com/graph-gophers/graphql-go/issues/210

  8. https://github.com/graphql-rust/juniper/issues/183

  9. https://github.com/graphql-rust/juniper/issues/108

PLEASE

Help us, gqlgen team.

🙏

nanozuki commented 3 years ago

Very glad to see your effort to deal with the problem of optional/nullable. I vote for solution 2: Generate NullType to handle optional params/models.

  1. It's usually used in golang ecosystem, also in std packages: https://golang.org/pkg/database/sql/#NullString
  2. In graphQL schema, undefined has no difference to null. If the type is String! it can't be also null and undefined.
  3. For the motioned problem: IF a field is optional, the field value can be null, and the meaning of these two situations are different, a developer should use a special type, maybe NullNullString. (In some other language such as rust, you can see some type like Option<Option<T>>)
  4. gqlgen is an awesome code generation framework, and code generation is the best way to live with NullType before the generic type feature publish.
  5. After the generic type feature published, it's easy to migrate to generic types since no need to change the code structure.
oiime commented 3 years ago

Here's an additional, slightly different but just as ugly solution, it uses GetFieldContext instead of GetRequestContext and gives back a list of "touched" fields within an object

// GetArgumentFieldnames returns a list of fieldnames from an argument
func (r *Resolver) GetArgumentFieldnames(ctx context.Context, name string) []string {
    fieldContext := graphql.GetFieldContext(ctx)
    names := []string{}
    for _, arg := range fieldContext.Field.Arguments {
        if arg.Name != name {
            continue
        }
        for _, child := range arg.Value.Children {
            names = append(names, child.Name)
        }
    }
    return names
}
Panakotta00 commented 2 years ago

Is there any progress on this?

I would allow for a optional struct at least for input fields, that allows to differentiate defined and undefined fields.

Especially now with Go 1.18's generics this could be implemented, more or less type safe.

Here a Reddit Post that discusses optionals a bit further.

maaft commented 2 years ago

Also interested!

sonatard commented 1 year ago

gqlgen supported Go 1.18 and Generics. Supporting Optional has become a reality.

Desuuuu commented 1 year ago

I drafted a PR to address this if anyone wants to have a look!

StevenACoffman commented 1 year ago

I would like to get more opinions on https://github.com/99designs/gqlgen/pull/2585#issuecomment-1479841839 please!

Jin5823 commented 7 months ago
# graphql
type Mutation {
  submit(input: SubmitInput!): SubmitPayload
}

input SubmitInput {
  firstName: String
  lastName: String
  email: String
}
// go
type SubmitInput struct {
    FirstName **string                            
    LastName  **string                            
    Email     **string                            
}

var input SubmitInput

if input.Email == nil {
    // this is undefined
}
if input.Email != nil && *input.Email == nil {
    // this is null
}
if input.Email != nil && *input.Email != nil {
    email := **input.Email
    // this is value
}

Hi !~ Can this be one of solutions ?