Closed5
GraphQL NullValue
値を入れない場合とnull を渡す場合を区別したい
gqlgenの場合GetFieldContextを使う
↑のコードだと子のフィールドが取れないので下記のような関数を作り、FieldMaskのような形で取得する// 再帰関数
func getFieldNamesRecursively(children ast.ChildValueList, names []string, prefix string) []string {
childrenFieldNames := []string{}
for _, child := range children {
if len(prefix) != 0 {
names = append(names, prefix+"."+child.Name)
} else {
names = append(names, child.Name)
}
if len(child.Value.Children) != 0 {
childrenFieldNames = append(childrenFieldNames, getFieldNamesRecursively(
child.Value.Children, childrenFieldNames, names[len(names)-1])...)
}
}
return append(names, childrenFieldNames...)
}
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
}
names = append(names, getFieldNamesRecursively(arg.Value.Children, names, "")...)
}
return names
}
試した結果
# スキーマ
input AddressInput {
postCode: String
prefecture: String
city: String
}
input UserProfileInput {
bio: String
address: AddressInput
}
input UpdateUserInput {
id: ID!
name: String
profile: UserProfileInput
}
type UpdateUserPayload {
touchedFields: [String!]!
}
type Mutation {
updateUser(input: UpdateUserInput!): UpdateUserPayload!
}
// resolver
func (r *mutationResolver) UpdateUser(ctx context.Context, input model.UpdateUserInput) (*model.UpdateUserPayload, error) {
touchedFields := r.GetArgumentFieldNames(ctx, "input")
return &model.UpdateUserPayload{TouchedFields: touchedFields}, nil
}
# クエリ
mutation {
updateUser(input: {
id: "1234"
profile: {
bio: null
address: {
city: null
}
}
})
{
touchedFields
}
}
// 結果
{
"data": {
"updateUser": {
"touchedFields": [
"id",
"profile",
"profile.bio",
"profile.address",
"profile.address.city"
]
}
}
}
// 指定していないname, profile.address.postCode, profile.address.prefectureは帰ってこない
Rust x juniperの場合、Nullable型を使うと区別できる
- Nullable型のexplicitメソッドを使い
Nullable<T>
をOption<Option<T>>
に変換する - 値を入れていない場合、
Option<Option<T>>
の中身がNone
になる - 値を入れた場合、
Option<Option<T>>
の中身がOption<T>
になり、nullを入れた場合None
、nullではない値を入れた場合T
になる
let charts = input.charts.explicit();
if let Some(Some(charts)) = charts {
match charts.sp_leggendaria.explicit() {
// 未指定の場合
None => println!("sp_leggendaria: no update"),
// 指定した場合 (null指定もこっち)
Some(sp_leggendaria) => println!("sp_leggendaria: update to {:?}", sp_leggendaria)
}
}
Apollo Serverの場合は単純にjavascriptなので
値を入れていない場合はundefined
、nullを入れた場合はnull
になる(わかりやすい)
# 投げたmutation
mutation($input: UpdateUserInput!) {
updateUser(input: $input) {
id
name
}
}
// resolver
export const mutation = {
updateUser: (
parent,
args,
context,
info
) => {
console.log(args.input)
return users[0];
}
}
nullを渡した場合
// variables
{
"input": {
"id": "test",
"name": null
}
}
// console.log 結果
{ id: 'test', name: null }
未指定の場合
// variables
{
"input": {
"id": "test"
}
}
// console.log 結果
{ id: 'test' }
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.
Generics追加によってgqlgen側が対応したら上記実装なしで実現できそう?
このスクラップは2023/01/15にクローズされました