CSS調整

2018年9月27日

Spring Data RestのJson SchemaのEnumをJsonIgnoreする

SDRにはJsonSchemaを出力する機構があります。
要するにEntityを元にJsonSchemaを生成するConverterがあるってことですね。

普通にJsonSchemaを出力してみると


class Text {
  @Column
  var title: String = ""
  @Column
  var page: Int = 0
  @Column
  @Enumerated(EnumType.STRING)
  var category: CategoryType = CategoryType.Memo
  
  enum class CategoryType {
    Memo,
    Article,
    Log,
    Code
  }
}

上のEntityがあったとして、

RequestHeader ->
  Accept: application/schema+json

HttpRequest.GET ->
  /profile/texts

となげると

{
  title: "Text",
  type: "object",
  properties: {
    title: {
      title: "Title",
      type: "string"
    },
    page: {
      title: "Page",
      type: "integer"
    },
    category: {
      title: "Category",
      type: "string",
      enum: [
        "Memo",
        "Article",
        "Log",
        "Code"
      ]
    }
  }
}

といったものが返ってきます。(一部不要な部分は削ってます)


このEntityの一部にJsonIgnoreを指定してみます


class Text {
  @Column
  var title: String = ""
  
  @JsonIgnore
  @Column
  var page: Int = 0
  
  @Column
  @Enumerated(EnumType.STRING)
  var category: CategoryType = CategoryType.Memo
  
  enum class CategoryType {
    Memo,
    Article,
  
    @JsonIgnore Log,
    @JsonIgnore Code
  }
}

同じリクエストをなげた場合、返ってくるのは、

{
  title: "Text",
  type: "object",
  properties: {
    title: {
      title: "Title",
      type: "string"
    },
    category: {
      title: "Category",
      type: "string",
      enum: [
        "Memo",
        "Article",
        "Log",
        "Code"
      ]
    }
  }
}

となります。
pageプロパティは期待どおり出力されなくなりましたが、categoryプロパティのenum値は全て出力されてしまっています。


これをどうにかしたいのが、本題。


enum値のJsonSchemaはJsonSchema.EnumPropertyというクラスで実装されています。


標準のconverter相当の作り方をした場合


fun buildSchema(clazz: Class<Any>): JsonSchema.JsonSchemaProperty? {
  return when {
    clazz.isEnum -> buildEnumSchema(clazz)
    else -> null
  }
}
fun buildEnumSchema(clazz: Class<Any>): JsonSchema.JsonSchemaProperty {
  return JsonSchema.EnumProperty("name", "title", clazz, "description", false)
}

少し雑ですが、要するに、Classを渡して生成しています。

Classを渡した場合のコンストラクタは、

* 全Enumメンバーを取得
* toStringしてList化
* 別のコンストラクタへ渡す

という処理になっているので、JsonIgnore分を削ったListを用意して、その「別のコンストラクタ」をダイレクトに呼んであげれば良さそうです。


JsonIgnoreなメンバを省いたEnumPropertyを作る場合


上の処理を書き換えます。

fun buildEnumSchema(clazz: Class<Any>): JsonSchema.JsonSchemaProperty {
  clazz as Class<Enum<*>>
  return JsonSchema.EnumProperty("name", "title",
    clazz.enumConstants.filter { 
      clazz.fields[it.ordinal]
           .getAnnotationsByType(JsonIgnore::class.java)
           .isEmpty() 
    }.map { it.toString() }.toList(),
    "description", false)
}

これをConverterに組み込んで、JsonSchema取得のリクエストをなげてみると、

{
  title: "Text",
  type: "object",
  properties: {
    title: {
      title: "Title",
      type: "string"
    },
    category: {
      title: "Category",
      type: "string",
      enum: [
        "Memo",
        "Article"
      ]
    }
  }
}

categoryのenumの値もフィルタリングされました。


やっていることは単純で、enumメンバのうち、JsonIgnoreが貼られているものを除外してtoStringしたものを渡してEnumPropertyを作っているだけです。

ポイントは、ClassがEnumだと後続処理に教える為に未代入でキャストしているのと、enumConstantsを再度、自身のClassからFieldとして取得してAnnotationを見れるようにするところでしょうか。


最新のSDRならもしかしたら、わざわざやらなくてもいけるのかな?とか思いますが、まあ、メモ程度ってことで。

いじょ