Add more java.time adapters 55/118055/4
authorPamela Dragosh <pdragosh@research.att.com>
Thu, 18 Feb 2021 15:57:23 +0000 (09:57 -0600)
committerPamela Dragosh <pdragosh@research.att.com>
Thu, 18 Feb 2021 16:31:55 +0000 (10:31 -0600)
The time extensions require these adapters for anyone using our
gson adapter to serialize/deserialize the DecisionRequest.

Issue-ID: POLICY-2810
Change-Id: I64a7e4a5a256c77bd67dab867664e41b61cd0050
Signed-off-by: Pamela Dragosh <pdragosh@research.att.com>
gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java
gson/src/main/java/org/onap/policy/common/gson/LocalDateTypeAdapter.java [new file with mode: 0644]
gson/src/main/java/org/onap/policy/common/gson/OffsetDateTimeTypeAdapter.java [new file with mode: 0644]
gson/src/main/java/org/onap/policy/common/gson/OffsetTimeTypeAdapter.java [new file with mode: 0644]
gson/src/main/java/org/onap/policy/common/gson/ZoneOffsetTypeAdapter.java [new file with mode: 0644]
gson/src/test/java/org/onap/policy/common/gson/LocalDateTypeAdapterTest.java [new file with mode: 0644]
gson/src/test/java/org/onap/policy/common/gson/OffsetDateTimeAdapterTest.java [new file with mode: 0644]
gson/src/test/java/org/onap/policy/common/gson/OffsetTimeTypeAdapterTest.java [new file with mode: 0644]
gson/src/test/java/org/onap/policy/common/gson/ZoneOffsetTypeAdapterTest.java [new file with mode: 0644]

index d6e36b3..df3def3 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2019-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,7 +31,11 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.Produces;
@@ -91,7 +95,11 @@ public class GsonMessageBodyHandler implements MessageBodyReader<Object>, Messag
         return builder.disableHtmlEscaping().registerTypeAdapterFactory(new MapDoubleAdapterFactory())
                         .registerTypeAdapter(Instant.class, new InstantTypeAdapter())
                         .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter())
-                        .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeTypeAdapter());
+                        .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeTypeAdapter())
+                        .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeTypeAdapter())
+                        .registerTypeAdapter(OffsetTime.class, new OffsetTimeTypeAdapter())
+                        .registerTypeAdapter(LocalDate.class, new LocalDateTypeAdapter())
+                        .registerTypeAdapter(ZoneOffset.class, new ZoneOffsetTypeAdapter());
     }
 
     @Override
