はじめに
Java 開発において、JSON データの取り扱いは一般的な要件です。Jackson ライブラリの ObjectMapper クラスは、Java オブジェクトと JSON 間の変換を行う強力なツールです。しかし、JSON データを処理する際、Java クラスで定義されていないプロパティに遭遇することがあります。これは、逆シリアル化中に例外を引き起こす可能性があります。
この実験(Lab)では、JSON データ内の未知のプロパティを無視するように ObjectMapper を設定する方法を説明します。JSON を適切に処理する方法を学び、時間の経過とともに変化する可能性のある外部データソースを扱う際に、アプリケーションをより堅牢にします。
JSON と ObjectMapper の基本を理解する
未知のプロパティの処理方法に入る前に、まず JSON とは何か、そして Java で Jackson の ObjectMapper を使用する方法を理解しましょう。
JSON とは?
JSON (JavaScript Object Notation) は、人間が読み書きしやすく、機械が解析および生成しやすい軽量なデータ交換形式です。キーと値のペアで構成されており、Web アプリケーションとサーバー間でデータを送信するために一般的に使用されます。
JSON オブジェクトの簡単な例を次に示します。
{
"name": "John Doe",
"age": 30,
"email": "john.doe@example.com"
}
Jackson ObjectMapper の紹介
Jackson ライブラリは、Java で最も人気のある JSON 処理ライブラリの 1 つです。ObjectMapper クラスは Jackson の中心的なコンポーネントであり、Java オブジェクトと JSON 間の変換機能を提供します。
簡単な Java クラスを作成し、ObjectMapper を使用して JSON との間で変換してみましょう。
まず、次の手順に従って Person クラスを作成します。
- WebIDE を開き、エクスプローラービューに移動します。
~/project/src/main/java/com/labex/json/Person.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
public class Person {
private String name;
private int age;
// Default constructor needed for Jackson
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
次に、ObjectMapper の使用方法を示す Java クラスを作成しましょう。
~/project/src/main/java/com/labex/json/ObjectMapperDemo.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class ObjectMapperDemo {
public static void main(String[] args) {
try {
// Create a Person object
Person person = new Person("John Doe", 30);
// Create an ObjectMapper instance
ObjectMapper objectMapper = new ObjectMapper();
// Serialize Person object to JSON string
String jsonString = objectMapper.writeValueAsString(person);
System.out.println("Serialized to JSON: " + jsonString);
// Deserialize JSON string back to Person object
Person deserializedPerson = objectMapper.readValue(jsonString, Person.class);
System.out.println("Deserialized from JSON: " + deserializedPerson);
} catch (IOException e) {
e.printStackTrace();
}
}
}
次に、このクラスをコンパイルして実行しましょう。
- WebIDE でターミナルを開きます。
- まだそこにいない場合は、プロジェクトディレクトリに移動します。
cd ~/project - クラスをコンパイルして実行します。
mvn compile exec:java -Dexec.mainClass="com.labex.json.ObjectMapperDemo"
次のような出力が表示されるはずです。
Serialized to JSON: {"name":"John Doe","age":30}
Deserialized from JSON: Person{name='John Doe', age=30}
これは、ObjectMapper の基本的な機能を示しています。
- シリアライゼーション(Serialization):Java オブジェクトを JSON 文字列に変換する
- デシリアライゼーション(Deserialization):JSON 文字列を Java オブジェクトに変換する
次のステップでは、JSON に Java クラスで定義されていないプロパティが含まれている場合に何が起こるか、そしてそれらを処理する方法を探ります。
未知のプロパティの問題
現実世界では、JSON データは時間の経過とともに進化することがよくあります。API が新しいフィールドを追加したり、異なるシステムが追加情報を含めたりする場合があります。Java クラスと一致しないプロパティを持つ JSON を受信した場合、Jackson のデフォルトの動作は例外をスローすることです。
この問題を実際に見てみましょう。
~/project/src/main/java/com/labex/json/UnknownPropertiesDemo.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class UnknownPropertiesDemo {
public static void main(String[] args) {
try {
// Create a JSON string with an extra property not in our Person class
String jsonWithExtraProperty = "{\"name\":\"John Doe\",\"age\":30,\"email\":\"john.doe@example.com\"}";
// Create an ObjectMapper instance
ObjectMapper objectMapper = new ObjectMapper();
System.out.println("Attempting to deserialize JSON with an extra 'email' property...");
// Try to deserialize the JSON into a Person object
Person person = objectMapper.readValue(jsonWithExtraProperty, Person.class);
// This line won't be reached if an exception occurs
System.out.println("Successfully deserialized: " + person);
} catch (Exception e) {
System.out.println("Error occurred: " + e.getMessage());
}
}
}
- このクラスをコンパイルして実行します。
cd ~/project mvn compile exec:java -Dexec.mainClass="com.labex.json.UnknownPropertiesDemo"
次のようなエラー出力が表示されるはずです。
Attempting to deserialize JSON with an extra 'email' property...
Error occurred: Unrecognized field "email" (class com.labex.json.Person), not marked as ignorable (2 known properties: "name", "age"])
このエラーは、Person クラスに email プロパティがないのに、JSON 文字列にはあるために発生します。デフォルトでは、Jackson の ObjectMapper は、認識できないプロパティに遭遇すると例外をスローします。
多くの現実世界のシナリオでは、アプリケーションをより柔軟にし、そのような状況を適切に処理したいと考えます。ここで、ObjectMapper を設定して未知のプロパティを無視することが役立ちます。
ObjectMapper を設定して未知のプロパティを無視する
問題点を理解したので、例外をスローする代わりに、ObjectMapper を設定して未知のプロパティを無視する方法を学びましょう。
ObjectMapper を設定して未知のプロパティを無視するには、主に 2 つの方法があります。
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIESを使用してconfigure()メソッドを使用する- Java クラスでアノテーションを使用する
両方の方法を実装してみましょう。
方法 1:configure() メソッドを使用する
この方法は、デシリアライズされるすべてのクラスに対して、すべての未知のプロパティを無視するように ObjectMapper インスタンスを設定します。
~/project/src/main/java/com/labex/json/IgnoreUnknownDemo.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class IgnoreUnknownDemo {
public static void main(String[] args) {
try {
// Create a JSON string with an extra property not in our Person class
String jsonWithExtraProperty = "{\"name\":\"John Doe\",\"age\":30,\"email\":\"john.doe@example.com\"}";
// Create an ObjectMapper instance
ObjectMapper objectMapper = new ObjectMapper();
// Configure ObjectMapper to ignore unknown properties
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
System.out.println("Attempting to deserialize JSON with an extra 'email' property...");
// Deserialize the JSON into a Person object
Person person = objectMapper.readValue(jsonWithExtraProperty, Person.class);
// This should now work without throwing an exception
System.out.println("Successfully deserialized: " + person);
} catch (Exception e) {
System.out.println("Error occurred: " + e.getMessage());
}
}
}
- このクラスをコンパイルして実行します。
cd ~/project mvn compile exec:java -Dexec.mainClass="com.labex.json.IgnoreUnknownDemo"
次のような出力が表示されるはずです。
Attempting to deserialize JSON with an extra 'email' property...
Successfully deserialized: Person{name='John Doe', age=30}
ここで、デシリアライズが成功し、email プロパティが単に無視されていることに注意してください。JSON に余分なプロパティがあっても、アプリケーションは引き続き機能します。
方法 2:アノテーションを使用する
よりきめ細かい制御が必要な場合は、Jackson アノテーションを使用して、クラスレベルで動作を指定できます。
~/project/src/main/java/com/labex/json/PersonWithAnnotation.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class PersonWithAnnotation {
private String name;
private int age;
// Default constructor needed for Jackson
public PersonWithAnnotation() {
}
public PersonWithAnnotation(String name, int age) {
this.name = name;
this.age = age;
}
// Getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "PersonWithAnnotation{name='" + name + "', age=" + age + "}";
}
}
- 次に、この方法をテストするためのデモクラスを作成します。
~/project/src/main/java/com/labex/json/AnnotationDemo.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class AnnotationDemo {
public static void main(String[] args) {
try {
// Create a JSON string with an extra property
String jsonWithExtraProperty = "{\"name\":\"John Doe\",\"age\":30,\"email\":\"john.doe@example.com\"}";
// Create an ObjectMapper instance (no special configuration needed)
ObjectMapper objectMapper = new ObjectMapper();
System.out.println("Attempting to deserialize JSON with an extra 'email' property...");
// Deserialize the JSON into a PersonWithAnnotation object
PersonWithAnnotation person = objectMapper.readValue(jsonWithExtraProperty, PersonWithAnnotation.class);
// This should work without throwing an exception
System.out.println("Successfully deserialized: " + person);
} catch (Exception e) {
System.out.println("Error occurred: " + e.getMessage());
}
}
}
- このクラスをコンパイルして実行します。
cd ~/project mvn compile exec:java -Dexec.mainClass="com.labex.json.AnnotationDemo"
次のような出力が表示されるはずです。
Attempting to deserialize JSON with an extra 'email' property...
Successfully deserialized: PersonWithAnnotation{name='John Doe', age=30}
@JsonIgnoreProperties(ignoreUnknown = true) アノテーションは、JSON をこのクラスのインスタンスにデシリアライズするときに、Jackson に未知のプロパティを無視するように指示します。この方法は、ObjectMapper 全体を設定するよりも、よりターゲットを絞った方法です。
実用的なアプリケーションと現実世界のユースケース
ObjectMapper を設定して未知のプロパティを無視する方法を理解したので、この機能が役立つ実用的なアプリケーションと現実世界のシナリオをいくつか見てみましょう。
1. サードパーティ API の利用
外部 API と統合する場合、多くの場合、そのレスポンス形式の変更を制御することはできません。ObjectMapper を設定して未知のプロパティを無視することにより、API が新しいフィールドを追加した場合でも、アプリケーションは引き続き機能できます。
天気 API を利用する簡単な例を作成しましょう。
~/project/src/main/java/com/labex/json/WeatherData.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class WeatherData {
private String city;
private double temperature;
private int humidity;
// Default constructor needed for Jackson
public WeatherData() {
}
// Getters and setters
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public double getTemperature() {
return temperature;
}
public void setTemperature(double temperature) {
this.temperature = temperature;
}
public int getHumidity() {
return humidity;
}
public void setHumidity(int humidity) {
this.humidity = humidity;
}
@Override
public String toString() {
return "WeatherData{" +
"city='" + city + '\'' +
", temperature=" + temperature +
", humidity=" + humidity +
'}';
}
}
~/project/src/main/java/com/labex/json/WeatherApiConsumer.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
import com.fasterxml.jackson.databind.ObjectMapper;
public class WeatherApiConsumer {
public static void main(String[] args) {
try {
// Create an ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
// Simulate an initial API response with just the basics
String initialApiResponse = "{\"city\":\"New York\",\"temperature\":72.5,\"humidity\":65}";
// Parse the response
WeatherData weatherData = objectMapper.readValue(initialApiResponse, WeatherData.class);
System.out.println("Initial weather data: " + weatherData);
// Now simulate the API adding new fields in a future version
String updatedApiResponse =
"{\"city\":\"New York\",\"temperature\":72.5,\"humidity\":65," +
"\"wind_speed\":10.2,\"pressure\":1013.25,\"forecast\":\"sunny\"}";
// Parse the updated response with the same object model
WeatherData updatedWeatherData = objectMapper.readValue(updatedApiResponse, WeatherData.class);
System.out.println("Updated weather data (with ignored fields): " + updatedWeatherData);
// Notice how our application continues to work without changes to our model
} catch (Exception e) {
System.out.println("Error occurred: " + e.getMessage());
}
}
}
- このクラスをコンパイルして実行します。
cd ~/project mvn compile exec:java -Dexec.mainClass="com.labex.json.WeatherApiConsumer"
次のような出力が表示されるはずです。
Initial weather data: WeatherData{city='New York', temperature=72.5, humidity=65}
Updated weather data (with ignored fields): WeatherData{city='New York', temperature=72.5, humidity=65}
WeatherData クラスで定義されていない追加のフィールドが含まれていても、アプリケーションが更新された API レスポンスで引き続き機能することに注意してください。
2. 異なる API バージョンの処理
もう 1 つの一般的なシナリオは、同じクライアントコードで複数のバージョンの API をサポートする必要がある場合です。
~/project/src/main/java/com/labex/json/ApiVersionDemo.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ApiVersionDemo {
public static void main(String[] args) {
try {
// Create an ObjectMapper that ignores unknown properties
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// API v1 response
String apiV1Response = "{\"name\":\"John Doe\",\"age\":30}";
// API v2 response with additional fields
String apiV2Response =
"{\"name\":\"John Doe\",\"age\":30,\"email\":\"john.doe@example.com\"," +
"\"address\":\"123 Main St\",\"phone\":\"555-1234\"}";
// We can use the same Person class for both versions
System.out.println("Parsing API v1 response:");
Person personV1 = objectMapper.readValue(apiV1Response, Person.class);
System.out.println(personV1);
System.out.println("\nParsing API v2 response with the same class:");
Person personV2 = objectMapper.readValue(apiV2Response, Person.class);
System.out.println(personV2);
// Both work fine with our simple Person class
} catch (Exception e) {
System.out.println("Error occurred: " + e.getMessage());
}
}
}
- このクラスをコンパイルして実行します。
cd ~/project mvn compile exec:java -Dexec.mainClass="com.labex.json.ApiVersionDemo"
次のような出力が表示されるはずです。
Parsing API v1 response:
Person{name='John Doe', age=30}
Parsing API v2 response with the same class:
Person{name='John Doe', age=30}
この例は、ObjectMapper を設定して未知のプロパティを無視することで、同じ Java クラスで異なるバージョンの API を処理できることを示しています。これにより、コードの保守性が向上し、変更への適応性が高まります。
ベストプラクティスと追加の設定オプション
ObjectMapper を設定して未知のプロパティを無視する方法を理解したので、現実世界のアプリケーションで役立つ可能性のあるベストプラクティスと追加の設定オプションについて説明しましょう。
ベストプラクティス
1. アプリケーションに適したアプローチを選択する
- グローバル設定: すべてのデシリアライズ操作で未知のプロパティを無視する場合は、
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)を使用します。 - クラスレベルのアノテーション: 特定のクラスのみが未知のプロパティを無視する場合は、
@JsonIgnoreProperties(ignoreUnknown = true)を使用します。
2. エラー処理とロギングを検討する
未知のプロパティを無視する場合でも、特に開発またはテスト環境では、未知のプロパティが検出されたときにログを記録することが役立つ場合があります。
未知のプロパティのカスタム処理の例を作成しましょう。
~/project/src/main/java/com/labex/json/LoggingUnknownPropertiesDemo.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import java.io.IOException;
public class LoggingUnknownPropertiesDemo {
public static void main(String[] args) {
try {
// Create a JSON string with an extra property
String jsonWithExtraProperty =
"{\"name\":\"John Doe\",\"age\":30,\"email\":\"john.doe@example.com\"}";
// Create an ObjectMapper instance
ObjectMapper objectMapper = new ObjectMapper();
// Configure ObjectMapper to ignore unknown properties
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Add a problem handler to log unknown properties
objectMapper.addHandler(new DeserializationProblemHandler() {
@Override
public boolean handleUnknownProperty(DeserializationContext ctxt,
JsonParser p,
com.fasterxml.jackson.databind.JsonDeserializer<?> deserializer,
Object beanOrClass,
String propertyName) throws IOException {
System.out.println("WARNING: Unknown property '" + propertyName +
"' found during deserialization of " +
beanOrClass.getClass().getSimpleName());
// Skip the value and continue
p.skipChildren();
return true;
}
});
// Deserialize the JSON into a Person object
Person person = objectMapper.readValue(jsonWithExtraProperty, Person.class);
System.out.println("Successfully deserialized: " + person);
} catch (Exception e) {
System.out.println("Error occurred: " + e.getMessage());
}
}
}
- このクラスをコンパイルして実行します。
cd ~/project mvn compile exec:java -Dexec.mainClass="com.labex.json.LoggingUnknownPropertiesDemo"
次のような出力が表示されるはずです。
WARNING: Unknown property 'email' found during deserialization of Person
Successfully deserialized: Person{name='John Doe', age=30}
このアプローチにより、未知のプロパティを無視しながら、その存在を認識することができ、デバッグや監視に役立ちます。
追加の設定オプション
Jackson の ObjectMapper は、他の多くの便利な設定オプションを提供します。以下に、未知のプロパティを無視することとよく組み合わせて使用されるものをいくつか示します。
~/project/src/main/java/com/labex/json/AdditionalConfigurationsDemo.javaに新しいファイルを作成します。- ファイルに次のコードを追加します。
package com.labex.json;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class AdditionalConfigurationsDemo {
public static void main(String[] args) {
try {
// Create a Person object
Person person = new Person("John Doe", 30);
// Create an ObjectMapper with various configurations
ObjectMapper objectMapper = new ObjectMapper();
// Ignore unknown properties during deserialization
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Allow JSON with single quotes
objectMapper.configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// Allow JSON with unquoted field names
objectMapper.configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// Format output JSON with indentation for better readability
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// Serialize the Person object to JSON with pretty printing
String jsonString = objectMapper.writeValueAsString(person);
System.out.println("Formatted JSON:");
System.out.println(jsonString);
// Parse JSON with single quotes and unquoted field names
String nonStandardJson = "{'name':'Jane Smith', age:25}";
Person parsedPerson = objectMapper.readValue(nonStandardJson, Person.class);
System.out.println("\nParsed from non-standard JSON: " + parsedPerson);
} catch (Exception e) {
System.out.println("Error occurred: " + e.getMessage());
}
}
}
- このクラスをコンパイルして実行します。
cd ~/project mvn compile exec:java -Dexec.mainClass="com.labex.json.AdditionalConfigurationsDemo"
次のような出力が表示されるはずです。
Formatted JSON:
{
"name" : "John Doe",
"age" : 30
}
Parsed from non-standard JSON: Person{name='Jane Smith', age=25}
この例では、さまざまな設定オプションを示しています。
SerializationFeature.INDENT_OUTPUT: 生成された JSON をインデントで読みやすくしますALLOW_SINGLE_QUOTES: 二重引用符の代わりに一重引用符を使用した JSON を許可しますALLOW_UNQUOTED_FIELD_NAMES: 引用符のないフィールド名を許可します
これらの追加設定は、非標準の JSON を使用する場合や、JSON 出力を人間が読みやすくする必要がある場合など、さまざまなシナリオで役立ちます。
まとめ
この実験では、外部ソースからの JSON を処理する堅牢な Java アプリケーションを構築するための重要なスキルである、Jackson の ObjectMapper を設定して JSON データ内の未知のプロパティを無視する方法を学習しました。
以下を習得しました。
- Jackson の
ObjectMapperを使用した基本的な JSON シリアライズとデシリアライズ - JSON 内の未知のプロパティの問題と、それらが例外を引き起こす可能性がある理由
- 未知のプロパティを処理するための 2 つのアプローチ:
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)を使用したグローバル設定@JsonIgnoreProperties(ignoreUnknown = true)を使用したクラスレベルのアノテーション
- 実用的なアプリケーションと現実世界のユースケース:
- 時間の経過とともに変化する可能性のあるサードパーティ API の利用
- 同じコードで複数のバージョンの API をサポートする
- ベストプラクティスと追加の設定オプション:
- デシリアライズ中の未知のプロパティのロギング
- さまざまな JSON 形式と出力スタイルに対応する
ObjectMapperの設定
これらのスキルは、データ形式が変更されたり、予期しないプロパティが含まれていたりする場合でも、さまざまなソースからの JSON データを適切に処理できる、より回復力のある Java アプリケーションを構築するのに役立ちます。
さらに学習するには、カスタムシリアライザとデシリアライザ、日付形式の処理、ポリモーフィック型処理など、より複雑な JSON 処理シナリオに役立つ他の Jackson アノテーションと機能を調べてください。



