99designs / gqlgen

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

Request for omitempty on json tag for models #789

Closed apoggi-carecloud closed 1 year ago

apoggi-carecloud commented 5 years ago

Instead of having to manually define models with the 'omitempty' json tag, it would be great if we could pass that through the yaml config file.

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

iliasgal commented 5 years ago

Any update on this? Or any workaround about the omitempty tag?

apoggi-carecloud commented 5 years ago

I just wrote the omitempty tags via code generation to the files

zhulinwei commented 4 years ago

@apoggi-carecloud Does you write a plugins to add omitempty tag? Or you wrote the omitempty tags to the files after code generation?

RobinKamps commented 4 years ago

Omit per field could be a be a usefull feature - if the models are marshalled for internal usage. The generated marshaling to match a graphql query returns empty things. It has no external effect if a struct attribute is marked with omitempty tag, if the attribute is marked with an ! in the schema and therefore is not generated as a pointer. I prefer using no pointers especially for strings (to avoid the nasty two step instantiation) and mark everything with a !. I reuse the generated models for typesafe db ops and add typesafe validation (via ozzo) to them in another file, which is not overwritten by the generator. usually i add the omitempty tags to every model (and do other additions and custom renamings (like adding a dot in the json name etc.) via a small script like (example only for adding all omitempty tags), which is automatically called after every new model generation:

path := "models/models_gen.go"
    read, err := ioutil.ReadFile(path)
    if err != nil {
        panic(err)
    }

    newContents = strings.Replace(newContents, "\"`", ",omitempty\"`", -1)

    err = ioutil.WriteFile(path, []byte(newContents), 0)
    if err != nil {
        panic(err)
    }
KenG98 commented 4 years ago

Let's re-open this - omitempty is important for marshaling structs to JSON to send in a request. Lots of APIs have a default value for a field, but if you marshal a struct with an empty field right now, it'll use the default value (like an empty string), which will override the API's default.

harmnot commented 4 years ago

I only need use model_gen hook = https://gqlgen.com/recipes/modelgen-hook/

let's say you have gqlgen.yml in root dir, then create a file called runner_model_gen.go

in runner_model_gen.go:

package main

import (
    "fmt"
    "github.com/99designs/gqlgen/api"
    "github.com/99designs/gqlgen/codegen/config"
    "github.com/99designs/gqlgen/plugin/modelgen"
    "os"
    "strings"
)

func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild {
    for _, model := range b.Models {
        for _, field := range model.Fields {
                       // this is the logic to add omitempty
            omit := strings.TrimSuffix(field.Tag, `"`)
            field.Tag = fmt.Sprintf(`%v,omitempty"`, omit)
        }
    }
    return b
}

func main() {
    cfg, err := config.LoadConfigFromDefaultLocations()
    if err != nil {
        fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
        os.Exit(2)
    }

    p := modelgen.Plugin{
        MutateHook: mutateHook,
    }

    err = api.Generate(cfg,
        api.NoPlugins(),
        api.AddPlugin(&p),
    )
    if err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
        os.Exit(3)
    }
}

then run them go run runner_model_gen.go

check your model_gen.go then u can see the result, enjoy:)

thestephenstanton commented 4 years ago

I was looking for this as a feature of gqlgen and stumbled upon this issue. And I found myself wondering why it might not have been implemented (personally I like omitempty). But when I thought about it, when you have omitempty, it would be odd for someone to have a query:

{
  people {
    name
    nickname
  }
}

And get back something like:

{
    "data": {
        "people": [
            {
                "name": "Stephen",
                "nickname": "Steve"
            },
            {
                "name": "Joe"
            }
        ]
    }
}

instead of something like

{
    "data": {
        "people": [
            {
                "name": "Stephen",
                "nickname": "Steve"
            },
            {
                "name": "Joe",
                "nickname": null
            }
        ]
    }
}

Especially given that your query directly asks for it. So maybe in the graphql world, it is good to return back the empty value of the field.

But then again, some empty values, like for bools and ints, could be deceptive by returning falses and 0s (nvm, that is what the pointers are for lol)

Thoughts?

sanjeevchopra commented 3 years ago

This causes default values for input types not to be applied.

input Foo {
  principalType: PrincipalType = User
}

enum PrincipalType {
  User
  Org
}
type Foo struct {
    PrincipalType *PrincipalType `json:"principalType" yaml:"principalType"`
}

//...

func (ec *executionContext) unmarshalInputFoo(ctx context.Context, obj interface{}) (model.Foo, error) {
    var it model.Foo
    var asMap = obj.(map[string]interface{})

    if _, present := asMap["principalType"]; !present {
        asMap["principalType"] = "User"
    }
kelvne commented 3 years ago

I'm re-using the models internally and I wanted the possibility to add omitempty since I'm marshalling the objects into maps to send to other APIs.

However for my scenario it can be achieve with a simple solution.

Minor example:

func cleanPayload(properties *model.UserProperties) (map[string]interface{}, error) {
    marshaled, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }

    mapped := make(map[string]interface{})
    err = json.Unmarshal(marshaled, &mapped)
    if err != nil {
        return nil, err
    }

    for key, val := range mapped {
        if val == nil {
            delete(mapped, key)
        }
    }

    return mapped, nil
}

It's working for us, hope it helps.