graphql
graphql copied to clipboard
Eliminating null fields in JSON output
The resolver pipeline seems to like explicit null fields. For example, if you do something like query { things { id, name } }
, you might end up with something like:
{
"results": {
"things": [
{"id": "1", "name": null},
{"id": "2", "name": null},
{"id": "3", "name": null},
{"id": "4", "name": null},
{"id": "5", "name": "a"}
]
}
}
but a frontend would generally be perfectly happy with:
{
"results": {
"things": [
{"id": "1"},
{"id": "2"},
{"id": "3"},
{"id": "4"},
{"id": "5", "name": "a"}
]
}
}
Considering the things
result only, that's a space saving of 20%. For large result sets, the aggregate size of all null values can be considerable. A real-world result set of ours is 273KB with nulls, but 189KB without nulls, which is a space saving factor of 41%.
I could do a pass over our results to eliminate nulls (possibly expensive, reflection-heavy and error-prone) or use a JSON encoder that eliminates nulls or allows filtering (not sure if that exists). But it would be a lot simpler if the resolver code could have a mode where object keys that are null wouldn't be emitted in the first place. Thoughts?
I think you can use struct_tags
like this
type Thing struct {
id int
name string `json:",omitempty"'
}
No, that would not let you distinguish between a null string and an empty string, which is not the same thing. Or deal with null structs.
You use Pointer! *time.Time or *[]wtfarray .... This makes a accurate null-object.
No idea what you meant by that, sorry. I want to avoid nulls ending up in the serialization.
Well, exactly! Me too!
If you use omitempty for the string definition, send it and a key is empty, it will delete that key / value in your request. This pair is not available
Yes, good that far!
Of course, the other party has to deal with it. If you work with structures, then that should not be a problem.
But if you now have objects, arrays in your structures, then the receiver gets objects with value null
. You can not declare an object within a structure with omitempty, you can do it, but that does not work. Unless the object within a structure is a pointer (*, &).
For example, the object time.Time
:
type Thing struct {
id int
name string `json:",omitempty"'
timer time.Time
}
For example, if you create this for the JSON string, the value of key timer
will look like this:
0001-01-01T00:00:00Z
Other values will produce a null
If you change that easily, to:
type Thing struct {
id int
name string `json:",omitempty"'
timer *time.Time
}
Then no time.Time object is transmitted. That's magic!
it may be that you still have to pack 'omitempty' behind it.
Thanks, but two problems. First, an empty string is semantically not the same as null
. Secondly, graphql-go
does not use JSON tags for object definitions. Given:
type Thing struct {
Name *string `json:"name,omitempty"
}
thingType := graphql.NewObject(graphql.ObjectConfig{
Name: "Thing",
Fields: graphql.Fields{
"name": &graphql.Field{Type: graphql.String},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return Thing{Name: nil}, nil
},
})
...
Then:
$ curl http://localhost:300/graphql?query={thing{name}}
{"thing":{"name":null}}
Well, you are calling a function that should give you all the names?
Then there is always a name to or just Result 0!
If, for example, you are looking for a name, then GraphQL also makes a non-nullable field
. So if you want that!
Model:
type Thing struct {
Name string `json:"name,omitempty" form:"name"`
}
Function:
func GetName(n string) (*models.Thing, error) {
var t = &models.Thing{Name: n}
return t, nil
}
GQLFunction:
"test": &graphql.Field{
Type: UserType,
Args: graphql.FieldConfigArgument{
"name": &graphql.ArgumentConfig{
Description: "Name String",
Type: graphql.NewNonNull(graphql.String), //
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
name := p.Args["name"].(string)
bname, err := controllers.GetName(name)
if err != nil {
log.Println(err)
return nil, err
}
return bname, err
},
},
CURL
http://localhost:5000/ql?query={test(name:"Keenefrombravia"){name}}
MyRESULT:
{"data":{"test":null},"errors":[{"message":"Cannot return null for non-nullable field Thing.name.","locations":[{"line":1,"column":31}],"path":["test","name"]}]}
No, the field can be null
. But there's no point in returning null
data in JSON, because a missing field is by definition equivalent to null.
ok, I understand!
I have just searched in an existing function for a user that does not exist!
{"data":{"user":null}}
now I am motivated!
"but a frontend would generally be perfectly happy with:"
Frontender here, its not Graphql spec. See for example https://github.com/graphql/graphql-js/issues/731#issuecomment-284978418 and the size you are talking about is not so much when its gzipped. So in absolute numbers yes, in real numbers no. Also in frontend JS typeof null equals object, and we are very happy with null :)