先頭1文字だけのキャメルケースがうまくいかない?
メモ書き程度に、SpringのdependencyライブラリにあるJacksonで、スネークケースJSONの場合にうまくいかなかったので検証してみます。
ひとまず事象
package com.example.controller;
import com.example.resource.TestJacksonResource;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
public class SampleController {
@GetMapping("test")
public TestJacksonResource jackson() throws IOException {
String snakeJson = "{\"i_hoge_name\":\"hoge\",\"low_fuga_name\":\"fuga\"}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
TestJacksonResource resource = objectMapper.readValue(snakeJson, TestJacksonResource.class);
return resource;
}
}
package com.example.resource;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class TestJacksonResource implements Serializable {
private String iHogeName;
private String lowFugaName;
}
Unrecognized field "i_hoge_name" (class com.example.resource.TestJacksonResource), not marked as ignorable (2 known properties: "ihoge_name", "low_fuga_name"]) at [Source: (String)"{"i_hoge_name":"hoge","low_fuga_name":"fuga"}"; line: 1, column: 17] (through reference chain: com.example.resource.TestJacksonResource["i_hoge_name"])
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "i_hoge_name" (class com.example.resource.TestJacksonResource), not marked as ignorable (2 known properties: "ihoge_name", "low_fuga_name"])
at [Source: (String)"{"i_hoge_name":"hoge","low_fuga_name":"fuga"}"; line: 1, column: 17] (through reference chain: com.example.resource.TestJacksonResource["i_hoge_name"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:823)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1153)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1589)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1567)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:294)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
at com.example.controller.SampleController.jackson(SampleController.java:22)
Jacksonにより認識されているプロパティ名がどうやらi_hoge_name
ではなくihoge_name
のためマッピングできないらしい。
JavaBeanのフィールド名に@JsonProperty
をつければ解決することはわかっているのですが、なるべくJavaBeanに手を入れたくないです。
JavaBeanのフィールドをpublicにする
通ったけどpublic
なフィールドなんていやじゃ!
lombok使わないでprivateフィールドでアクセッサ用意してみる
public String getiHogeName() {
return iHogeName;
}
public String getLowFugaName() {
return lowFugaName;
}
public void setiHogeName(String iHogeName) {
this.iHogeName = iHogeName;
}
public void setLowFugaName(String lowFugaName) {
this.lowFugaName = lowFugaName;
}
{"iHogeName":"hoge","lowFugaName":"fuga"}
おぉ通った!!
lombokの自動生成クラスをのぞいてみる
public String getIHogeName() {
return this.iHogeName;
}
なるほど、これで原因はわかりましたね。
実装まではのぞいてませんが、Jacksonのプロパティ名を決定するロジックがアクセッサメソッドから生成するみたいですね。
iHogeName
のアクセッサ命名ってgetIHogeName
(lombok)とgetiHogeName
(intelliJのcommand+N
)はどっちが正しいのでしょう。
感覚的には先頭大文字のgetIHogeName
かなぁ。。。
そもそもそんなフィールド名つけんじゃねぇー!が正解かも。
参考
こんなstackoverflowもありました。
Springbootの@JsonPropertyで指定したキーでJSONを返したい
挙動から察するに、Jacksonが「フィールド名」と、(lombokにより自動生成されている)「getter」の双方からJSONシリアライズ対象を決定しているように見受けられます(こちらによるとおそらくこの推測は正しいかと)。Spring上での設定は私には分からないのですが(前述リンク先の通り、JacksonへはObjectMapper#setVisibilityメソッドで設定可能)、フィールドアクセスのみでJSONシリアライズを行うようにすれば解消するのではないでしょうか。(Jackson2ObjectMapperBuilder bean を設定?)
原因はわかったけどlombok使いたいしフィールド名も変えたくない
そもそもの使い方が間違ってるから諦めますか、、、
時間があったら調べてみたいですが。
ただデシリアライズ(読み込み)時は@JsonProperty
でいいんですけど、シリアライズ時も影響を受けるので、いい方法無いかなぁ
- そもそもResourceクラスを共有するなという問題
- ControllerでのシリアライズはMessageConverterの問題