Closed5

GraphQL NullValue

プレミ社員プレミ社員

値を入れない場合とnull を渡す場合を区別したい

gqlgenの場合GetFieldContextを使う
https://github.com/99designs/gqlgen/issues/1416
↑のコードだと子のフィールドが取れないので下記のような関数を作り、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型を使うと区別できる

https://graphql-rust.github.io/juniper/master/advanced/implicit_and_explicit_null.html

  1. Nullable型のexplicitメソッドを使いNullable<T>Option<Option<T>>に変換する
  2. 値を入れていない場合、Option<Option<T>>の中身がNoneになる
  3. 値を入れた場合、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' }
このスクラップは2023/01/15にクローズされました