diff --git a/gson/src/main/java/org/onap/policy/common/gson/LocalDateTypeAdapter.java b/gson/src/main/java/org/onap/policy/common/gson/LocalDateTypeAdapter.java
new file mode 100644 (file)
index 0000000..8e33e46
--- /dev/null
@@ -0,0 +1,68 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.gson;
+
+import com.google.gson.JsonParseException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+public class LocalDateTypeAdapter extends TypeAdapter<LocalDate> {
+    private DateTimeFormatter formatter;
+
+    public LocalDateTypeAdapter() {
+        this(DateTimeFormatter.ISO_LOCAL_DATE);
+    }
+
+    public LocalDateTypeAdapter(DateTimeFormatter formatter) {
+        this.formatter = formatter;
+    }
+
+    @Override
+    public LocalDate read(JsonReader in) throws IOException {
+        try {
+            if (in.peek() == JsonToken.NULL) {
+                in.nextNull();
+                return null;
+            } else {
+                return LocalDate.parse(in.nextString(), formatter);
+            }
+
+        } catch (DateTimeParseException e) {
+            throw new JsonParseException("invalid date", e);
+        }
+    }
+
+    @Override
+    public void write(JsonWriter out, LocalDate value) throws IOException {
+        if (value == null) {
+            out.nullValue();
+        } else {
+            String text = value.format(formatter);
+            out.value(text);
+        }
+    }
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/OffsetDateTimeTypeAdapter.java b/gson/src/main/java/org/onap/policy/common/gson/OffsetDateTimeTypeAdapter.java
new file mode 100644 (file)
index 0000000..faf5ffd
--- /dev/null
@@ -0,0 +1,68 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.gson;
+
+import com.google.gson.JsonParseException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+public class OffsetDateTimeTypeAdapter extends TypeAdapter<OffsetDateTime> {
+    private DateTimeFormatter formatter;
+
+    public OffsetDateTimeTypeAdapter() {
+        this(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+    }
+
+    public OffsetDateTimeTypeAdapter(DateTimeFormatter formatter) {
+        this.formatter = formatter;
+    }
+
+    @Override
+    public OffsetDateTime read(JsonReader in) throws IOException {
+        try {
+            if (in.peek() == JsonToken.NULL) {
+                in.nextNull();
+                return null;
+            } else {
+                return OffsetDateTime.parse(in.nextString(), formatter);
+            }
+
+        } catch (DateTimeParseException e) {
+            throw new JsonParseException("invalid date", e);
+        }
+    }
+
+    @Override
+    public void write(JsonWriter out, OffsetDateTime value) throws IOException {
+        if (value == null) {
+            out.nullValue();
+        } else {
+            String text = value.format(formatter);
+            out.value(text);
+        }
+    }
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/OffsetTimeTypeAdapter.java b/gson/src/main/java/org/onap/policy/common/gson/OffsetTimeTypeAdapter.java
new file mode 100644 (file)
index 0000000..49a7d25
--- /dev/null
@@ -0,0 +1,68 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.gson;
+
+import com.google.gson.JsonParseException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.time.OffsetTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+public class OffsetTimeTypeAdapter extends TypeAdapter<OffsetTime> {
+    private DateTimeFormatter formatter;
+
+    public OffsetTimeTypeAdapter() {
+        this(DateTimeFormatter.ISO_OFFSET_TIME);
+    }
+
+    public OffsetTimeTypeAdapter(DateTimeFormatter formatter) {
+        this.formatter = formatter;
+    }
+
+    @Override
+    public OffsetTime read(JsonReader in) throws IOException {
+        try {
+            if (in.peek() == JsonToken.NULL) {
+                in.nextNull();
+                return null;
+            } else {
+                return OffsetTime.parse(in.nextString(), formatter);
+            }
+
+        } catch (DateTimeParseException e) {
+            throw new JsonParseException("invalid time", e);
+        }
+    }
+
+    @Override
+    public void write(JsonWriter out, OffsetTime value) throws IOException {
+        if (value == null) {
+            out.nullValue();
+        } else {
+            String text = value.format(formatter);
+            out.value(text);
+        }
+    }
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/ZoneOffsetTypeAdapter.java b/gson/src/main/java/org/onap/policy/common/gson/ZoneOffsetTypeAdapter.java
new file mode 100644 (file)
index 0000000..eb91ac4
--- /dev/null
@@ -0,0 +1,57 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.gson;
+
+import com.google.gson.JsonParseException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.time.DateTimeException;
+import java.time.ZoneOffset;
+
+public class ZoneOffsetTypeAdapter extends TypeAdapter<ZoneOffset>  {
+
+    @Override
+    public ZoneOffset read(JsonReader in) throws IOException {
+        try {
+            if (in.peek() == JsonToken.NULL) {
+                in.nextNull();
+                return null;
+            } else {
+                return ZoneOffset.of(in.nextString());
+            }
+
+        } catch (DateTimeException e) {
+            throw new JsonParseException("invalid zone", e);
+        }
+    }
+
+    @Override
+    public void write(JsonWriter out, ZoneOffset value) throws IOException {
+        if (value == null) {
+            out.nullValue();
+        } else {
+            out.value(value.toString());
+        }
+    }
+}
diff --git a/gson/src/test/java/org/onap/policy/common/gson/LocalDateTypeAdapterTest.java b/gson/src/test/java/org/onap/policy/common/gson/LocalDateTypeAdapterTest.java
new file mode 100644 (file)
index 0000000..17acf5e
--- /dev/null
@@ -0,0 +1,73 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.gson;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+import java.time.LocalDate;
+import lombok.ToString;
+import org.junit.Test;
+
+public class LocalDateTypeAdapterTest {
+    private static Gson gson =
+            new GsonBuilder().registerTypeAdapter(LocalDate.class, new LocalDateTypeAdapter()).create();
+    private static final String TEST_DATE = "2020-01-01";
+
+    @Test
+    public void test() {
+        InterestingFields data = new InterestingFields();
+        data.date = LocalDate.parse(TEST_DATE);
+
+        String json = gson.toJson(data);
+
+        // instant should be encoded as a number, without quotes
+        assertThat(json).doesNotContain("year").contains(TEST_DATE);
+
+        InterestingFields data2 = gson.fromJson(json, InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+
+        // try when the date-time string is invalid
+        String json2 = json.replace("2020", "invalid-date");
+        assertThatThrownBy(() -> gson.fromJson(json2, InterestingFields.class)).isInstanceOf(JsonParseException.class)
+                        .hasMessageContaining("invalid date");
+
+        // null output
+        data.date = null;
+        json = gson.toJson(data);
+        data2 = gson.fromJson(json, InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+
+        // null input
+        data2 = gson.fromJson("{\"date\":null}", InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+    }
+
+    @ToString
+    private static class InterestingFields {
+        private LocalDate date;
+    }
+
+}
diff --git a/gson/src/test/java/org/onap/policy/common/gson/OffsetDateTimeAdapterTest.java b/gson/src/test/java/org/onap/policy/common/gson/OffsetDateTimeAdapterTest.java
new file mode 100644 (file)
index 0000000..a0bcb1b
--- /dev/null
@@ -0,0 +1,72 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.gson;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+import java.time.OffsetDateTime;
+import lombok.ToString;
+import org.junit.Test;
+
+public class OffsetDateTimeAdapterTest {
+    private static Gson gson =
+            new GsonBuilder().registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeTypeAdapter()).create();
+    private static final String TEST_DATE = "2020-01-01T12:00:00.999+05:00";
+
+    @Test
+    public void test() {
+        InterestingFields data = new InterestingFields();
+        data.date = OffsetDateTime.parse(TEST_DATE);
+
+        String json = gson.toJson(data);
+
+        // instant should be encoded as a number, without quotes
+        assertThat(json).doesNotContain("year").contains(TEST_DATE);
+
+        InterestingFields data2 = gson.fromJson(json, InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+
+        // try when the date-time string is invalid
+        String json2 = json.replace("2020", "invalid-date");
+        assertThatThrownBy(() -> gson.fromJson(json2, InterestingFields.class)).isInstanceOf(JsonParseException.class)
+                        .hasMessageContaining("invalid date");
+
+        // null output
+        data.date = null;
+        json = gson.toJson(data);
+        data2 = gson.fromJson(json, InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+
+        // null input
+        data2 = gson.fromJson("{\"date\":null}", InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+    }
+
+    @ToString
+    private static class InterestingFields {
+        private OffsetDateTime date;
+    }
+}
diff --git a/gson/src/test/java/org/onap/policy/common/gson/OffsetTimeTypeAdapterTest.java b/gson/src/test/java/org/onap/policy/common/gson/OffsetTimeTypeAdapterTest.java
new file mode 100644 (file)
index 0000000..8098af9
--- /dev/null
@@ -0,0 +1,72 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.gson;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+import java.time.OffsetTime;
+import lombok.ToString;
+import org.junit.Test;
+
+public class OffsetTimeTypeAdapterTest {
+    private static Gson gson =
+            new GsonBuilder().registerTypeAdapter(OffsetTime.class, new OffsetTimeTypeAdapter()).create();
+    private static final String TEST_TIME = "12:00:00.999+05:00";
+
+    @Test
+    public void test() {
+        InterestingFields data = new InterestingFields();
+        data.time = OffsetTime.parse(TEST_TIME);
+
+        String json = gson.toJson(data);
+
+        // instant should be encoded as a number, without quotes
+        assertThat(json).doesNotContain("foo").contains(TEST_TIME);
+
+        InterestingFields data2 = gson.fromJson(json, InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+
+        // try when the date-time string is invalid
+        String json2 = json.replace("12", "invalid-time");
+        assertThatThrownBy(() -> gson.fromJson(json2, InterestingFields.class)).isInstanceOf(JsonParseException.class)
+                        .hasMessageContaining("invalid time");
+
+        // null output
+        data.time = null;
+        json = gson.toJson(data);
+        data2 = gson.fromJson(json, InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+
+        // null input
+        data2 = gson.fromJson("{\"time\":null}", InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+    }
+
+    @ToString
+    private static class InterestingFields {
+        private OffsetTime time;
+    }
+}
diff --git a/gson/src/test/java/org/onap/policy/common/gson/ZoneOffsetTypeAdapterTest.java b/gson/src/test/java/org/onap/policy/common/gson/ZoneOffsetTypeAdapterTest.java
new file mode 100644 (file)
index 0000000..d9a3316
--- /dev/null
@@ -0,0 +1,72 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.gson;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+import java.time.ZoneOffset;
+import lombok.ToString;
+import org.junit.Test;
+
+public class ZoneOffsetTypeAdapterTest {
+    private static Gson gson =
+            new GsonBuilder().registerTypeAdapter(ZoneOffset.class, new ZoneOffsetTypeAdapter()).create();
+    private static final String TEST_ZONE = "+05:00";
+
+    @Test
+    public void test() {
+        InterestingFields data = new InterestingFields();
+        data.zone = ZoneOffset.of(TEST_ZONE);
+
+        String json = gson.toJson(data);
+
+        // instant should be encoded as a number, without quotes
+        assertThat(json).doesNotContain("foo").contains(TEST_ZONE);
+
+        InterestingFields data2 = gson.fromJson(json, InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+
+        // try when the date-time string is invalid
+        String json2 = json.replace("05", "invalid-zone");
+        assertThatThrownBy(() -> gson.fromJson(json2, InterestingFields.class)).isInstanceOf(JsonParseException.class)
+                        .hasMessageContaining("invalid zone");
+
+        // null output
+        data.zone = null;
+        json = gson.toJson(data);
+        data2 = gson.fromJson(json, InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+
+        // null input
+        data2 = gson.fromJson("{\"zone\":null}", InterestingFields.class);
+        assertEquals(data.toString(), data2.toString());
+    }
+
+    @ToString
+    private static class InterestingFields {
+        private ZoneOffset zone;
+    }
+}