更新後にデシリアライゼーションが失敗します。
マイクロサービスをSpring 1.5.10.RELEASE
からSpring 2.0.3.RELEASE
に更新し、lombok
を1.16.14
から1.18.0
およびjackson-datatype-jsr310
から2.9.4
に更新しました] _から2.9.6
へ。
JSON文字列-
{"heading":"Validation failed","detail":"field must not be null"}
クラス -
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDetail {
private final String heading;
private final String detail;
private String type;
}
メソッド呼び出し-
ErrorDetail errorDetail = asObject(jsonString, ErrorDetail.class);
デシリアライズに使用されるメソッド-
import com.fasterxml.jackson.databind.ObjectMapper;
// more imports and class defination.
private static <T> T asObject(final String str, Class<T> clazz) {
try {
return new ObjectMapper().readValue(str, clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
エラー-
Java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.foo.bar.ErrorDetail` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"heading":"Validation failed","detail":"field must not be null"}"; line: 1, column: 2]
Lombokは、バージョン1.16.20のコンストラクターで@ConstructorProperties
の生成を停止しました(モジュールを使用するアプリケーションがJava 9+)を壊す可能性があるため、 changelog を参照)。コンストラクターのパラメーターの名前が含まれています(クラスのコンパイル時に削除されるため、実行時にパラメーター名を引き続き取得できるようにするための回避策です。デフォルトではアノテーションが生成されないため、ジャクソンはフィールド名をマッピングできませんコンストラクターのパラメーター。
解決策1:@NoArgsConstructor
および@Setter
を使用しますが、不変性が失われます(それが重要な場合)。
更新:@NoArgsConstructor
と@Getter
(@Setter
なし)だけでも動作する場合があります( INFER_PROPERTY_MUTATORS=true
=)。このようにして、少なくとも通常の(反射しない)コードからクラスを不変に保つことができます。
解決策2:注釈を再度生成するようにlombokを設定します lombok.config
ファイルを使用 行lombok.anyConstructor.addConstructorProperties = true
を含みます。 (モジュールを使用している場合は、Java.desktop
がモジュールパスにあることを確認してください。)
解決策3:この質問に対して here または @ Randakar answer で説明されているように、Lombokの@Builder
と組み合わせてJacksonのビルダーサポートを使用します。
Jacksonとlombokを適切に連携させる最善の方法は、DTOを常に不変にし、jacksonにBuilderを使用してオブジェクトにデシリアライズするように指示することです。
不変オブジェクトは、フィールドをin situで変更できない場合、コンパイラーがより積極的な最適化を行うことができるという単純な理由から、良いアイデアです。
これを行うには、JsonDeserializeとJsonPojoBuilderの2つのアノテーションが必要です。
例:
@Builder
@Value // instead of @Data
@RequiredArgsConstructor
@NonNull // Best practice, see below.
@JsonDeserialize(builder = ErrorDetail.ErrorDetailBuilder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDetail {
private final String heading;
// Set defaults if fields can be missing, like this:
@Builder.Default
private final String detail = "default detail";
// Example of how to do optional fields, you will need to configure
// your object mapper to support that and include the JDK 8 module in your dependencies..
@Builder.Default
private Optional<String> type = Optional.empty()
@JsonPOJOBuilder(withPrefix = "")
public static final class ErrorDetailBuilder {
}
}
Finalフィールドを持つクラスを逆シリアル化したい場合。そのため、逆シリアル化する最終フィールドを含むコンストラクタを宣言する必要があります。
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDetail {
private final String heading;
private final String detail;
private String type;
@JsonCreator
public ErrorDetail(@JsonProperty("heading") String heading, @JsonProperty("detail") String detail) {
this.heading = heading;
this.detail = detail;
}
}
また、マッパーでデシリアライズするときにMapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORSこのプロパティを設定する必要がありますfalse。
private static <T> T asObject(final String str, Class<T> clazz) {
try {
return new ObjectMapper().configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS,false).readValue(str, clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
私の意見では、@Data
アノテーションの使用は悪いアプローチです。 @Data
を@Getting
、@Setter
、@EqualsAndHashcode
などに変更してください。
助けになるならここに書いてください。
更新
@Data
create @RequiredArgsConstructor
をお勧めします。これは、最終フィールドを持ち、private String type
を持たないコンストラクターです。