diff --git a/.github/workflows/dep_build_v2.yml b/.github/workflows/dep_build_v2.yml
index 626cb550..f80d3222 100644
--- a/.github/workflows/dep_build_v2.yml
+++ b/.github/workflows/dep_build_v2.yml
@@ -10,7 +10,7 @@ permissions:
jobs:
build:
- runs-on: 'ubuntu-22.04'
+ runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
@@ -19,6 +19,8 @@ jobs:
JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+ with:
+ ref: 2.x
- name: Set up JDK
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
with:
diff --git a/.github/workflows/dep_build_v3.yml b/.github/workflows/dep_build_v3.yml
index cf461a51..8f56e555 100644
--- a/.github/workflows/dep_build_v3.yml
+++ b/.github/workflows/dep_build_v3.yml
@@ -14,13 +14,13 @@ jobs:
strategy:
fail-fast: false
matrix:
- java_version: ['8', '17', '21']
+ java_version: ['17', '21']
env:
JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
- ref: master
+ ref: 3.x
- name: Set up JDK
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
with:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 23e1c1ef..778ed7bf 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,6 +1,7 @@
name: Build and Deploy Snapshot
on:
push:
+ branches: ['2.*']
paths-ignore:
- "README.md"
- "release-notes/*"
@@ -14,17 +15,20 @@ permissions:
jobs:
build:
- runs-on: 'ubuntu-latest'
+ runs-on: 'ubuntu-24.04'
strategy:
fail-fast: false
matrix:
java_version: ['8', '11', '17', '21']
+ include:
+ - java_version: '8'
+ release_build: 'R'
env:
JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK
- uses: actions/setup-java@v4
+ uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with:
distribution: 'temurin'
java-version: ${{ matrix.java_version }}
@@ -41,19 +45,19 @@ jobs:
id: projectVersion
run: echo "version=$(./mvnw org.apache.maven.plugins:maven-help-plugin:3.5.1:evaluate -DforceStdout -Dexpression=project.version -q)" >> $GITHUB_OUTPUT
- name: Deploy snapshot
- if: ${{ github.event_name != 'pull_request' && matrix.java_version == '8' && endsWith(steps.projectVersion.outputs.version, '-SNAPSHOT') }}
+ if: ${{ matrix.release_build && github.event_name != 'pull_request' && endsWith(steps.projectVersion.outputs.version, '-SNAPSHOT') }}
env:
CI_DEPLOY_USERNAME: ${{ secrets.CENTRAL_DEPLOY_USERNAME }}
CI_DEPLOY_PASSWORD: ${{ secrets.CENTRAL_DEPLOY_PASSWORD }}
# MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: ./mvnw -B -q -ff -DskipTests -ntp source:jar deploy
- name: Generate code coverage
- if: ${{ github.event_name != 'pull_request' && matrix.java_version == '8' }}
+ if: ${{ matrix.release_build && github.event_name != 'pull_request' }}
run: ./mvnw -B -q -ff -ntp test
- name: Publish code coverage
- if: ${{ github.event_name != 'pull_request' && matrix.java_version == '8' }}
- uses: codecov/codecov-action@v4
+ if: ${{ matrix.release_build && github.event_name != 'pull_request' }}
+ uses: codecov/codecov-action@015f24e6818733317a2da2edd6290ab26238649a # v5.0.7
with:
token: ${{ secrets.CODECOV_TOKEN }}
- file: ./target/site/jacoco/jacoco.xml
+ files: ./target/site/jacoco/jacoco.xml
flags: unittests
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 642d572c..b9b1153a 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,2 +1,18 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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
+#
+# https://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.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
diff --git a/README.md b/README.md
index dee05669..1bbeef26 100644
--- a/README.md
+++ b/README.md
@@ -20,15 +20,11 @@ When used with Jackson 2.x, Java 8 support is provided via 3 separate modules:
all of which are built from this repository, and accessed and used as separate Jackson modules
(with separate Maven artifacts).
-### Jackson 3.0
+### Jackson 3.x
-Jackson 3.0 changes things as it requires Java 8 to work and can thereby directly supported features.
+Jackson 3.0 changes things as it requires Java 17 to work and can thereby directly supported features.
-Because of this `parameter-names` and `datatypes` modules are merged into `jackson-databind`
-and need not be registered; `datetime` module (`JavaTimeModule`) remains separate module due to its size
-and configurability options.
-
-So you will only need to separately add "Java 8 Date/time" module (see above for description)
+Because of this, all 3 modules are merged into `jackson-databind` and need not be registered (as of Jackson `3.0.0`)
## License
@@ -36,7 +32,7 @@ All modules are licensed under [Apache License 2.0](http://www.apache.org/licens
## Status
-[](https://travis-ci.org/FasterXML/jackson-modules-java8)
+[](https://github.com/FasterXML/jackson-modules-java8/actions/workflows/main.yml)
[](https://tidelift.com/subscription/pkg/maven-com-fasterxml-jackson-datatype-jackson-datatype-jsr310?utm_source=maven-com-fasterxml-jackson-datatype-jackson-datatype-jsr310&utm_medium=referral&utm_campaign=readme)
## Usage
@@ -46,19 +42,19 @@ All modules are licensed under [Apache License 2.0](http://www.apache.org/licens
To include modules, you use some or all of:
```xml
-
+
com.fasterxml.jackson.modulejackson-module-parameter-names
-
+
com.fasterxml.jackson.datatypejackson-datatype-jsr310
-
+
com.fasterxml.jackson.datatypejackson-datatype-jdk8
@@ -132,4 +128,4 @@ Following developers have committer access to this project.
## More
-See [Wiki](../../wiki) for more information (javadocs).
+See [Wiki](../../wiki) for more information.
diff --git a/datatypes/pom.xml b/datatypes/pom.xml
index b8ddd70d..c1c67e2c 100644
--- a/datatypes/pom.xml
+++ b/datatypes/pom.xml
@@ -9,7 +9,7 @@
com.fasterxml.jackson.modulejackson-modules-java8
- 2.18.11-SNAPSHOT
+ 2.23.0-SNAPSHOTcom.fasterxml.jackson.datatypejackson-datatype-jdk8
@@ -35,22 +35,16 @@ JDK 8 data types.
org.apache.maven.plugins${version.plugin.surefire}maven-surefire-plugin
-
-
- com/fasterxml/jackson/failing/*.java
-
- com.google.code.maven-replacer-pluginreplacer
-
-
- process-packageVersion
- generate-sources
-
-
+
+
+
+ org.cyclonedx
+ cyclonedx-maven-plugin
+
+
+ org.gradlex
+ gradle-module-metadata-maven-plugin
+
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/ContextualOptional17Test.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/ContextualOptional17Test.java
index 37f9e448..64239a2e 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/ContextualOptional17Test.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/ContextualOptional17Test.java
@@ -5,10 +5,14 @@
import java.util.Optional;
import java.util.TimeZone;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.ObjectMapper;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
public class ContextualOptional17Test extends ModuleTestBase
{
// [datatypes-java8#17]
@@ -30,6 +34,7 @@ static class ContextualOptionals
/**********************************************************
*/
+ @Test
public void testContextualOptionals() throws Exception
{
final ObjectMapper mapper = mapperWithModule();
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/CreatorTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/CreatorTest.java
index 66f41e80..dd33b8cf 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/CreatorTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/CreatorTest.java
@@ -2,11 +2,15 @@
import java.util.Optional;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
+import static org.junit.jupiter.api.Assertions.*;
+
public class CreatorTest extends ModuleTestBase
{
static class CreatorWithOptionalStrings
@@ -35,6 +39,7 @@ public CreatorWithOptionalStrings(@JsonProperty("a") Optional a,
* Test to ensure that creator parameters use defaulting
* (introduced in Jackson 2.6)
*/
+ @Test
public void testCreatorWithOptionalDefault() throws Exception
{
CreatorWithOptionalStrings bean = MAPPER.readValue(
@@ -47,6 +52,7 @@ public void testCreatorWithOptionalDefault() throws Exception
assertEquals("foo", bean.a.get());
}
+ @Test
public void testCreatorWithOptionalAbsentAsNull() throws Exception
{
Jdk8Module module = new Jdk8Module()
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/DoubleStreamSerializerTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/DoubleStreamSerializerTest.java
index db29ce40..9b845777 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/DoubleStreamSerializerTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/DoubleStreamSerializerTest.java
@@ -1,15 +1,15 @@
package com.fasterxml.jackson.datatype.jdk8;
-import static org.junit.Assert.*;
-
import java.io.IOException;
import java.util.stream.DoubleStream;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
+import static org.junit.jupiter.api.Assertions.*;
+
@SuppressWarnings({ "unqualified-field-access", "javadoc" })
public class DoubleStreamSerializerTest extends StreamTestBase {
@@ -21,7 +21,7 @@ public class DoubleStreamSerializerTest extends StreamTestBase {
final String exceptionMessage = "DoubleStream peek threw";
- @Before
+ @BeforeEach
public void setUp() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/IntStreamSerializerTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/IntStreamSerializerTest.java
index bc06a70e..09360df5 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/IntStreamSerializerTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/IntStreamSerializerTest.java
@@ -1,11 +1,11 @@
package com.fasterxml.jackson.datatype.jdk8;
-import static org.junit.Assert.*;
+import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.stream.IntStream;
-import org.junit.Test;
+import static org.junit.jupiter.api.Assertions.*;
@SuppressWarnings({ "unqualified-field-access", "javadoc" })
public class IntStreamSerializerTest extends StreamTestBase {
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/JDKSerializabilityTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/JDKSerializabilityTest.java
index 195bec9e..f48d86d6 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/JDKSerializabilityTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/JDKSerializabilityTest.java
@@ -3,10 +3,12 @@
import java.io.*;
import java.util.Optional;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
public class JDKSerializabilityTest extends ModuleTestBase
{
static class BooleanBean {
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/Java8OptionalUnwrappedTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/Java8OptionalUnwrappedTest.java
index 1c4639cb..35804af1 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/Java8OptionalUnwrappedTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/Java8OptionalUnwrappedTest.java
@@ -3,6 +3,8 @@
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JavaType;
@@ -11,6 +13,8 @@
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
+import static org.junit.jupiter.api.Assertions.*;
+
public class Java8OptionalUnwrappedTest extends ModuleTestBase
{
static class Child {
@@ -46,6 +50,7 @@ static class Bean2 {
public String name;
}
+ @Test
public void testUntypedWithOptionalsNotNulls() throws Exception
{
final ObjectMapper mapper = mapperWithModule(false);
@@ -55,6 +60,7 @@ public void testUntypedWithOptionalsNotNulls() throws Exception
}
// for [datatype-jdk8#20]
+ @Test
public void testShouldSerializeUnwrappedOptional() throws Exception {
final ObjectMapper mapper = mapperWithModule(false);
@@ -63,6 +69,7 @@ public void testShouldSerializeUnwrappedOptional() throws Exception {
}
// for [datatype-jdk8#26]
+ @Test
public void testPropogatePrefixToSchema() throws Exception {
final ObjectMapper mapper = mapperWithModule(false);
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/LongStreamSerializerTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/LongStreamSerializerTest.java
index 1d483a54..fc70b207 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/LongStreamSerializerTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/LongStreamSerializerTest.java
@@ -1,11 +1,11 @@
package com.fasterxml.jackson.datatype.jdk8;
-import static org.junit.Assert.*;
-
import java.io.IOException;
import java.util.stream.LongStream;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@SuppressWarnings({ "unqualified-field-access", "javadoc" })
public class LongStreamSerializerTest extends StreamTestBase {
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/ModuleTestBase.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/ModuleTestBase.java
index f72498c6..c0240db6 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/ModuleTestBase.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/ModuleTestBase.java
@@ -8,7 +8,9 @@
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
-public abstract class ModuleTestBase extends junit.framework.TestCase
+import static org.junit.jupiter.api.Assertions.fail;
+
+public abstract class ModuleTestBase
{
public static class NoCheckSubTypeValidator
extends PolymorphicTypeValidator.Base
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalBasicTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalBasicTest.java
index 29eaae6e..ed883906 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalBasicTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalBasicTest.java
@@ -2,6 +2,8 @@
import java.util.*;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
@@ -9,6 +11,8 @@
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
+import static org.junit.jupiter.api.Assertions.*;
+
public class OptionalBasicTest extends ModuleTestBase
{
public static final class OptionalData {
@@ -59,6 +63,7 @@ public static class ContainedImpl implements Contained { }
private final ObjectMapper MAPPER = mapperWithModule();
+ @Test
public void testOptionalTypeResolution() throws Exception {
// With 2.6, we need to recognize it as ReferenceType
JavaType t = MAPPER.constructType(Optional.class);
@@ -67,6 +72,7 @@ public void testOptionalTypeResolution() throws Exception {
assertTrue(t.isReferenceType());
}
+ @Test
public void testDeserAbsent() throws Exception {
Optional> value = MAPPER.readValue("null",
new TypeReference>() {
@@ -74,6 +80,7 @@ public void testDeserAbsent() throws Exception {
assertFalse(value.isPresent());
}
+ @Test
public void testDeserSimpleString() throws Exception {
Optional> value = MAPPER.readValue("\"simpleString\"",
new TypeReference>() {
@@ -82,6 +89,7 @@ public void testDeserSimpleString() throws Exception {
assertEquals("simpleString", value.get());
}
+ @Test
public void testDeserInsideObject() throws Exception {
OptionalData data = MAPPER.readValue("{\"myString\":\"simpleString\"}",
OptionalData.class);
@@ -89,6 +97,7 @@ public void testDeserInsideObject() throws Exception {
assertEquals("simpleString", data.myString.get());
}
+ @Test
public void testDeserComplexObject() throws Exception {
TypeReference> type = new TypeReference>() {
};
@@ -99,6 +108,7 @@ public void testDeserComplexObject() throws Exception {
assertEquals("simpleString", data.get().myString.get());
}
+ @Test
public void testDeserGeneric() throws Exception {
TypeReference>> type = new TypeReference>>() {
};
@@ -109,16 +119,19 @@ public void testDeserGeneric() throws Exception {
assertEquals("simpleString", data.get().myData.get());
}
+ @Test
public void testSerAbsent() throws Exception {
String value = MAPPER.writeValueAsString(Optional.empty());
assertEquals("null", value);
}
+ @Test
public void testSerSimpleString() throws Exception {
String value = MAPPER.writeValueAsString(Optional.of("simpleString"));
assertEquals("\"simpleString\"", value);
}
+ @Test
public void testSerInsideObject() throws Exception {
OptionalData data = new OptionalData();
data.myString = Optional.of("simpleString");
@@ -126,6 +139,7 @@ public void testSerInsideObject() throws Exception {
assertEquals("{\"myString\":\"simpleString\"}", value);
}
+ @Test
public void testSerComplexObject() throws Exception {
OptionalData data = new OptionalData();
data.myString = Optional.of("simpleString");
@@ -133,6 +147,7 @@ public void testSerComplexObject() throws Exception {
assertEquals("{\"myString\":\"simpleString\"}", value);
}
+ @Test
public void testSerGeneric() throws Exception {
OptionalGenericData data = new OptionalGenericData();
data.myData = Optional.of("simpleString");
@@ -140,6 +155,7 @@ public void testSerGeneric() throws Exception {
assertEquals("{\"myData\":\"simpleString\"}", value);
}
+ @Test
public void testSerNonNull() throws Exception {
OptionalData data = new OptionalData();
data.myString = Optional.empty();
@@ -149,6 +165,7 @@ public void testSerNonNull() throws Exception {
assertEquals("{}", value);
}
+ @Test
public void testSerOptDefault() throws Exception {
OptionalData data = new OptionalData();
data.myString = Optional.empty();
@@ -157,6 +174,7 @@ public void testSerOptDefault() throws Exception {
assertEquals("{\"myString\":null}", value);
}
+ @Test
public void testSerOptNull() throws Exception {
OptionalData data = new OptionalData();
data.myString = null;
@@ -166,6 +184,7 @@ public void testSerOptNull() throws Exception {
}
@SuppressWarnings("deprecation")
+ @Test
public void testSerOptDisableAsNull() throws Exception {
final OptionalData data = new OptionalData();
data.myString = Optional.empty();
@@ -187,6 +206,7 @@ public void testSerOptDisableAsNull() throws Exception {
assertEquals("{}", mapper.writeValueAsString(data));
}
+ @Test
public void testSerOptNonEmpty() throws Exception {
OptionalData data = new OptionalData();
data.myString = null;
@@ -195,6 +215,7 @@ public void testSerOptNonEmpty() throws Exception {
assertEquals("{}", value);
}
+ @Test
public void testWithTypingEnabled() throws Exception {
final ObjectMapper objectMapper = mapperWithModule();
// ENABLE TYPING
@@ -210,6 +231,7 @@ public void testWithTypingEnabled() throws Exception {
assertEquals(myData.myString, deserializedMyData.myString);
}
+ @Test
public void testObjectId() throws Exception {
final Unit input = new Unit();
input.link(input);
@@ -222,6 +244,7 @@ public void testObjectId() throws Exception {
assertSame(result, base);
}
+ @Test
public void testOptionalCollection() throws Exception {
TypeReference>> typeReference = new TypeReference>>() {
@@ -238,10 +261,11 @@ public void testOptionalCollection() throws Exception {
List> result = MAPPER.readValue(str, typeReference);
assertEquals(list.size(), result.size());
for (int i = 0; i < list.size(); ++i) {
- assertEquals("Entry #" + i, list.get(i), result.get(i));
+ assertEquals(list.get(i), result.get(i), "Entry #" + i);
}
}
+ @Test
public void testPolymorphic() throws Exception
{
final Container dto = new Container();
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalBooleanTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalBooleanTest.java
index 56ac1fb4..26475cb3 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalBooleanTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalBooleanTest.java
@@ -2,8 +2,12 @@
import java.util.Optional;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.databind.ObjectMapper;
+import static org.junit.jupiter.api.Assertions.*;
+
public class OptionalBooleanTest extends ModuleTestBase
{
static class BooleanBean {
@@ -18,6 +22,7 @@ public BooleanBean(Boolean b) {
private final ObjectMapper MAPPER = mapperWithModule();
// for [datatype-jdk8#23]
+ @Test
public void testBoolean() throws Exception
{
// First, serialization
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalConverterTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalConverterTest.java
index f01e7e42..fb508da9 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalConverterTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalConverterTest.java
@@ -2,11 +2,15 @@
import java.util.Optional;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.util.StdConverter;
+import static org.junit.jupiter.api.Assertions.*;
+
// [modules-java#294]: Optional + converters
public class OptionalConverterTest extends ModuleTestBase
{
@@ -56,6 +60,7 @@ public PointReferenceBean(int x, int y) {
private final ObjectMapper MAPPER = mapperWithModule();
// [modules-java#294]: Optional + converters, deser
+ @Test
public void testDeserializeOptionalConverting() throws Exception {
PointReferenceBean w = MAPPER.readerFor(PointReferenceBean.class)
.readValue("{\"opt\": [1,2]}");
@@ -68,6 +73,7 @@ public void testDeserializeOptionalConverting() throws Exception {
}
// [modules-java#294]: Optional + converters, ser
+ @Test
public void testSerializeOptionalConverting() throws Exception {
assertEquals("{\"opt\":[3,4]}",
MAPPER.writeValueAsString(new PointReferenceBean(3, 4)));
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalMergeTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalMergeTest.java
index cd852ee7..bea56026 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalMergeTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalMergeTest.java
@@ -4,9 +4,13 @@
import java.util.List;
import java.util.Optional;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.annotation.JsonMerge;
import com.fasterxml.jackson.databind.ObjectMapper;
+import static org.junit.jupiter.api.Assertions.*;
+
public class OptionalMergeTest extends ModuleTestBase
{
// [modules-java8#214]
@@ -18,6 +22,7 @@ static class OptionalListWrapper {
private final ObjectMapper MAPPER = mapperWithModule();
// [modules-java8#214]: ReferenceType of List, merge
+ @Test
public void testMergeToListViaRef() throws Exception
{
OptionalListWrapper base = MAPPER.readValue(a2q("{'list':['a']}"),
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalNumbersTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalNumbersTest.java
index b5c6849c..667d0b6d 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalNumbersTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalNumbersTest.java
@@ -4,13 +4,14 @@
import java.util.OptionalInt;
import java.util.OptionalLong;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
public class OptionalNumbersTest extends ModuleTestBase
{
@@ -52,12 +53,14 @@ static class OptionalDoubleBean {
/**********************************************************
*/
+ @Test
public void testOptionalIntAbsent() throws Exception
{
String json = MAPPER.writeValueAsString(OptionalInt.empty());
assertFalse(MAPPER.readValue(json, OptionalInt.class).isPresent());
}
+ @Test
public void testOptionalIntInArrayAbsent() throws Exception
{
OptionalInt[] ints = MAPPER.readValue("[null]", OptionalInt[].class);
@@ -66,11 +69,13 @@ public void testOptionalIntInArrayAbsent() throws Exception
assertFalse(ints[0].isPresent());
}
+ @Test
public void testOptionalIntPresent() throws Exception
{
assertEquals(5, MAPPER.readValue(MAPPER.writeValueAsBytes(OptionalInt.of(5)), OptionalInt.class).getAsInt());
}
+ @Test
public void testOptionalIntCoerceFromString() throws Exception
{
OptionalInt opt = MAPPER.readValue(q("123"), OptionalInt.class);
@@ -95,11 +100,13 @@ public void testOptionalIntCoerceFromString() throws Exception
/**********************************************************
*/
+ @Test
public void testOptionalLongAbsent() throws Exception
{
assertFalse(MAPPER.readValue(MAPPER.writeValueAsBytes(OptionalLong.empty()), OptionalLong.class).isPresent());
}
+ @Test
public void testOptionalLongInArrayAbsent() throws Exception
{
OptionalLong[] arr = MAPPER.readValue("[null]", OptionalLong[].class);
@@ -107,12 +114,14 @@ public void testOptionalLongInArrayAbsent() throws Exception
assertNotNull(arr[0]);
assertFalse(arr[0].isPresent());
}
-
+
+ @Test
public void testOptionalLongPresent() throws Exception
{
assertEquals(Long.MAX_VALUE, MAPPER.readValue(MAPPER.writeValueAsBytes(OptionalLong.of(Long.MAX_VALUE)), OptionalLong.class).getAsLong());
}
+ @Test
public void testOptionalLongCoerceFromString() throws Exception
{
OptionalLong opt = MAPPER.readValue(q("123"), OptionalLong.class);
@@ -132,7 +141,8 @@ public void testOptionalLongCoerceFromString() throws Exception
assertNotNull(bean.value);
assertEquals(19L, bean.value.getAsLong());
}
-
+
+ @Test
public void testOptionalLongSerializeFilter() throws Exception
{
ObjectMapper mapper = mapperWithModule()
@@ -158,11 +168,13 @@ public void testOptionalLongSerializeFilter() throws Exception
/**********************************************************
*/
+ @Test
public void testOptionalDoubleAbsent() throws Exception
{
assertFalse(MAPPER.readValue(MAPPER.writeValueAsBytes(OptionalInt.empty()), OptionalInt.class).isPresent());
}
+ @Test
public void testOptionalDoubleInArrayAbsent() throws Exception
{
OptionalDouble[] arr = MAPPER.readValue("[null]", OptionalDouble[].class);
@@ -171,11 +183,13 @@ public void testOptionalDoubleInArrayAbsent() throws Exception
assertFalse(arr[0].isPresent());
}
+ @Test
public void testOptionalDoublePresent() throws Exception
{
assertEquals(Double.MIN_VALUE, MAPPER.readValue(MAPPER.writeValueAsBytes(OptionalDouble.of(Double.MIN_VALUE)), OptionalDouble.class).getAsDouble());
}
+ @Test
public void testOptionalDoubleCoerceFromString() throws Exception
{
OptionalDouble opt = MAPPER.readValue(q("0.25"), OptionalDouble.class);
@@ -196,6 +210,7 @@ public void testOptionalDoubleCoerceFromString() throws Exception
assertEquals(0.5, bean.value.getAsDouble());
}
+ @Test
public void testOptionalDoubleInArraySpecialValues() throws Exception
{
OptionalDouble[] actual = MAPPER.readValue(
@@ -212,6 +227,7 @@ public void testOptionalDoubleInArraySpecialValues() throws Exception
assertArrayEquals(expected, actual);
}
+ @Test
public void testOptionalDoubleInArraySpecialValuesWithoutCoercion() throws Exception
{
OptionalDouble[] actual = MAPPER_WITHOUT_COERCION.readValue(
@@ -227,12 +243,14 @@ public void testOptionalDoubleInArraySpecialValuesWithoutCoercion() throws Excep
assertArrayEquals(expected, actual);
}
+ @Test
public void testQuotedOptionalDoubleWithoutCoercion()
{
assertThrows(MismatchedInputException.class,
() -> MAPPER_WITHOUT_COERCION.readValue(a2q("['1']"), OptionalDouble[].class));
}
+ @Test
public void testOptionalDoubleBeanSpecialValuesWithoutCoercion_null() throws Exception
{
OptionalDoubleBean bean = MAPPER_WITHOUT_COERCION.readValue(
@@ -240,6 +258,7 @@ public void testOptionalDoubleBeanSpecialValuesWithoutCoercion_null() throws Exc
assertEquals(OptionalDouble.empty(), bean.value);
}
+ @Test
public void testOptionalDoubleBeanSpecialValuesWithoutCoercion_nan() throws Exception
{
OptionalDoubleBean bean = MAPPER_WITHOUT_COERCION.readValue(
@@ -247,6 +266,7 @@ public void testOptionalDoubleBeanSpecialValuesWithoutCoercion_nan() throws Exce
assertEquals(OptionalDouble.of(Double.NaN), bean.value);
}
+ @Test
public void testOptionalDoubleBeanSpecialValuesWithoutCoercion_positiveInfinity() throws Exception
{
OptionalDoubleBean bean = MAPPER_WITHOUT_COERCION.readValue(
@@ -254,6 +274,7 @@ public void testOptionalDoubleBeanSpecialValuesWithoutCoercion_positiveInfinity(
assertEquals(OptionalDouble.of(Double.POSITIVE_INFINITY), bean.value);
}
+ @Test
public void testOptionalDoubleBeanSpecialValuesWithoutCoercion_negativeInfinity() throws Exception
{
OptionalDoubleBean bean = MAPPER_WITHOUT_COERCION.readValue(
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalTest.java
index 0ff5aa0f..d4bc805c 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalTest.java
@@ -4,6 +4,8 @@
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -18,6 +20,8 @@
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
import com.fasterxml.jackson.databind.type.TypeFactory;
+import static org.junit.jupiter.api.Assertions.*;
+
public class OptionalTest extends ModuleTestBase
{
private static final TypeReference> OPTIONAL_STRING_TYPE = new TypeReference>() {};
@@ -126,21 +130,25 @@ public String deserialize(JsonParser p, DeserializationContext ctxt)
/**********************************************************
*/
+ @Test
public void testStringAbsent() throws Exception
{
assertFalse(roundtrip(Optional.empty(), OPTIONAL_STRING_TYPE).isPresent());
}
+ @Test
public void testStringPresent() throws Exception
{
assertEquals("test", roundtrip(Optional.of("test"), OPTIONAL_STRING_TYPE).get());
}
+ @Test
public void testBeanAbsent() throws Exception
{
assertFalse(roundtrip(Optional.empty(), OPTIONAL_BEAN_TYPE).isPresent());
}
+ @Test
public void testBeanPresent() throws Exception
{
final TestBean bean = new TestBean(Integer.MAX_VALUE, "woopwoopwoopwoopwoop");
@@ -148,6 +156,7 @@ public void testBeanPresent() throws Exception
}
// [issue#4]
+ @Test
public void testBeanWithCreator() throws Exception
{
final Issue4Entity emptyEntity = new Issue4Entity(Optional.empty());
@@ -160,6 +169,7 @@ public void testBeanWithCreator() throws Exception
}
// [issue#4]
+ @Test
public void testOptionalStringInBean() throws Exception
{
OptionalStringBean bean = MAPPER.readValue("{\"value\":\"xyz\"}", OptionalStringBean.class);
@@ -168,6 +178,7 @@ public void testOptionalStringInBean() throws Exception
}
// To support [datatype-jdk8#8]
+ @Test
public void testExcludeIfOptionalAbsent() throws Exception
{
ObjectMapper mapper = mapperWithModule()
@@ -187,6 +198,7 @@ public void testExcludeIfOptionalAbsent() throws Exception
mapper.writeValueAsString(new OptionalStringBean(null)));
}
+ @Test
public void testWithCustomDeserializer() throws Exception
{
CaseChangingStringWrapper w = MAPPER.readValue(a2q("{'value':'FoobaR'}"),
@@ -195,6 +207,7 @@ public void testWithCustomDeserializer() throws Exception
}
// [modules-java8#36]
+ @Test
public void testWithCustomDeserializerIfOptionalAbsent() throws Exception
{
// 10-Aug-2017, tatu: Actually this is not true: missing value does not trigger
@@ -208,6 +221,7 @@ public void testWithCustomDeserializerIfOptionalAbsent() throws Exception
CaseChangingStringWrapper.class).value);
}
+ @Test
public void testCustomSerializer() throws Exception
{
final String VALUE = "fooBAR";
@@ -215,6 +229,7 @@ public void testCustomSerializer() throws Exception
assertEquals(json, a2q("{'value':'FOOBAR'}"));
}
+ @Test
public void testCustomSerializerIfOptionalAbsent() throws Exception
{
ObjectMapper mapper = mapperWithModule()
@@ -235,6 +250,7 @@ public void testCustomSerializerIfOptionalAbsent() throws Exception
}
// [modules-java8#33]: Verify against regression...
+ @Test
public void testOtherRefSerializers() throws Exception
{
String json = MAPPER.writeValueAsString(new AtomicReference("foo"));
@@ -242,6 +258,7 @@ public void testOtherRefSerializers() throws Exception
}
// Check [databind#2796] here too
+ @Test
public void testTypeResolution() throws Exception
{
// Should be able to construct using parametric `constructType()`
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithEmptyTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithEmptyTest.java
index ca96c4fd..dd415a65 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithEmptyTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithEmptyTest.java
@@ -2,9 +2,13 @@
import java.util.Optional;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
+import static org.junit.jupiter.api.Assertions.*;
+
public class OptionalWithEmptyTest extends ModuleTestBase
{
private final ObjectMapper MAPPER = mapperWithModule();
@@ -18,12 +22,14 @@ public BooleanBean(Boolean b) {
}
}
+ @Test
public void testOptionalFromEmpty() throws Exception {
Optional> value = MAPPER.readValue(q(""), new TypeReference>() {});
assertEquals(false, value.isPresent());
}
// for [datatype-jdk8#23]
+ @Test
public void testBooleanWithEmpty() throws Exception
{
// and looks like a special, somewhat non-conforming case is what a user had
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalnclusionTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalnclusionTest.java
index 0ddb1885..32508055 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalnclusionTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalnclusionTest.java
@@ -4,6 +4,8 @@
import java.util.Map;
import java.util.Optional;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
@@ -11,6 +13,8 @@
import com.fasterxml.jackson.databind.ObjectMapper;
+import static org.junit.jupiter.api.Assertions.*;
+
public class OptionalnclusionTest extends ModuleTestBase
{
@JsonAutoDetect(fieldVisibility=Visibility.ANY)
@@ -55,6 +59,7 @@ public OptMapBean(String key, Optional> v) {
private final ObjectMapper MAPPER = mapperWithModule();
+ @Test
public void testSerOptNonEmpty() throws Exception
{
OptionalData data = new OptionalData();
@@ -64,6 +69,7 @@ public void testSerOptNonEmpty() throws Exception
assertEquals("{}", value);
}
+ @Test
public void testSerOptNonDefault() throws Exception
{
OptionalData data = new OptionalData();
@@ -73,6 +79,7 @@ public void testSerOptNonDefault() throws Exception
assertEquals("{}", value);
}
+ @Test
public void testSerOptNonAbsent() throws Exception
{
OptionalData data = new OptionalData();
@@ -82,6 +89,7 @@ public void testSerOptNonAbsent() throws Exception
assertEquals("{}", value);
}
+ @Test
public void testExcludeEmptyStringViaOptional() throws Exception
{
String json = MAPPER.writeValueAsString(new OptionalNonEmptyStringBean("x"));
@@ -92,6 +100,7 @@ public void testExcludeEmptyStringViaOptional() throws Exception
assertEquals("{}", json);
}
+ @Test
public void testSerPropInclusionAlways() throws Exception
{
JsonInclude.Value incl =
@@ -101,6 +110,7 @@ public void testSerPropInclusionAlways() throws Exception
mapper.writeValueAsString(OptionalGenericData.construct(Boolean.TRUE)));
}
+ @Test
public void testSerPropInclusionNonNull() throws Exception
{
JsonInclude.Value incl =
@@ -110,6 +120,7 @@ public void testSerPropInclusionNonNull() throws Exception
mapper.writeValueAsString(OptionalGenericData.construct(Boolean.TRUE)));
}
+ @Test
public void testSerPropInclusionNonAbsent() throws Exception
{
JsonInclude.Value incl =
@@ -119,6 +130,7 @@ public void testSerPropInclusionNonAbsent() throws Exception
mapper.writeValueAsString(OptionalGenericData.construct(Boolean.TRUE)));
}
+ @Test
public void testSerPropInclusionNonEmpty() throws Exception
{
JsonInclude.Value incl =
@@ -128,6 +140,7 @@ public void testSerPropInclusionNonEmpty() throws Exception
mapper.writeValueAsString(OptionalGenericData.construct(Boolean.TRUE)));
}
+ @Test
public void testMapElementInclusion() throws Exception
{
ObjectMapper mapper = mapperWithModule().setDefaultPropertyInclusion(
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/PolymorphicOptionalTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/PolymorphicOptionalTest.java
index 0d8741dc..d9c82c60 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/PolymorphicOptionalTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/PolymorphicOptionalTest.java
@@ -2,11 +2,15 @@
import java.util.Optional;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.databind.ObjectMapper;
+import static org.junit.jupiter.api.Assertions.*;
+
public class PolymorphicOptionalTest extends ModuleTestBase
{
// For [datatype-jdk8#14]
@@ -25,6 +29,7 @@ public static class ContainedImpl implements Contained { }
private final ObjectMapper MAPPER = mapperWithModule();
// [datatype-jdk8#14]
+ @Test
public void testPolymorphic14() throws Exception
{
final Container dto = new Container();
diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/SchemaVisitorTest.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/SchemaVisitorTest.java
index fac77e9a..f1283b9a 100644
--- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/SchemaVisitorTest.java
+++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/SchemaVisitorTest.java
@@ -3,17 +3,22 @@
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonParser.NumberType;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.*;
+import static org.junit.jupiter.api.Assertions.*;
+
// trivial tests visitor used (mostly) for JSON Schema generation
public class SchemaVisitorTest extends ModuleTestBase
{
private final ObjectMapper MAPPER = mapperWithModule();
// for [datatype-jdk8#25]
+ @Test
public void testOptionalInteger() throws Exception
{
final AtomicReference
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
@@ -86,29 +98,21 @@
com.google.code.maven-replacer-pluginreplacer
-
-
- process-packageVersion
- generate-sources
-
-
+
+
+
+ org.cyclonedx
+ cyclonedx-maven-pluginorg.apache.maven.pluginsmaven-compiler-plugin
- 3.0true${javac.src.version}${javac.target.version}truetrue
- true
-
- 10000
- 10000
-
-
+
+
+ org.gradlex
+ gradle-module-metadata-maven-plugin
+
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/DecimalUtils.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/DecimalUtils.java
index 7d8484db..a76c0139 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/DecimalUtils.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/DecimalUtils.java
@@ -30,8 +30,6 @@
*/
public final class DecimalUtils
{
- private static final BigDecimal ONE_BILLION = new BigDecimal(1_000_000_000L);
-
private DecimalUtils() { }
public static String toDecimal(long seconds, int nanoseconds)
@@ -86,6 +84,14 @@ public static String toDecimal(long seconds, int nanoseconds)
*/
public static BigDecimal toBigDecimal(long seconds, int nanoseconds)
{
+ // [modules-java8#359] For negative seconds with positive nanos (times before epoch),
+ // we need to compute the proper decimal value: seconds + (nanos / 1_000_000_000)
+ // Example: Instant{epochSecond=-1, nano=999000000} represents -0.001 seconds
+ if (seconds < 0 && nanoseconds > 0) {
+ return BigDecimal.valueOf(seconds)
+ .add(BigDecimal.valueOf(nanoseconds).scaleByPowerOfTen(-9));
+ }
+
if (nanoseconds == 0L) {
// 14-Mar-2015, tatu: Let's retain one zero to avoid interpretation
// as integral number
@@ -98,15 +104,17 @@ public static BigDecimal toBigDecimal(long seconds, int nanoseconds)
}
/**
- * @deprecated due to potential unbounded latency on some JRE releases.
+ * Extracts the seconds and nanoseconds component of {@code seconds} as {@code long} and {@code int}
+ * values, passing them to the given converter. The implementation avoids latency issues present
+ * on some JRE releases.
+ *
+ * @since 2.9.8
+ * @deprecated Since 2.19: use {@link #extractSecondsAndNanos(BigDecimal, BiFunction, boolean)} instead.
*/
- @Deprecated // since 2.9.8
- public static int extractNanosecondDecimal(BigDecimal value, long integer)
+ @Deprecated // since 2.19
+ public static T extractSecondsAndNanos(BigDecimal seconds, BiFunction convert)
{
- // !!! 14-Mar-2016, tatu: Somewhat inefficient; should replace with functionally
- // equivalent code that just subtracts integral part? (or, measure and show
- // there's no difference and do nothing... )
- return value.subtract(BigDecimal.valueOf(integer)).multiply(ONE_BILLION).intValue();
+ return extractSecondsAndNanos(seconds, convert, true);
}
/**
@@ -114,13 +122,12 @@ public static int extractNanosecondDecimal(BigDecimal value, long integer)
* values, passing them to the given converter. The implementation avoids latency issues present
* on some JRE releases.
*
- * @since 2.9.8
+ * @since 2.19
*/
- public static T extractSecondsAndNanos(BigDecimal seconds, BiFunction convert)
- {
+ public static T extractSecondsAndNanos(BigDecimal seconds,
+ BiFunction convert, boolean negativeAdjustment) {
// Complexity is here to workaround unbounded latency in some BigDecimal operations.
// https://github.com/FasterXML/jackson-databind/issues/2141
-
long secondsOnly;
int nanosOnly;
@@ -141,8 +148,11 @@ else if (seconds.scale() < -63) {
nanosOnly = nanoseconds.subtract(BigDecimal.valueOf(secondsOnly).scaleByPowerOfTen(9)).intValue();
if (secondsOnly < 0 && secondsOnly > Instant.MIN.getEpochSecond()) {
- // Issue #69 and Issue #120: avoid sending a negative adjustment to the Instant constructor, we want this as the actual nanos
- nanosOnly = Math.abs(nanosOnly);
+ // [modules-java8#337] since 2.19, not always we need to adjust nanos
+ if (negativeAdjustment) {
+ // Issue #69 and Issue #120: avoid sending a negative adjustment to the Instant constructor, we want this as the actual nanos
+ nanosOnly = Math.abs(nanosOnly);
+ }
}
}
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JSR310Module.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JSR310Module.java
index a8b7e68a..d12cc119 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JSR310Module.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JSR310Module.java
@@ -26,6 +26,7 @@
import com.fasterxml.jackson.databind.deser.ValueInstantiators;
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
+import com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
@@ -184,7 +185,8 @@ public ValueInstantiator findValueInstantiator(DeserializationConfig config,
} else {
// we don't need Annotations, so constructing directly is fine here
// even if it's not generally recommended
- ac = AnnotatedClass.construct(config.constructType(ZoneId.class), config);
+ ac = AnnotatedClassResolver.resolve(config,
+ config.constructType(ZoneId.class), config);
}
if (!inst.canCreateFromString()) {
AnnotatedMethod factory = _findFactory(ac, "of", String.class);
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeFeature.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeFeature.java
index 94912d27..36bebe27 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeFeature.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeFeature.java
@@ -19,6 +19,19 @@ public enum JavaTimeFeature implements JacksonFeature
*/
NORMALIZE_DESERIALIZED_ZONE_ID(true),
+ /**
+ * Feature that determines whether the {@link java.util.TimeZone} of the
+ * {@link com.fasterxml.jackson.databind.DeserializationContext} is used
+ * when leniently deserializing {@link java.time.LocalDate} or
+ * {@link java.time.LocalDateTime} from the UTC/ISO instant format.
+ *
+ * Default setting is disabled, for backwards-compatibility with
+ * Jackson 2.18.
+ *
+ * @since 2.19
+ */
+ USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING(false),
+
/**
* Feature that controls whether stringified numbers (Strings that without
* quotes would be legal JSON Numbers) may be interpreted as
@@ -38,7 +51,40 @@ public enum JavaTimeFeature implements JacksonFeature
*
* Default setting is false, meaning that Month is serialized/deserialized as a zero-based index.
*/
- ONE_BASED_MONTHS(false)
+ ONE_BASED_MONTHS(false),
+
+ /**
+ * Feature that determines whether sub-second digits are always written when
+ * serializing {@link java.time.Instant}, {@link java.time.OffsetDateTime},
+ * {@link java.time.ZonedDateTime} and {@link java.time.LocalDateTime} values
+ * as ISO-8601 Strings using the default format.
+ *
+ * When disabled (the default), the JDK-provided ISO formatters are used and
+ * a zero sub-second value is omitted altogether -- {@code 2017-09-14T04:28:48Z}
+ * -- which means that output width varies with the value, breaking systems
+ * that expect fixed-precision timestamps (or that sort timestamps as text).
+ *
+ * When enabled, at least 3 (millisecond) sub-second digits are always written,
+ * zero-padded if necessary -- {@code 2017-09-14T04:28:48.000Z}. Higher precision
+ * is preserved: a value with microsecond or nanosecond precision is written with
+ * 6 or 9 digits respectively, so no information is lost.
+ *
+ * Only affects the default format: an explicit {@code DateTimeFormatter} or
+ * a {@link com.fasterxml.jackson.annotation.JsonFormat} pattern takes precedence,
+ * as does writing values as numeric timestamps.
+ *
+ * Also note that this only applies to values, and NOT to {@link java.util.Map}
+ * keys: date/time keys keep being written using the JDK-provided ISO formatters,
+ * so a zero sub-second value is still omitted there. Types other than the four
+ * listed above -- notably {@link java.time.LocalTime} and
+ * {@link java.time.OffsetTime}, whose ISO formats also omit the seconds field --
+ * are likewise unaffected.
+ *
+ * Default setting is disabled, for backwards compatibility.
+ *
+ * @since 2.23
+ */
+ ALWAYS_WRITE_SUBSECOND_DIGITS(false)
;
/**
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java
index 2325c5f0..80ede099 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java
@@ -17,6 +17,7 @@
package com.fasterxml.jackson.datatype.jsr310;
import java.time.*;
+import java.time.temporal.TemporalAdjuster;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
@@ -31,6 +32,7 @@
import com.fasterxml.jackson.databind.module.SimpleKeyDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
+import com.fasterxml.jackson.databind.ser.std.ToEmptyObjectSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.*;
import com.fasterxml.jackson.datatype.jsr310.deser.key.*;
@@ -129,8 +131,8 @@ public void setupModule(SetupContext context) {
// // Other deserializers
desers.addDeserializer(Duration.class, DurationDeserializer.INSTANCE);
- desers.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
- desers.addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE);
+ desers.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE.withFeatures(_features));
+ desers.addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE.withFeatures(_features));
desers.addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE);
desers.addDeserializer(MonthDay.class, MonthDayDeserializer.INSTANCE);
desers.addDeserializer(OffsetTime.class, OffsetTimeDeserializer.INSTANCE);
@@ -152,15 +154,15 @@ public void setupModule(SetupContext context) {
context.addDeserializers(_deserializers);
}
- SimpleSerializers sers = new SimpleSerializers();
+ JavaTimeSerializers sers = new JavaTimeSerializers();
sers.addSerializer(Duration.class, DurationSerializer.INSTANCE);
- sers.addSerializer(Instant.class, InstantSerializer.INSTANCE);
- sers.addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE);
+ sers.addSerializer(Instant.class, InstantSerializer.INSTANCE.withFeatures(_features));
+ sers.addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE.withFeatures(_features));
sers.addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE);
sers.addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE);
sers.addSerializer(MonthDay.class, MonthDaySerializer.INSTANCE);
- sers.addSerializer(OffsetDateTime.class, OffsetDateTimeSerializer.INSTANCE);
+ sers.addSerializer(OffsetDateTime.class, OffsetDateTimeSerializer.INSTANCE.withFeatures(_features));
sers.addSerializer(OffsetTime.class, OffsetTimeSerializer.INSTANCE);
sers.addSerializer(Period.class, new ToStringSerializer(Period.class));
sers.addSerializer(Year.class, YearSerializer.INSTANCE);
@@ -171,7 +173,7 @@ public void setupModule(SetupContext context) {
* serialization with timezone offset only, not timezone id.
* But this is configurable.
*/
- sers.addSerializer(ZonedDateTime.class, ZonedDateTimeSerializer.INSTANCE);
+ sers.addSerializer(ZonedDateTime.class, ZonedDateTimeSerializer.INSTANCE.withFeatures(_features));
// since 2.11: need to override Type Id handling
// (actual concrete type is `ZoneRegion`, but that's not visible)
@@ -264,18 +266,50 @@ protected AnnotatedMethod _findFactory(AnnotatedClass cls, String name, Class>
{
final int argCount = argTypes.length;
for (AnnotatedMethod method : cls.getFactoryMethods()) {
- if (!name.equals(method.getName())
- || (method.getParameterCount() != argCount)) {
- continue;
+ if (name.equals(method.getName())
+ && (method.getParameterCount() == argCount)
+ && _allArgTypesMatch(argTypes, method)) {
+ return method;
}
- for (int i = 0; i < argCount; ++i) {
- Class> argType = method.getParameter(i).getRawType();
- if (!argType.isAssignableFrom(argTypes[i])) {
- continue;
+ }
+ return null;
+ }
+
+ // @since 2.21
+ private boolean _allArgTypesMatch(Class>[] expectedArgTypes, AnnotatedMethod method)
+ {
+ for (int i = 0, len = expectedArgTypes.length; i < len; ++i) {
+ Class> argType = method.getParameter(i).getRawType();
+ if (!argType.isAssignableFrom(expectedArgTypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Container for serializers, with one tweak; specific lookup we need to deal
+ * with specific {@code TemporalAdjuster} closure subtypes.
+ *
+ * @since 2.19
+ */
+ @SuppressWarnings("serial")
+ class JavaTimeSerializers extends SimpleSerializers {
+ @Override
+ public JsonSerializer> findSerializer(SerializationConfig config,
+ JavaType type, BeanDescription beanDesc)
+ {
+ JsonSerializer> ser = super.findSerializer(config, type, beanDesc);
+ if (ser == null) {
+ // 27-Jan-2025, tatu: [modules-java8#207] Need actual "ToEmptySerializer"
+ // with Jackson 2.x, to avoid "No Bean Properties found" error
+ // But! Must be careful to only apply to lambdas
+ if (type.isTypeOrSubTypeOf(TemporalAdjuster.class)
+ && type.getRawClass().isSynthetic()) {
+ return new ToEmptyObjectSerializer(type);
}
}
- return method;
+ return ser;
}
- return null;
}
}
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java
index dee6d35f..79a1fc25 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java
@@ -156,22 +156,26 @@ public Duration deserialize(JsonParser parser, DeserializationContext context) t
{
case JsonTokenId.ID_NUMBER_FLOAT:
BigDecimal value = parser.getDecimalValue();
- return DecimalUtils.extractSecondsAndNanos(value, Duration::ofSeconds);
+ // [modules-java8#337] since 2.19, Duration does not need negative adjustment
+ return DecimalUtils.extractSecondsAndNanos(value, Duration::ofSeconds, false);
case JsonTokenId.ID_NUMBER_INT:
return _fromTimestamp(context, parser.getLongValue());
case JsonTokenId.ID_STRING:
return _fromString(parser, context, parser.getText());
- // 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
- case JsonTokenId.ID_START_OBJECT:
- return _fromString(parser, context,
- context.extractScalarFromObject(parser, this, handledType()));
case JsonTokenId.ID_EMBEDDED_OBJECT:
// 20-Apr-2016, tatu: Related to [databind#1208], can try supporting embedded
// values quite easily
return (Duration) parser.getEmbeddedObject();
-
case JsonTokenId.ID_START_ARRAY:
return _deserializeFromArray(parser, context);
+ // 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ // 17-May-2025, tatu: [databind#4656] need to check for `null`
+ String str = context.extractScalarFromObject(parser, this, handledType());
+ if (str != null) {
+ return _fromString(parser, context, str);
+ }
+ // fall through
}
return _handleUnexpectedToken(context, parser, JsonToken.VALUE_STRING,
JsonToken.VALUE_NUMBER_INT, JsonToken.VALUE_NUMBER_FLOAT);
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java
index c80bd5e6..ee50ac63 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java
@@ -200,12 +200,12 @@ protected InstantDeserializer(Class supportedType,
*/
@Deprecated()
protected InstantDeserializer(Class supportedType,
- DateTimeFormatter formatter,
- Function parsedToValue,
- Function fromMilliseconds,
- Function fromNanoseconds,
- BiFunction adjust,
- boolean replaceZeroOffsetAsZ
+ DateTimeFormatter formatter,
+ Function parsedToValue,
+ Function fromMilliseconds,
+ Function fromNanoseconds,
+ BiFunction adjust,
+ boolean replaceZeroOffsetAsZ
) {
this(supportedType, formatter, parsedToValue, fromMilliseconds, fromNanoseconds,
adjust, replaceZeroOffsetAsZ,
@@ -299,8 +299,11 @@ protected InstantDeserializer(InstantDeserializer base,
_alwaysAllowStringifiedDateTimestamps = features.isEnabled(JavaTimeFeature.ALWAYS_ALLOW_STRINGIFIED_DATE_TIMESTAMPS);
}
+ /**
+ * NOTE: {@code public} since 2.21
+ */
@Override
- protected InstantDeserializer withDateFormat(DateTimeFormatter dtf) {
+ public InstantDeserializer withDateFormat(DateTimeFormatter dtf) {
if (dtf == _formatter) {
return this;
}
@@ -355,10 +358,6 @@ public T deserialize(JsonParser parser, DeserializationContext context) throws I
return _fromLong(context, parser.getLongValue());
case JsonTokenId.ID_STRING:
return _fromString(parser, context, parser.getText());
- // 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
- case JsonTokenId.ID_START_OBJECT:
- return _fromString(parser, context,
- context.extractScalarFromObject(parser, this, handledType()));
case JsonTokenId.ID_EMBEDDED_OBJECT:
// 20-Apr-2016, tatu: Related to [databind#1208], can try supporting embedded
// values quite easily
@@ -366,6 +365,14 @@ public T deserialize(JsonParser parser, DeserializationContext context) throws I
case JsonTokenId.ID_START_ARRAY:
return _deserializeFromArray(parser, context);
+ // 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
+ case JsonTokenId.ID_START_OBJECT:
+ // 17-May-2025, tatu: [databind#4656] need to check for `null`
+ String str = context.extractScalarFromObject(parser, this, handledType());
+ if (str != null) {
+ return _fromString(parser, context, str);
+ }
+ // fall through
}
return _handleUnexpectedToken(context, parser, JsonToken.VALUE_STRING,
JsonToken.VALUE_NUMBER_INT, JsonToken.VALUE_NUMBER_FLOAT);
@@ -477,7 +484,10 @@ protected T _fromLong(DeserializationContext context, long timestamp)
protected T _fromDecimal(DeserializationContext context, BigDecimal value)
{
FromDecimalArguments args =
- DecimalUtils.extractSecondsAndNanos(value, (s, ns) -> new FromDecimalArguments(s, ns, getZone(context)));
+ DecimalUtils.extractSecondsAndNanos(value, (s, ns) -> new FromDecimalArguments(s, ns, getZone(context)),
+ // [modules-java8#359] since 2.21, Instant.ofEpochSecond() correctly handles
+ // negative nanoseconds, so no adjustment needed
+ false);
return fromNanoseconds.apply(args);
}
@@ -528,14 +538,42 @@ private static String replaceZeroOffsetAsZ(String text)
}
// @since 2.13
- private String addInColonToOffsetIfMissing(String text)
+ private static String addInColonToOffsetIfMissing(String text)
{
- final Matcher matcher = ISO8601_COLONLESS_OFFSET_REGEX.matcher(text);
- if (matcher.find()){
- StringBuilder sb = new StringBuilder(matcher.group(0));
- sb.insert(3, ":");
+ int timeIndex = text.indexOf('T');
+ if (timeIndex < 0 || timeIndex > text.length() - 1) {
+ return text;
+ }
+
+ int offsetIndex = text.indexOf('+', timeIndex + 1);
+ if (offsetIndex < 0) {
+ offsetIndex = text.indexOf('-', timeIndex + 1);
+ }
+
+ if (offsetIndex < 0 || offsetIndex > text.length() - 5) {
+ return text;
+ }
- return matcher.replaceFirst(sb.toString());
+ int colonIndex = text.indexOf(':', offsetIndex);
+ if (colonIndex == offsetIndex + 3) {
+ return text;
+ }
+
+ if (Character.isDigit(text.charAt(offsetIndex + 1))
+ && Character.isDigit(text.charAt(offsetIndex + 2))
+ && Character.isDigit(text.charAt(offsetIndex + 3))
+ && Character.isDigit(text.charAt(offsetIndex + 4))) {
+ String match = text.substring(offsetIndex, offsetIndex + 5);
+ return text.substring(0, offsetIndex)
+ + match.substring(0, 3) + ':' + match.substring(3)
+ + text.substring(offsetIndex + match.length());
+ }
+
+ // fallback to slow regex path, should be fully handled by the above
+ final Matcher matcher = ISO8601_COLONLESS_OFFSET_REGEX.matcher(text);
+ if (matcher.find()) {
+ String match = matcher.group(0);
+ return matcher.replaceFirst(match.substring(0, 3) + ':' + match.substring(3));
}
return text;
}
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java
index 8ec7efba..68e42954 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java
@@ -27,7 +27,7 @@ public abstract class JSR310DateTimeDeserializerBase
protected final DateTimeFormatter _formatter;
/**
- * Setting that indicates the {@Link JsonFormat.Shape} specified for this deserializer
+ * Setting that indicates the {@link JsonFormat.Shape} specified for this deserializer
* as a {@link com.fasterxml.jackson.annotation.JsonFormat.Shape} annotation on
* property or class, or due to per-type "config override", or from global settings:
* If Shape is NUMBER_INT, the input value is considered to be epoch days. If not a
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java
index 5cb8e465..96596a34 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310StringParsableDeserializer.java
@@ -122,15 +122,17 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
}
// 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
if (p.isExpectedStartObjectToken()) {
- return _fromString(p, ctxt,
- ctxt.extractScalarFromObject(p, this, handledType()));
- }
- if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
+ // 17-May-2025, tatu: [databind#4656] need to check for `null`
+ String str = ctxt.extractScalarFromObject(p, this, handledType());
+ if (str != null) {
+ return _fromString(p, ctxt, str);
+ }
+ // fall through
+ } else if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
// 20-Apr-2016, tatu: Related to [databind#1208], can try supporting embedded
// values quite easily
return p.getEmbeddedObject();
- }
- if (p.isExpectedStartArrayToken()) {
+ } else if (p.isExpectedStartArrayToken()) {
return _deserializeFromArray(p, ctxt);
}
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserializer.java
index dd33ce41..5775a1d4 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserializer.java
@@ -18,16 +18,19 @@
import java.io.IOException;
import java.time.DateTimeException;
+import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature;
/**
* Deserializer for Java 8 temporal {@link LocalDate}s.
@@ -38,16 +41,31 @@ public class LocalDateDeserializer extends JSR310DateTimeDeserializerBase features) {
+ super(LocalDate.class, base._formatter);
+ _useTimeZoneForLenientDateParsing = features.isEnabled(JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING);
}
@Override
@@ -84,62 +113,77 @@ protected LocalDateDeserializer withLeniency(Boolean leniency) {
@Override
protected LocalDateDeserializer withShape(JsonFormat.Shape shape) { return new LocalDateDeserializer(this, shape); }
+ /**
+ * Since 2.19
+ */
+ public LocalDateDeserializer withFeatures(JacksonFeatureSet features) {
+ if (_useTimeZoneForLenientDateParsing ==
+ features.isEnabled(JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING)) {
+ return this;
+ }
+ return new LocalDateDeserializer(this, features);
+ }
+
@Override
- public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException
+ public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- if (parser.hasToken(JsonToken.VALUE_STRING)) {
- return _fromString(parser, context, parser.getText());
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ return _fromString(p, ctxt, p.getText());
}
// 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
- if (parser.isExpectedStartObjectToken()) {
- return _fromString(parser, context,
- context.extractScalarFromObject(parser, this, handledType()));
+ if (p.isExpectedStartObjectToken()) {
+ // 17-May-2025, tatu: [databind#4656] need to check for `null`
+ String str = ctxt.extractScalarFromObject(p, this, handledType());
+ if (str != null) {
+ return _fromString(p, ctxt, str);
+ }
+ return _handleUnexpectedToken(ctxt, p, "Expected array or string");
}
- if (parser.isExpectedStartArrayToken()) {
- JsonToken t = parser.nextToken();
+ if (p.isExpectedStartArrayToken()) {
+ JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
return null;
}
- if (context.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
+ if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
&& (t == JsonToken.VALUE_STRING || t==JsonToken.VALUE_EMBEDDED_OBJECT)) {
- final LocalDate parsed = deserialize(parser, context);
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(parser, context);
+ final LocalDate parsed = deserialize(p, ctxt);
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ handleMissingEndArrayForSingle(p, ctxt);
}
return parsed;
}
if (t == JsonToken.VALUE_NUMBER_INT) {
- int year = parser.getIntValue();
- int month = parser.nextIntValue(-1);
- int day = parser.nextIntValue(-1);
+ int year = p.getIntValue();
+ int month = p.nextIntValue(-1);
+ int day = p.nextIntValue(-1);
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY,
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ throw ctxt.wrongTokenException(p, handledType(), JsonToken.END_ARRAY,
"Expected array to end");
}
return LocalDate.of(year, month, day);
}
- context.reportInputMismatch(handledType(),
+ ctxt.reportInputMismatch(handledType(),
"Unexpected token (%s) within Array, expected VALUE_NUMBER_INT",
t);
}
- if (parser.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
- return (LocalDate) parser.getEmbeddedObject();
+ if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
+ return (LocalDate) p.getEmbeddedObject();
}
// 06-Jan-2018, tatu: Is this actually safe? Do users expect such coercion?
- if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
- CoercionAction act = context.findCoercionAction(logicalType(), _valueClass,
+ if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ CoercionAction act = ctxt.findCoercionAction(logicalType(), _valueClass,
CoercionInputShape.Integer);
- _checkCoercionFail(context, act, handledType(), parser.getLongValue(),
- "Integer value (" + parser.getLongValue() + ")");
+ _checkCoercionFail(ctxt, act, handledType(), p.getLongValue(),
+ "Integer value (" + p.getLongValue() + ")");
// issue 58 - also check for NUMBER_INT, which needs to be specified when serializing.
if (_shape == JsonFormat.Shape.NUMBER_INT || isLenient()) {
- return LocalDate.ofEpochDay(parser.getLongValue());
+ return LocalDate.ofEpochDay(p.getLongValue());
}
- return _failForNotLenient(parser, context, JsonToken.VALUE_STRING);
+ return _failForNotLenient(p, ctxt, JsonToken.VALUE_STRING);
}
- return _handleUnexpectedToken(context, parser, "Expected array or string.");
+ return _handleUnexpectedToken(ctxt, p, "Expected array or string");
}
protected LocalDate _fromString(JsonParser p, DeserializationContext ctxt,
@@ -161,6 +205,9 @@ protected LocalDate _fromString(JsonParser p, DeserializationContext ctxt,
if (string.length() > 10 && string.charAt(10) == 'T') {
if (isLenient()) {
if (string.endsWith("Z")) {
+ if (_useTimeZoneForLenientDateParsing) {
+ return Instant.parse(string).atZone(ctxt.getTimeZone().toZoneId()).toLocalDate();
+ }
return LocalDate.parse(string.substring(0, string.length() - 1),
DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateTimeDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateTimeDeserializer.java
index 79ccc35d..c76fdb0d 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateTimeDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateTimeDeserializer.java
@@ -18,6 +18,7 @@
import java.io.IOException;
import java.time.DateTimeException;
+import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
@@ -27,10 +28,12 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.JsonTokenId;
+import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature;
/**
* Deserializer for Java 8 temporal {@link LocalDateTime}s.
@@ -43,6 +46,9 @@ public class LocalDateTimeDeserializer
{
private static final long serialVersionUID = 1L;
+ private final static boolean DEFAULT_USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING
+ = JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING.enabledByDefault();
+
private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();
@@ -54,6 +60,17 @@ public class LocalDateTimeDeserializer
*/
protected final Boolean _readTimestampsAsNanosOverride;
+ /**
+ * Flag set from
+ * {@link com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature#USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING}
+ * to determine whether the {@link java.util.TimeZone} of the
+ * {@link com.fasterxml.jackson.databind.DeserializationContext} is used
+ * when leniently deserializing from the UTC/ISO instant format.
+ *
+ * @since 2.19
+ */
+ protected final boolean _useTimeZoneForLenientDateParsing;
+
protected LocalDateTimeDeserializer() { // was private before 2.12
this(DEFAULT_FORMATTER);
}
@@ -61,6 +78,7 @@ protected LocalDateTimeDeserializer() { // was private before 2.12
public LocalDateTimeDeserializer(DateTimeFormatter formatter) {
super(LocalDateTime.class, formatter);
_readTimestampsAsNanosOverride = null;
+ _useTimeZoneForLenientDateParsing = DEFAULT_USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING;
}
/**
@@ -69,6 +87,7 @@ public LocalDateTimeDeserializer(DateTimeFormatter formatter) {
protected LocalDateTimeDeserializer(LocalDateTimeDeserializer base, Boolean leniency) {
super(base, leniency);
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
+ _useTimeZoneForLenientDateParsing = base._useTimeZoneForLenientDateParsing;
}
/**
@@ -81,6 +100,16 @@ protected LocalDateTimeDeserializer(LocalDateTimeDeserializer base,
Boolean readTimestampsAsNanosOverride) {
super(base, leniency, formatter, shape);
_readTimestampsAsNanosOverride = readTimestampsAsNanosOverride;
+ _useTimeZoneForLenientDateParsing = base._useTimeZoneForLenientDateParsing;
+ }
+
+ /**
+ * Since 2.19
+ */
+ protected LocalDateTimeDeserializer(LocalDateTimeDeserializer base, JacksonFeatureSet features) {
+ super(LocalDateTime.class, base._formatter);
+ _readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
+ _useTimeZoneForLenientDateParsing = features.isEnabled(JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING);
}
@Override
@@ -107,53 +136,68 @@ protected JSR310DateTimeDeserializerBase> _withFormatOverrides(Deserialization
return deser;
}
+ /**
+ * Since 2.19
+ */
+ public LocalDateTimeDeserializer withFeatures(JacksonFeatureSet features) {
+ if (_useTimeZoneForLenientDateParsing ==
+ features.isEnabled(JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING)) {
+ return this;
+ }
+ return new LocalDateTimeDeserializer(this, features);
+ }
+
@Override
- public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
+ public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- if (parser.hasTokenId(JsonTokenId.ID_STRING)) {
- return _fromString(parser, context, parser.getText());
+ if (p.hasTokenId(JsonTokenId.ID_STRING)) {
+ return _fromString(p, ctxt, p.getText());
}
// 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
- if (parser.isExpectedStartObjectToken()) {
- return _fromString(parser, context,
- context.extractScalarFromObject(parser, this, handledType()));
+ if (p.isExpectedStartObjectToken()) {
+ // 17-May-2025, tatu: [databind#4656] need to check for `null`
+ String str = ctxt.extractScalarFromObject(p, this, handledType());
+ if (str != null) {
+ return _fromString(p, ctxt, str);
+ }
+ return _handleUnexpectedToken(ctxt, p, "Expected array or string");
}
- if (parser.isExpectedStartArrayToken()) {
- JsonToken t = parser.nextToken();
+ if (p.isExpectedStartArrayToken()) {
+ JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
return null;
}
if ((t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT)
- && context.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- final LocalDateTime parsed = deserialize(parser, context);
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(parser, context);
+ && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ final LocalDateTime parsed = deserialize(p, ctxt);
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ handleMissingEndArrayForSingle(p, ctxt);
}
return parsed;
}
if (t == JsonToken.VALUE_NUMBER_INT) {
LocalDateTime result;
- int year = parser.getIntValue();
- int month = parser.nextIntValue(-1);
- int day = parser.nextIntValue(-1);
- int hour = parser.nextIntValue(-1);
- int minute = parser.nextIntValue(-1);
+ int year = p.getIntValue();
+ int month = p.nextIntValue(-1);
+ int day = p.nextIntValue(-1);
+ int hour = p.nextIntValue(-1);
+ int minute = p.nextIntValue(-1);
- t = parser.nextToken();
+ t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
result = LocalDateTime.of(year, month, day, hour, minute);
} else {
- int second = parser.getIntValue();
- t = parser.nextToken();
+ int second = p.getIntValue();
+ t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
result = LocalDateTime.of(year, month, day, hour, minute, second);
} else {
- int partialSecond = parser.getIntValue();
- if (partialSecond < 1_000 && !shouldReadTimestampsAsNanoseconds(context))
+ int partialSecond = p.getIntValue();
+ if (partialSecond < 1_000 && !shouldReadTimestampsAsNanoseconds(ctxt))
partialSecond *= 1_000_000; // value is milliseconds, convert it to nanoseconds
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY,
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ throw ctxt.wrongTokenException(p, handledType(), JsonToken.END_ARRAY,
"Expected array to end");
}
result = LocalDateTime.of(year, month, day, hour, minute, second, partialSecond);
@@ -161,17 +205,17 @@ public LocalDateTime deserialize(JsonParser parser, DeserializationContext conte
}
return result;
}
- context.reportInputMismatch(handledType(),
+ ctxt.reportInputMismatch(handledType(),
"Unexpected token (%s) within Array, expected VALUE_NUMBER_INT",
t);
}
- if (parser.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
- return (LocalDateTime) parser.getEmbeddedObject();
+ if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
+ return (LocalDateTime) p.getEmbeddedObject();
}
- if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
- _throwNoNumericTimestampNeedTimeZone(parser, context);
+ if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ _throwNoNumericTimestampNeedTimeZone(p, ctxt);
}
- return _handleUnexpectedToken(context, parser, "Expected array or string.");
+ return _handleUnexpectedToken(ctxt, p, "Expected array or string");
}
protected boolean shouldReadTimestampsAsNanoseconds(DeserializationContext context) {
@@ -195,11 +239,12 @@ protected LocalDateTime _fromString(JsonParser p, DeserializationContext ctxt,
if (_formatter == DEFAULT_FORMATTER) {
// ... only allow iff lenient mode enabled since
// JavaScript by default includes time and zone in JSON serialized Dates (UTC/ISO instant format).
- // And if so, do NOT use zoned date parsing as that can easily produce
- // incorrect answer.
if (string.length() > 10 && string.charAt(10) == 'T') {
if (string.endsWith("Z")) {
if (isLenient()) {
+ if (_useTimeZoneForLenientDateParsing) {
+ return Instant.parse(string).atZone(ctxt.getTimeZone().toZoneId()).toLocalDateTime();
+ }
return LocalDateTime.parse(string.substring(0, string.length()-1),
_formatter);
}
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserializer.java
index da860cf3..f023be9a 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalTimeDeserializer.java
@@ -103,51 +103,55 @@ protected JSR310DateTimeDeserializerBase> _withFormatOverrides(Deserialization
}
@Override
- public LocalTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
+ public LocalTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- if (parser.hasToken(JsonToken.VALUE_STRING)) {
- return _fromString(parser, context, parser.getText());
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ return _fromString(p, ctxt, p.getText());
}
// 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
- if (parser.isExpectedStartObjectToken()) {
- return _fromString(parser, context,
- context.extractScalarFromObject(parser, this, handledType()));
+ if (p.isExpectedStartObjectToken()) {
+ // 17-May-2025, tatu: [databind#4656] need to check for `null`
+ String str = ctxt.extractScalarFromObject(p, this, handledType());
+ if (str != null) {
+ return _fromString(p, ctxt, str);
+ }
+ return _handleUnexpectedToken(ctxt, p, "Expected array or string");
}
- if (parser.isExpectedStartArrayToken()) {
- JsonToken t = parser.nextToken();
+ if (p.isExpectedStartArrayToken()) {
+ JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
return null;
}
- if (context.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
+ if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
&& (t == JsonToken.VALUE_STRING || t==JsonToken.VALUE_EMBEDDED_OBJECT)) {
- final LocalTime parsed = deserialize(parser, context);
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(parser, context);
+ final LocalTime parsed = deserialize(p, ctxt);
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ handleMissingEndArrayForSingle(p, ctxt);
}
return parsed;
}
if (t == JsonToken.VALUE_NUMBER_INT) {
- int hour = parser.getIntValue();
+ int hour = p.getIntValue();
- parser.nextToken();
- int minute = parser.getIntValue();
+ p.nextToken();
+ int minute = p.getIntValue();
LocalTime result;
- t = parser.nextToken();
+ t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
result = LocalTime.of(hour, minute);
} else {
- int second = parser.getIntValue();
- t = parser.nextToken();
+ int second = p.getIntValue();
+ t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
result = LocalTime.of(hour, minute, second);
} else {
- int partialSecond = parser.getIntValue();
- if(partialSecond < 1_000 && !shouldReadTimestampsAsNanoseconds(context))
+ int partialSecond = p.getIntValue();
+ if(partialSecond < 1_000 && !shouldReadTimestampsAsNanoseconds(ctxt))
partialSecond *= 1_000_000; // value is milliseconds, convert it to nanoseconds
- t = parser.nextToken();
+ t = p.nextToken();
if (t != JsonToken.END_ARRAY) {
- throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY,
+ throw ctxt.wrongTokenException(p, handledType(), JsonToken.END_ARRAY,
"Expected array to end");
}
result = LocalTime.of(hour, minute, second, partialSecond);
@@ -155,17 +159,17 @@ public LocalTime deserialize(JsonParser parser, DeserializationContext context)
}
return result;
}
- context.reportInputMismatch(handledType(),
+ ctxt.reportInputMismatch(handledType(),
"Unexpected token (%s) within Array, expected VALUE_NUMBER_INT",
t);
}
- if (parser.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
- return (LocalTime) parser.getEmbeddedObject();
+ if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
+ return (LocalTime) p.getEmbeddedObject();
}
- if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
- _throwNoNumericTimestampNeedTimeZone(parser, context);
+ if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ _throwNoNumericTimestampNeedTimeZone(p, ctxt);
}
- return _handleUnexpectedToken(context, parser, "Expected array or string.");
+ return _handleUnexpectedToken(ctxt, p, "Expected array or string");
}
protected boolean shouldReadTimestampsAsNanoseconds(DeserializationContext context) {
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserializer.java
index 449cf718..88d71697 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/MonthDayDeserializer.java
@@ -62,50 +62,52 @@ protected MonthDayDeserializer withDateFormat(DateTimeFormatter dtf) {
}
@Override
- public MonthDay deserialize(JsonParser parser, DeserializationContext context) throws IOException
+ public MonthDay deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- if (parser.hasToken(JsonToken.VALUE_STRING)) {
- return _fromString(parser, context, parser.getText());
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ return _fromString(p, ctxt, p.getText());
}
// 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
- if (parser.isExpectedStartObjectToken()) {
- return _fromString(parser, context,
- context.extractScalarFromObject(parser, this, handledType()));
- }
- if (parser.isExpectedStartArrayToken()) {
- JsonToken t = parser.nextToken();
+ if (p.isExpectedStartObjectToken()) {
+ // 17-May-2025, tatu: [databind#4656] need to check for `null`
+ String str = ctxt.extractScalarFromObject(p, this, handledType());
+ if (str != null) {
+ return _fromString(p, ctxt, str);
+ }
+ // fall through
+ } else if (p.isExpectedStartArrayToken()) {
+ JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
return null;
}
if ((t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT)
- && context.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- final MonthDay parsed = deserialize(parser, context);
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(parser, context);
+ && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ final MonthDay parsed = deserialize(p, ctxt);
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ handleMissingEndArrayForSingle(p, ctxt);
}
return parsed;
}
if (t != JsonToken.VALUE_NUMBER_INT) {
- _reportWrongToken(context, JsonToken.VALUE_NUMBER_INT, "month");
+ _reportWrongToken(ctxt, JsonToken.VALUE_NUMBER_INT, "month");
}
- int month = parser.getIntValue();
- int day = parser.nextIntValue(-1);
+ int month = p.getIntValue();
+ int day = p.nextIntValue(-1);
if (day == -1) {
- if (!parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
- _reportWrongToken(context, JsonToken.VALUE_NUMBER_INT, "day");
+ if (!p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ _reportWrongToken(ctxt, JsonToken.VALUE_NUMBER_INT, "day");
}
- day = parser.getIntValue();
+ day = p.getIntValue();
}
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY,
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ throw ctxt.wrongTokenException(p, handledType(), JsonToken.END_ARRAY,
"Expected array to end");
}
return MonthDay.of(month, day);
+ } else if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
+ return (MonthDay) p.getEmbeddedObject();
}
- if (parser.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
- return (MonthDay) parser.getEmbeddedObject();
- }
- return _handleUnexpectedToken(context, parser,
+ return _handleUnexpectedToken(ctxt, p,
JsonToken.VALUE_STRING, JsonToken.START_ARRAY);
}
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserializer.java
index 8331af7f..0a23ac39 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetTimeDeserializer.java
@@ -99,75 +99,79 @@ protected JSR310DateTimeDeserializerBase> _withFormatOverrides(Deserialization
}
@Override
- public OffsetTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
+ public OffsetTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- if (parser.hasToken(JsonToken.VALUE_STRING)) {
- return _fromString(parser, context, parser.getText());
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ return _fromString(p, ctxt, p.getText());
}
// 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
- if (parser.isExpectedStartObjectToken()) {
- return _fromString(parser, context,
- context.extractScalarFromObject(parser, this, handledType()));
+ if (p.isExpectedStartObjectToken()) {
+ // 17-May-2025, tatu: [databind#4656] need to check for `null`
+ String str = ctxt.extractScalarFromObject(p, this, handledType());
+ if (str != null) {
+ return _fromString(p, ctxt, str);
+ }
+ // fall through
}
- if (!parser.isExpectedStartArrayToken()) {
- if (parser.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
- return (OffsetTime) parser.getEmbeddedObject();
+ if (!p.isExpectedStartArrayToken()) {
+ if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
+ return (OffsetTime) p.getEmbeddedObject();
}
- if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
- _throwNoNumericTimestampNeedTimeZone(parser, context);
+ if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ _throwNoNumericTimestampNeedTimeZone(p, ctxt);
}
- throw context.wrongTokenException(parser, handledType(), JsonToken.START_ARRAY,
+ throw ctxt.wrongTokenException(p, handledType(), JsonToken.START_ARRAY,
"Expected array or string.");
}
- JsonToken t = parser.nextToken();
+ JsonToken t = p.nextToken();
if (t != JsonToken.VALUE_NUMBER_INT) {
if (t == JsonToken.END_ARRAY) {
return null;
}
if ((t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT)
- && context.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- final OffsetTime parsed = deserialize(parser, context);
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(parser, context);
+ && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ final OffsetTime parsed = deserialize(p, ctxt);
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ handleMissingEndArrayForSingle(p, ctxt);
}
return parsed;
}
- context.reportInputMismatch(handledType(),
+ ctxt.reportInputMismatch(handledType(),
"Unexpected token (%s) within Array, expected VALUE_NUMBER_INT",
t);
}
- int hour = parser.getIntValue();
- int minute = parser.nextIntValue(-1);
+ int hour = p.getIntValue();
+ int minute = p.nextIntValue(-1);
if (minute == -1) {
- t = parser.getCurrentToken();
+ t = p.getCurrentToken();
if (t == JsonToken.END_ARRAY) {
return null;
}
if (t != JsonToken.VALUE_NUMBER_INT) {
- _reportWrongToken(context, JsonToken.VALUE_NUMBER_INT, "minutes");
+ _reportWrongToken(ctxt, JsonToken.VALUE_NUMBER_INT, "minutes");
}
- minute = parser.getIntValue();
+ minute = p.getIntValue();
}
int partialSecond = 0;
int second = 0;
- if (parser.nextToken() == JsonToken.VALUE_NUMBER_INT) {
- second = parser.getIntValue();
- if (parser.nextToken() == JsonToken.VALUE_NUMBER_INT) {
- partialSecond = parser.getIntValue();
- if (partialSecond < 1_000 && !shouldReadTimestampsAsNanoseconds(context)) {
+ if (p.nextToken() == JsonToken.VALUE_NUMBER_INT) {
+ second = p.getIntValue();
+ if (p.nextToken() == JsonToken.VALUE_NUMBER_INT) {
+ partialSecond = p.getIntValue();
+ if (partialSecond < 1_000 && !shouldReadTimestampsAsNanoseconds(ctxt)) {
partialSecond *= 1_000_000; // value is milliseconds, convert it to nanoseconds
}
- parser.nextToken();
+ p.nextToken();
}
}
- if (parser.getCurrentToken() == JsonToken.VALUE_STRING) {
- OffsetTime result = OffsetTime.of(hour, minute, second, partialSecond, ZoneOffset.of(parser.getText()));
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- _reportWrongToken(context, JsonToken.END_ARRAY, "timezone");
+ if (p.getCurrentToken() == JsonToken.VALUE_STRING) {
+ OffsetTime result = OffsetTime.of(hour, minute, second, partialSecond, ZoneOffset.of(p.getText()));
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ _reportWrongToken(ctxt, JsonToken.END_ARRAY, "timezone");
}
return result;
}
- throw context.wrongTokenException(parser, handledType(), JsonToken.VALUE_STRING,
+ throw ctxt.wrongTokenException(p, handledType(), JsonToken.VALUE_STRING,
"Expected string for TimeZone after numeric values");
}
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OneBasedMonthDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OneBasedMonthDeserializer.java
index ed626849..d6b71680 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OneBasedMonthDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/OneBasedMonthDeserializer.java
@@ -2,10 +2,9 @@
import java.io.IOException;
import java.time.Month;
-import java.util.regex.Pattern;
import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonToken;
+
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.std.DelegatingDeserializer;
@@ -17,31 +16,57 @@
public class OneBasedMonthDeserializer extends DelegatingDeserializer {
private static final long serialVersionUID = 1L;
- private static final Pattern HAS_ONE_OR_TWO_DIGITS = Pattern.compile("^\\d{1,2}$");
-
public OneBasedMonthDeserializer(JsonDeserializer> defaultDeserializer) {
super(defaultDeserializer);
}
@Override
- public Object deserialize(JsonParser parser, DeserializationContext context) throws IOException {
- JsonToken token = parser.currentToken();
- Month zeroBaseMonth = (Month) getDelegatee().deserialize(parser, context);
- if (!_isNumericValue(parser.getText(), token)) {
- return zeroBaseMonth;
- }
- if (zeroBaseMonth == Month.JANUARY) {
- throw new InvalidFormatException(parser, "Month.JANUARY value not allowed for 1-based Month.", zeroBaseMonth, Month.class);
+ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ switch (p.currentToken()) {
+ case VALUE_NUMBER_INT:
+ return _decodeMonth(p.getIntValue(), p);
+ case VALUE_STRING:
+ String monthSpec = p.getText();
+ int oneBasedMonthNumber = _decodeNumber(monthSpec);
+ if (oneBasedMonthNumber >= 0) {
+ return _decodeMonth(oneBasedMonthNumber, p);
+ }
+ // Otherwise fall through to default handling
+ break;
+ default:
}
- return zeroBaseMonth.minus(1);
+ return getDelegatee().deserialize(p, ctxt);
}
- private boolean _isNumericValue(String text, JsonToken token) {
- return token == JsonToken.VALUE_NUMBER_INT || _isNumberAsString(text, token);
+ /**
+ * @return Numeric value of input text that represents a 1-digit or 2-digit number.
+ * Negative value in other cases (empty string, not a number, 3 or more digits).
+ */
+ private int _decodeNumber(String text) {
+ int numValue;
+ switch (text.length()) {
+ case 1:
+ char c = text.charAt(0);
+ boolean cValid = ('0' <= c && c <= '9');
+ numValue = cValid ? (c - '0') : -1;
+ break;
+ case 2:
+ char c1 = text.charAt(0);
+ char c2 = text.charAt(1);
+ boolean c12valid = ('0' <= c1 && c1 <= '9' && '0' <= c2 && c2 <= '9');
+ numValue = c12valid ? (10 * (c1 - '0') + (c2 - '0')) : -1;
+ break;
+ default:
+ numValue = -1;
+ }
+ return numValue;
}
- private boolean _isNumberAsString(String text, JsonToken token) {
- return token == JsonToken.VALUE_STRING && HAS_ONE_OR_TWO_DIGITS.matcher(text).matches();
+ private Month _decodeMonth(int oneBasedMonthNumber, JsonParser parser) throws InvalidFormatException {
+ if (Month.JANUARY.getValue() <= oneBasedMonthNumber && oneBasedMonthNumber <= Month.DECEMBER.getValue()) {
+ return Month.of(oneBasedMonthNumber);
+ }
+ throw new InvalidFormatException(parser, "Month number " + oneBasedMonthNumber + " not allowed for 1-based Month.", oneBasedMonthNumber, Integer.class);
}
@Override
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearDeserializer.java
index d34db205..7e6c3603 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearDeserializer.java
@@ -79,27 +79,28 @@ protected YearDeserializer withLeniency(Boolean leniency) {
}
@Override
- public Year deserialize(JsonParser parser, DeserializationContext context) throws IOException
+ public Year deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- JsonToken t = parser.currentToken();
+ JsonToken t = p.currentToken();
if (t == JsonToken.VALUE_STRING) {
- return _fromString(parser, context, parser.getText());
+ return _fromString(p, ctxt, p.getText());
}
// 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
if (t == JsonToken.START_OBJECT) {
- return _fromString(parser, context,
- context.extractScalarFromObject(parser, this, handledType()));
- }
- if (t == JsonToken.VALUE_NUMBER_INT) {
- return _fromNumber(context, parser.getIntValue());
- }
- if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
- return (Year) parser.getEmbeddedObject();
- }
- if (parser.hasToken(JsonToken.START_ARRAY)){
- return _deserializeFromArray(parser, context);
+ // 17-May-2025, tatu: [databind#4656] need to check for `null`
+ String str = ctxt.extractScalarFromObject(p, this, handledType());
+ if (str != null) {
+ return _fromString(p, ctxt, str);
+ }
+ // fall through
+ } else if (t == JsonToken.VALUE_NUMBER_INT) {
+ return _fromNumber(ctxt, p.getIntValue());
+ } else if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
+ return (Year) p.getEmbeddedObject();
+ } else if (p.isExpectedStartArrayToken()){
+ return _deserializeFromArray(p, ctxt);
}
- return _handleUnexpectedToken(context, parser, JsonToken.VALUE_STRING, JsonToken.VALUE_NUMBER_INT);
+ return _handleUnexpectedToken(ctxt, p, JsonToken.VALUE_STRING, JsonToken.VALUE_NUMBER_INT);
}
protected Year _fromString(JsonParser p, DeserializationContext ctxt,
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserializer.java
index 58709ffa..4114343e 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/YearMonthDeserializer.java
@@ -81,50 +81,52 @@ protected YearMonthDeserializer withLeniency(Boolean leniency) {
}
@Override
- public YearMonth deserialize(JsonParser parser, DeserializationContext context) throws IOException
+ public YearMonth deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
- if (parser.hasToken(JsonToken.VALUE_STRING)) {
- return _fromString(parser, context, parser.getText());
+ if (p.hasToken(JsonToken.VALUE_STRING)) {
+ return _fromString(p, ctxt, p.getText());
}
// 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
- if (parser.isExpectedStartObjectToken()) {
- return _fromString(parser, context,
- context.extractScalarFromObject(parser, this, handledType()));
- }
- if (parser.isExpectedStartArrayToken()) {
- JsonToken t = parser.nextToken();
+ if (p.isExpectedStartObjectToken()) {
+ // 17-May-2025, tatu: [databind#4656] need to check for `null`
+ String str = ctxt.extractScalarFromObject(p, this, handledType());
+ if (str != null) {
+ return _fromString(p, ctxt, str);
+ }
+ // fall through
+ } else if (p.isExpectedStartArrayToken()) {
+ JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) {
return null;
}
if ((t == JsonToken.VALUE_STRING || t == JsonToken.VALUE_EMBEDDED_OBJECT)
- && context.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
- final YearMonth parsed = deserialize(parser, context);
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- handleMissingEndArrayForSingle(parser, context);
+ && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
+ final YearMonth parsed = deserialize(p, ctxt);
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ handleMissingEndArrayForSingle(p, ctxt);
}
return parsed;
}
if (t != JsonToken.VALUE_NUMBER_INT) {
- _reportWrongToken(context, JsonToken.VALUE_NUMBER_INT, "years");
+ _reportWrongToken(ctxt, JsonToken.VALUE_NUMBER_INT, "years");
}
- int year = parser.getIntValue();
- int month = parser.nextIntValue(-1);
+ int year = p.getIntValue();
+ int month = p.nextIntValue(-1);
if (month == -1) {
- if (!parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
- _reportWrongToken(context, JsonToken.VALUE_NUMBER_INT, "months");
+ if (!p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
+ _reportWrongToken(ctxt, JsonToken.VALUE_NUMBER_INT, "months");
}
- month = parser.getIntValue();
+ month = p.getIntValue();
}
- if (parser.nextToken() != JsonToken.END_ARRAY) {
- throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY,
+ if (p.nextToken() != JsonToken.END_ARRAY) {
+ throw ctxt.wrongTokenException(p, handledType(), JsonToken.END_ARRAY,
"Expected array to end");
}
return YearMonth.of(year, month);
+ } else if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
+ return (YearMonth) p.getEmbeddedObject();
}
- if (parser.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
- return (YearMonth) parser.getEmbeddedObject();
- }
- return _handleUnexpectedToken(context, parser,
+ return _handleUnexpectedToken(ctxt, p,
JsonToken.VALUE_STRING, JsonToken.START_ARRAY);
}
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializer.java
index 0ed65bcd..0340d177 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializer.java
@@ -22,6 +22,10 @@
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
+import com.fasterxml.jackson.core.util.JacksonFeatureSet;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature;
+
/**
* Serializer for Java 8 temporal {@link Instant}s, {@link OffsetDateTime}, and {@link ZonedDateTime}s.
*
@@ -34,10 +38,20 @@ public class InstantSerializer extends InstantSerializerBase
public static final InstantSerializer INSTANCE = new InstantSerializer();
+ /**
+ * Whether {@link com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature#ALWAYS_WRITE_SUBSECOND_DIGITS}
+ * is enabled: if so, the default representation is padded to at least 3 sub-second
+ * digits.
+ *
+ * @since 2.23
+ */
+ private final boolean _alwaysWriteSubsecondDigits;
+
protected InstantSerializer() {
super(Instant.class, Instant::toEpochMilli, Instant::getEpochSecond, Instant::getNano,
// null -> use 'value.toString()', default format
null);
+ _alwaysWriteSubsecondDigits = false;
}
@Deprecated // since 2.14
@@ -52,11 +66,63 @@ protected InstantSerializer(InstantSerializer base,
protected InstantSerializer(InstantSerializer base, Boolean useTimestamp,
DateTimeFormatter formatter, JsonFormat.Shape shape) {
super(base, useTimestamp, base._useNanoseconds, formatter, shape);
+ _alwaysWriteSubsecondDigits = base._alwaysWriteSubsecondDigits;
}
protected InstantSerializer(InstantSerializer base,
Boolean useTimestamp, Boolean useNanoseconds, DateTimeFormatter formatter) {
super(base, useTimestamp, useNanoseconds, formatter);
+ _alwaysWriteSubsecondDigits = base._alwaysWriteSubsecondDigits;
+ }
+
+ /**
+ * @since 2.23
+ */
+ protected InstantSerializer(InstantSerializer base, boolean alwaysWriteSubsecondDigits) {
+ super(base, base._useTimestamp, base._useNanoseconds, base._formatter, base._shape);
+ _alwaysWriteSubsecondDigits = alwaysWriteSubsecondDigits;
+ }
+
+ /**
+ * Method called by {@link com.fasterxml.jackson.datatype.jsr310.JavaTimeModule}
+ * to apply module-level {@link JavaTimeFeature} settings.
+ *
+ * @since 2.23
+ */
+ public InstantSerializer withFeatures(JacksonFeatureSet features) {
+ if (features.isEnabled(JavaTimeFeature.ALWAYS_WRITE_SUBSECOND_DIGITS)) {
+ return new InstantSerializer(this, true);
+ }
+ return this;
+ }
+
+ /**
+ * Overridden to implement
+ * {@link com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature#ALWAYS_WRITE_SUBSECOND_DIGITS}
+ * by padding the default representation, instead of swapping in a different formatter.
+ *
+ * Rationale: the default representation is {@link Instant#toString()}, that is,
+ * {@link DateTimeFormatter#ISO_INSTANT}, which writes exactly 0, 3, 6 or 9 sub-second
+ * digits -- so the only case needing a fix is the zero one. Formatting through a
+ * zone-bound {@code DateTimeFormatter} instead would resolve the value via
+ * {@link java.time.LocalDateTime}, whose year range is narrower than that of
+ * {@code Instant}, and would thereby fail for {@link Instant#MIN} / {@link Instant#MAX}.
+ *
+ * @since 2.23
+ */
+ @Override
+ protected String formatValue(Instant value, SerializerProvider provider)
+ {
+ String formatted = super.formatValue(value, provider);
+ // Only applies to the default representation: an explicit formatter wins
+ if (_alwaysWriteSubsecondDigits && (_formatter == null) && (value.getNano() == 0)) {
+ final int last = formatted.length() - 1;
+ // Defensive: `ISO_INSTANT` always ends in 'Z', but do not corrupt output if not
+ if ((last >= 0) && (formatted.charAt(last) == 'Z')) {
+ formatted = formatted.substring(0, last) + ".000Z";
+ }
+ }
+ return formatted;
}
@Override
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java
index 1305a1c2..81c3ebd8 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java
@@ -47,8 +47,6 @@
public abstract class InstantSerializerBase
extends JSR310FormattedSerializerBase
{
- private final DateTimeFormatter defaultFormat;
-
private final ToLongFunction getEpochMillis;
private final ToLongFunction getEpochSeconds;
@@ -61,8 +59,7 @@ protected InstantSerializerBase(Class supportedType, ToLongFunction getEpo
{
// Bit complicated, just because we actually want to "hide" default formatter,
// so that it won't accidentally force use of textual presentation
- super(supportedType, null);
- this.defaultFormat = defaultFormat;
+ super(supportedType, null, defaultFormat);
this.getEpochMillis = getEpochMillis;
this.getEpochSeconds = getEpochSeconds;
this.getNanoseconds = getNanoseconds;
@@ -86,7 +83,18 @@ protected InstantSerializerBase(InstantSerializerBase base,
protected InstantSerializerBase(InstantSerializerBase base, Boolean useTimestamp,
Boolean useNanoseconds, DateTimeFormatter dtf, JsonFormat.Shape shape) {
super(base, useTimestamp, useNanoseconds, dtf, shape);
- defaultFormat = base.defaultFormat;
+ getEpochMillis = base.getEpochMillis;
+ getEpochSeconds = base.getEpochSeconds;
+ getNanoseconds = base.getNanoseconds;
+ }
+
+ /**
+ * @since 2.23
+ */
+ protected InstantSerializerBase(InstantSerializerBase base,
+ DateTimeFormatter defaultFormat)
+ {
+ super(base, defaultFormat);
getEpochMillis = base.getEpochMillis;
getEpochSeconds = base.getEpochSeconds;
getNanoseconds = base.getNanoseconds;
@@ -147,7 +155,7 @@ protected JsonToken serializationShape(SerializerProvider provider) {
// @since 2.12
protected String formatValue(T value, SerializerProvider provider)
{
- DateTimeFormatter formatter = (_formatter == null) ? defaultFormat :_formatter;
+ DateTimeFormatter formatter = (_formatter == null) ? _defaultFormat :_formatter;
if (formatter != null) {
if (formatter.getZone() == null) { // timezone set if annotated on property
// If the user specified to use the context TimeZone explicitly, and the formatter provided doesn't contain a TZ
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/JSR310FormattedSerializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/JSR310FormattedSerializerBase.java
index 870b0268..b17bd3f3 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/JSR310FormattedSerializerBase.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/JSR310FormattedSerializerBase.java
@@ -65,6 +65,18 @@ abstract class JSR310FormattedSerializerBase
*/
protected final DateTimeFormatter _formatter;
+ /**
+ * Format to use when no explicit {@link #_formatter} is configured. Unlike
+ * {@code _formatter}, a non-null value here does NOT force serialization as a
+ * JSON String -- which is exactly why the two cannot be collapsed into one.
+ *
+ * May be {@code null}, in which case the sub-class decides the fallback
+ * (typically either a JDK {@code ISO_*} constant or {@code value.toString()}).
+ *
+ * @since 2.23
+ */
+ protected final DateTimeFormatter _defaultFormat;
+
protected final JsonFormat.Shape _shape;
/**
@@ -81,13 +93,22 @@ protected JSR310FormattedSerializerBase(Class supportedType) {
protected JSR310FormattedSerializerBase(Class supportedType,
DateTimeFormatter formatter) {
+ this(supportedType, formatter, null);
+ }
+
+ /**
+ * @since 2.23
+ */
+ protected JSR310FormattedSerializerBase(Class supportedType,
+ DateTimeFormatter formatter, DateTimeFormatter defaultFormat) {
super(supportedType);
_useTimestamp = null;
_useNanoseconds = null;
_shape = null;
_formatter = formatter;
+ _defaultFormat = defaultFormat;
}
-
+
protected JSR310FormattedSerializerBase(JSR310FormattedSerializerBase> base,
Boolean useTimestamp, DateTimeFormatter dtf, JsonFormat.Shape shape)
{
@@ -103,6 +124,27 @@ protected JSR310FormattedSerializerBase(JSR310FormattedSerializerBase> base,
_useNanoseconds = useNanoseconds;
_formatter = dtf;
_shape = shape;
+ _defaultFormat = base._defaultFormat;
+ }
+
+ /**
+ * Copy-constructor used for replacing the default format -- and only that --
+ * of an existing serializer; needed for
+ * {@link com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature#ALWAYS_WRITE_SUBSECOND_DIGITS}.
+ * Note that the replacement must NOT be passed as {@code _formatter}, since a
+ * non-null {@code _formatter} also forces serialization as a JSON String.
+ *
+ * @since 2.23
+ */
+ protected JSR310FormattedSerializerBase(JSR310FormattedSerializerBase> base,
+ DateTimeFormatter defaultFormat)
+ {
+ super(base.handledType());
+ _useTimestamp = base._useTimestamp;
+ _useNanoseconds = base._useNanoseconds;
+ _formatter = base._formatter;
+ _shape = base._shape;
+ _defaultFormat = defaultFormat;
}
protected abstract JSR310FormattedSerializerBase> withFormat(Boolean useTimestamp,
@@ -219,7 +261,7 @@ protected SerializationFeature getTimestampsFeature() {
return SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
}
- protected boolean useTimestamp(SerializerProvider provider) {
+ protected boolean useTimestamp(SerializerProvider ctxt) {
if (_useTimestamp != null) {
return _useTimestamp.booleanValue();
}
@@ -232,8 +274,13 @@ protected boolean useTimestamp(SerializerProvider provider) {
}
}
// assume that explicit formatter definition implies use of textual format
- return (_formatter == null) && (provider != null)
- && provider.isEnabled(getTimestampsFeature());
+ return (_formatter == null) && useTimestampFromGlobalDefaults(ctxt);
+ }
+
+ // @since 2.19
+ protected boolean useTimestampFromGlobalDefaults(SerializerProvider ctxt) {
+ return (ctxt != null)
+ && ctxt.isEnabled(getTimestampsFeature());
}
protected boolean _useTimestampExplicitOnly(SerializerProvider provider) {
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/LocalDateTimeSerializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/LocalDateTimeSerializer.java
index 4383a36a..f22bbf9d 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/LocalDateTimeSerializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/LocalDateTimeSerializer.java
@@ -25,8 +25,10 @@
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.WritableTypeId;
+import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature;
/**
* Serializer for Java 8 temporal {@link LocalDateTime}s.
@@ -39,13 +41,13 @@ public class LocalDateTimeSerializer extends JSR310FormattedSerializerBase features) {
+ if (features.isEnabled(JavaTimeFeature.ALWAYS_WRITE_SUBSECOND_DIGITS)) {
+ return new LocalDateTimeSerializer(this, SubSecondFormatters.LOCAL_DATE_TIME);
+ }
+ return this;
+ }
+
@Override
protected JSR310FormattedSerializerBase withFormat(Boolean useTimestamp, DateTimeFormatter f, JsonFormat.Shape shape) {
return new LocalDateTimeSerializer(this, useTimestamp, _useNanoseconds, f);
}
protected DateTimeFormatter _defaultFormatter() {
- return DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+ return _defaultFormat;
}
@Override
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/OffsetDateTimeSerializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/OffsetDateTimeSerializer.java
index ff7bd4c7..7bdd0e9c 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/OffsetDateTimeSerializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/OffsetDateTimeSerializer.java
@@ -4,6 +4,9 @@
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
+import com.fasterxml.jackson.core.util.JacksonFeatureSet;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature;
+
public class OffsetDateTimeSerializer extends InstantSerializerBase
{
private static final long serialVersionUID = 1L;
@@ -35,6 +38,49 @@ public OffsetDateTimeSerializer(OffsetDateTimeSerializer base, Boolean useTimest
super(base, useTimestamp, base._useNanoseconds, formatter, shape);
}
+ /**
+ * @since 2.23
+ */
+ protected OffsetDateTimeSerializer(OffsetDateTimeSerializer base,
+ DateTimeFormatter defaultFormat) {
+ super(base, defaultFormat);
+ }
+
+ /**
+ * Method called by {@link com.fasterxml.jackson.datatype.jsr310.JavaTimeModule}
+ * to apply module-level {@link JavaTimeFeature} settings.
+ *
+ * @since 2.23
+ */
+ public OffsetDateTimeSerializer withFeatures(JacksonFeatureSet features) {
+ if (features.isEnabled(JavaTimeFeature.ALWAYS_WRITE_SUBSECOND_DIGITS)) {
+ return new OffsetDateTimeSerializer(this, SubSecondFormatters.OFFSET_DATE_TIME);
+ }
+ return this;
+ }
+
+ /**
+ * Method for constructing a new {@code OffsetDateTimeSerializer} with settings
+ * of this serializer but with custom {@link DateTimeFormatter} overrides.
+ * Commonly used on {@code INSTANCE} like so:
+ *
+ * DateTimeFormatter dtf = new DateTimeFormatterBuilder()
+ * .append(DateTimeFormatter.ISO_LOCAL_DATE)
+ * .appendLiteral('T')
+ * // and so on
+ * .toFormatter();
+ * OffsetDateTimeSerializer ser = OffsetDateTimeSerializer.INSTANCE
+ * .withFormatter(dtf);
+ * // register via Module
+ *
+ *
+ * @since 2.21
+ */
+ public OffsetDateTimeSerializer withFormatter(DateTimeFormatter formatter)
+ {
+ return new OffsetDateTimeSerializer(this, _useTimestamp, formatter, _shape);
+ }
+
@Override
protected JSR310FormattedSerializerBase> withFormat(Boolean useTimestamp,
DateTimeFormatter formatter, JsonFormat.Shape shape)
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/OffsetTimeSerializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/OffsetTimeSerializer.java
index f1dec8b9..30fc1037 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/OffsetTimeSerializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/OffsetTimeSerializer.java
@@ -89,7 +89,7 @@ public void serializeWithType(OffsetTime value, JsonGenerator g, SerializerProvi
typeSer.writeTypeSuffix(g, typeIdDef);
}
- private final void _serializeAsArrayContents(OffsetTime value, JsonGenerator g,
+ protected void _serializeAsArrayContents(OffsetTime value, JsonGenerator g,
SerializerProvider provider) throws IOException
{
g.writeNumber(value.getHour());
@@ -99,7 +99,7 @@ private final void _serializeAsArrayContents(OffsetTime value, JsonGenerator g,
if ((secs > 0) || (nanos > 0)) {
g.writeNumber(secs);
if (nanos > 0) {
- if(useNanoseconds(provider)) {
+ if (useNanoseconds(provider)) {
g.writeNumber(nanos);
} else {
g.writeNumber(value.get(ChronoField.MILLI_OF_SECOND));
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/SubSecondFormatters.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/SubSecondFormatters.java
new file mode 100644
index 00000000..825bf845
--- /dev/null
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/SubSecondFormatters.java
@@ -0,0 +1,91 @@
+package com.fasterxml.jackson.datatype.jsr310.ser;
+
+import java.time.chrono.IsoChronology;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.ResolverStyle;
+import java.time.temporal.ChronoField;
+
+/**
+ * Container for the ISO-8601 {@link DateTimeFormatter}s used in place of the
+ * JDK-provided defaults when
+ * {@link com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature#ALWAYS_WRITE_SUBSECOND_DIGITS}
+ * is enabled.
+ *
+ * These differ from the JDK counterparts only in the sub-second field: instead of
+ * omitting it when zero, at least 3 (millisecond) digits are always written, and up
+ * to 9 when the value carries higher precision (so nothing is truncated). They are
+ * otherwise built the same way -- including {@link ResolverStyle#STRICT} and the ISO
+ * chronology -- so that they remain drop-in replacements even though they are only
+ * ever used for printing here.
+ *
+ * Note that there is deliberately no counterpart of {@link DateTimeFormatter#ISO_INSTANT}
+ * here: formatting an {@link java.time.Instant} through a zone-bound formatter goes via
+ * {@link java.time.LocalDateTime}, whose year range is narrower than {@code Instant}'s,
+ * so {@link java.time.Instant#MIN} / {@link java.time.Instant#MAX} would fail. See
+ * {@link InstantSerializer#formatValue} for the handling used instead.
+ *
+ * @since 2.23
+ */
+class SubSecondFormatters
+{
+ private SubSecondFormatters() { }
+
+ /**
+ * Date and time down to the seconds field, followed by 3 to 9 sub-second digits:
+ * the shared prefix of all formatters here.
+ */
+ private static DateTimeFormatterBuilder _localDateTimeBuilder() {
+ return new DateTimeFormatterBuilder()
+ .append(DateTimeFormatter.ISO_LOCAL_DATE)
+ .appendLiteral('T')
+ .appendValue(ChronoField.HOUR_OF_DAY, 2)
+ .appendLiteral(':')
+ .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
+ .appendLiteral(':')
+ .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
+ .appendFraction(ChronoField.NANO_OF_SECOND, 3, 9, true);
+ }
+
+ /**
+ * {@link #_localDateTimeBuilder()} followed by the offset: the shared prefix of the
+ * offset- and zone-based formatters.
+ */
+ private static DateTimeFormatterBuilder _offsetDateTimeBuilder() {
+ return _localDateTimeBuilder().appendOffsetId();
+ }
+
+ /**
+ * Completes a builder the way the JDK completes its own {@code ISO_*} constants.
+ *
+ * Note that {@link DateTimeFormatterBuilder#toFormatter()} alone would yield
+ * {@link ResolverStyle#SMART} and no chronology, which is not what the constants
+ * these replace do.
+ */
+ private static DateTimeFormatter _isoFormatter(DateTimeFormatterBuilder b) {
+ return b.toFormatter()
+ .withResolverStyle(ResolverStyle.STRICT)
+ .withChronology(IsoChronology.INSTANCE);
+ }
+
+ /**
+ * Counterpart of {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME}.
+ */
+ final static DateTimeFormatter LOCAL_DATE_TIME = _isoFormatter(_localDateTimeBuilder());
+
+ /**
+ * Counterpart of {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME}.
+ */
+ final static DateTimeFormatter OFFSET_DATE_TIME = _isoFormatter(_offsetDateTimeBuilder());
+
+ /**
+ * Counterpart of {@link DateTimeFormatter#ISO_ZONED_DATE_TIME}, that is,
+ * {@link #OFFSET_DATE_TIME} with the optional {@code [Zone/Id]} suffix.
+ */
+ final static DateTimeFormatter ZONED_DATE_TIME = _isoFormatter(_offsetDateTimeBuilder()
+ .optionalStart()
+ .appendLiteral('[')
+ .parseCaseSensitive()
+ .appendZoneRegionId()
+ .appendLiteral(']'));
+}
diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/ZonedDateTimeSerializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/ZonedDateTimeSerializer.java
index f84f950b..58d0817c 100644
--- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/ZonedDateTimeSerializer.java
+++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/ZonedDateTimeSerializer.java
@@ -7,8 +7,10 @@
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature;
public class ZonedDateTimeSerializer extends InstantSerializerBase {
private static final long serialVersionUID = 1L;
@@ -21,7 +23,21 @@ public class ZonedDateTimeSerializer extends InstantSerializerBase
+ * Separate from the inherited {@code _defaultFormat} on purpose: writing with the
+ * zone id is a second output shape that has always used {@code ISO_ZONED_DATE_TIME}
+ * regardless of the default format (including a custom one passed to
+ * {@link #ZonedDateTimeSerializer(DateTimeFormatter)}), so the two cannot be
+ * collapsed without changing existing behaviour.
+ *
+ * @since 2.23
+ */
+ protected final DateTimeFormatter _zoneIdFormat;
+
protected ZonedDateTimeSerializer() {
// ISO_ZONED_DATE_TIME is an extended version of ISO compliant format
// ISO_OFFSET_DATE_TIME with additional information :Zone Id
@@ -34,6 +50,7 @@ public ZonedDateTimeSerializer(DateTimeFormatter formatter) {
ZonedDateTime::toEpochSecond, ZonedDateTime::getNano,
formatter);
_writeZoneId = null;
+ _zoneIdFormat = null;
}
protected ZonedDateTimeSerializer(ZonedDateTimeSerializer base,
@@ -56,6 +73,31 @@ protected ZonedDateTimeSerializer(ZonedDateTimeSerializer base,
JsonFormat.Shape shape, Boolean writeZoneId) {
super(base, useTimestamp, useNanoseconds, formatter, shape);
_writeZoneId = writeZoneId;
+ _zoneIdFormat = base._zoneIdFormat;
+ }
+
+ /**
+ * @since 2.23
+ */
+ protected ZonedDateTimeSerializer(ZonedDateTimeSerializer base,
+ DateTimeFormatter defaultFormat, DateTimeFormatter zoneIdFormat) {
+ super(base, defaultFormat);
+ _writeZoneId = base._writeZoneId;
+ _zoneIdFormat = zoneIdFormat;
+ }
+
+ /**
+ * Method called by {@link com.fasterxml.jackson.datatype.jsr310.JavaTimeModule}
+ * to apply module-level {@link JavaTimeFeature} settings.
+ *
+ * @since 2.23
+ */
+ public ZonedDateTimeSerializer withFeatures(JacksonFeatureSet features) {
+ if (features.isEnabled(JavaTimeFeature.ALWAYS_WRITE_SUBSECOND_DIGITS)) {
+ return new ZonedDateTimeSerializer(this,
+ SubSecondFormatters.OFFSET_DATE_TIME, SubSecondFormatters.ZONED_DATE_TIME);
+ }
+ return this;
}
@Override
@@ -89,7 +131,7 @@ public void serialize(ZonedDateTime value, JsonGenerator g, SerializerProvider p
; // use default handling
} else if (shouldWriteWithZoneId(provider)) {
// write with zone
- g.writeString(DateTimeFormatter.ISO_ZONED_DATE_TIME.format(value));
+ g.writeString(_zoneIdFormatter().format(value));
return;
}
}
@@ -110,6 +152,13 @@ protected String formatValue(ZonedDateTime value, SerializerProvider provider) {
return formatted;
}
+ /**
+ * @since 2.23
+ */
+ protected DateTimeFormatter _zoneIdFormatter() {
+ return (_zoneIdFormat == null) ? DateTimeFormatter.ISO_ZONED_DATE_TIME : _zoneIdFormat;
+ }
+
/**
* @since 2.8
*/
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java
index 1ed01eb9..272ec0c4 100644
--- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java
@@ -12,6 +12,9 @@
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
public class ModuleTestBase
{
protected static final ZoneId UTC = ZoneId.of("UTC");
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDecimalUtils.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDecimalUtils.java
index 5b9be3dc..b961fb39 100644
--- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDecimalUtils.java
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDecimalUtils.java
@@ -1,10 +1,12 @@
package com.fasterxml.jackson.datatype.jsr310;
-import org.junit.Test;
-
import java.math.BigDecimal;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
public class TestDecimalUtils extends ModuleTestBase
{
@@ -12,87 +14,34 @@ public class TestDecimalUtils extends ModuleTestBase
public void testToDecimal01()
{
String decimal = DecimalUtils.toDecimal(0, 0);
- assertEquals("The returned decimal is not correct.", NO_NANOSECS_SER, decimal);
+ assertEquals(NO_NANOSECS_SER, decimal, "The returned decimal is not correct.");
decimal = DecimalUtils.toDecimal(15, 72);
- assertEquals("The returned decimal is not correct.", "15.000000072", decimal);
+ assertEquals("15.000000072", decimal, "The returned decimal is not correct.");
decimal = DecimalUtils.toDecimal(19827342231L, 192837465);
- assertEquals("The returned decimal is not correct.", "19827342231.192837465", decimal);
+ assertEquals("19827342231.192837465", decimal, "The returned decimal is not correct.");
decimal = DecimalUtils.toDecimal(19827342231L, 0);
- assertEquals("The returned decimal is not correct.",
- "19827342231"+NO_NANOSECS_SUFFIX, decimal);
+ assertEquals("19827342231"+NO_NANOSECS_SUFFIX, decimal,
+ "The returned decimal is not correct.");
decimal = DecimalUtils.toDecimal(19827342231L, 999888000);
- assertEquals("The returned decimal is not correct.",
- "19827342231.999888000", decimal);
+ assertEquals("19827342231.999888000", decimal,
+ "The returned decimal is not correct.");
decimal = DecimalUtils.toDecimal(-22704862, 599000000);
- assertEquals("The returned decimal is not correct.",
- "-22704862.599000000", decimal);
- }
-
- @SuppressWarnings("deprecation")
- private void checkExtractNanos(long expectedSeconds, int expectedNanos, BigDecimal decimal)
- {
- long seconds = decimal.longValue();
- assertEquals("The second part is not correct.", expectedSeconds, seconds);
-
- int nanoseconds = DecimalUtils.extractNanosecondDecimal(decimal, seconds);
- assertEquals("The nanosecond part is not correct.", expectedNanos, nanoseconds);
- }
-
- @Test
- public void testExtractNanosecondDecimal01()
- {
- BigDecimal value = new BigDecimal("0");
- checkExtractNanos(0L, 0, value);
- }
-
- @Test
- public void testExtractNanosecondDecimal02()
- {
- BigDecimal value = new BigDecimal("15.000000072");
- checkExtractNanos(15L, 72, value);
- }
-
- @Test
- public void testExtractNanosecondDecimal03()
- {
- BigDecimal value = new BigDecimal("15.72");
- checkExtractNanos(15L, 720000000, value);
- }
-
- @Test
- public void testExtractNanosecondDecimal04()
- {
- BigDecimal value = new BigDecimal("19827342231.192837465");
- checkExtractNanos(19827342231L, 192837465, value);
+ assertEquals("-22704862.599000000", decimal,
+ "The returned decimal is not correct.");
}
- @Test
- public void testExtractNanosecondDecimal05()
- {
- BigDecimal value = new BigDecimal("19827342231");
- checkExtractNanos(19827342231L, 0, value);
- }
-
- @Test
- public void testExtractNanosecondDecimal06()
- {
- BigDecimal value = new BigDecimal("19827342231.999999999");
- checkExtractNanos(19827342231L, 999999999, value);
- }
-
-
private void checkExtractSecondsAndNanos(long expectedSeconds, int expectedNanos, BigDecimal decimal)
{
DecimalUtils.extractSecondsAndNanos(decimal, (Long s, Integer ns) -> {
- assertEquals("The second part is not correct.", expectedSeconds, s.longValue());
- assertEquals("The nanosecond part is not correct.", expectedNanos, ns.intValue());
+ assertEquals(expectedSeconds, s.longValue(), "The second part is not correct.");
+ assertEquals(expectedNanos, ns.intValue(), "The nanosecond part is not correct.");
return null;
- });
+ }, true);
}
@Test
@@ -144,7 +93,8 @@ public void testExtractSecondsAndNanosFromNegativeBigDecimal()
checkExtractSecondsAndNanos(-22704862L, 599000000, value);
}
- @Test(timeout = 100)
+ @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
+ @Test
public void testExtractSecondsAndNanos07()
{
BigDecimal value = new BigDecimal("1e10000000");
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestFeatures.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestFeatures.java
index 7b1952a9..a327a8c5 100644
--- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestFeatures.java
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestFeatures.java
@@ -16,32 +16,33 @@
package com.fasterxml.jackson.datatype.jsr310;
+import org.junit.jupiter.api.Test;
+
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
-import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
public class TestFeatures
{
@Test
public void testWriteDateTimestampsAsNanosecondsSettingEnabledByDefault()
{
- assertTrue("Write date timestamps as nanoseconds setting should be enabled by default.",
- SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS.enabledByDefault());
+ assertTrue(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS.enabledByDefault(),
+ "Write date timestamps as nanoseconds setting should be enabled by default.");
}
@Test
public void testReadDateTimestampsAsNanosecondsSettingEnabledByDefault()
{
- assertTrue("Read date timestamps as nanoseconds setting should be enabled by default.",
- DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS.enabledByDefault());
+ assertTrue(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS.enabledByDefault(),
+ "Read date timestamps as nanoseconds setting should be enabled by default.");
}
@Test
public void testAdjustDatesToContextTimeZoneSettingEnabledByDefault()
{
- assertTrue("Adjust dates to context time zone setting should be enabled by default.",
- DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE.enabledByDefault());
+ assertTrue(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE.enabledByDefault(),
+ "Adjust dates to context time zone setting should be enabled by default.");
}
}
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DefaultTypingTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DefaultTypingTest.java
index 17bf71e5..ffdb92f1 100644
--- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DefaultTypingTest.java
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DefaultTypingTest.java
@@ -2,13 +2,13 @@
import java.time.ZoneId;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
+import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
+import static org.junit.jupiter.api.Assertions.*;
+
public class DefaultTypingTest extends ModuleTestBase
{
private final ObjectMapper TYPING_MAPPER = newMapper();
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/failing/DurationDeser337Test.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeser337Test.java
similarity index 92%
rename from datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/failing/DurationDeser337Test.java
rename to datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeser337Test.java
index 94f1f0f1..6787935e 100644
--- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/failing/DurationDeser337Test.java
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeser337Test.java
@@ -1,14 +1,14 @@
-package com.fasterxml.jackson.datatype.jsr310.failing;
+package com.fasterxml.jackson.datatype.jsr310.deser;
import java.time.Duration;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
public class DurationDeser337Test extends ModuleTestBase
{
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java
index 9e891477..2be9a3c1 100644
--- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserTest.java
@@ -5,6 +5,10 @@
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAmount;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Feature;
@@ -17,12 +21,8 @@
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration;
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
-import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.*;
public class DurationDeserTest extends ModuleTestBase
{
@@ -63,7 +63,7 @@ public void testDeserializationAsFloat01() throws Exception
{
Duration value = READER.with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.readValue("60.0");
- assertEquals("The value is not correct.", Duration.ofSeconds(60L, 0), value);
+ assertEquals(Duration.ofSeconds(60L, 0), value, "The value is not correct.");
}
@Test
@@ -71,7 +71,7 @@ public void testDeserializationAsFloat02() throws Exception
{
Duration value = READER.without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.readValue("60.0");
- assertEquals("The value is not correct.", Duration.ofSeconds(60L, 0), value);
+ assertEquals(Duration.ofSeconds(60L, 0), value, "The value is not correct.");
}
@Test
@@ -79,7 +79,7 @@ public void testDeserializationAsFloat03() throws Exception
{
Duration value = READER.with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.readValue("13498.000008374");
- assertEquals("The value is not correct.", Duration.ofSeconds(13498L, 8374), value);
+ assertEquals(Duration.ofSeconds(13498L, 8374), value, "The value is not correct.");
}
@Test
@@ -87,7 +87,7 @@ public void testDeserializationAsFloat04() throws Exception
{
Duration value = READER.without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.readValue("13498.000008374");
- assertEquals("The value is not correct.", Duration.ofSeconds(13498L, 8374), value);
+ assertEquals(Duration.ofSeconds(13498L, 8374), value, "The value is not correct.");
}
/**
@@ -116,12 +116,14 @@ public void testDeserializationAsFloatEdgeCase02() throws Exception
assertEquals(0, value.getNano());
}
- @Test(expected = ArithmeticException.class)
+ @Test
public void testDeserializationAsFloatEdgeCase03() throws Exception
{
// Duration can't go this low
- READER.without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
- .readValue(Long.MIN_VALUE + ".1");
+ assertThrows(ArithmeticException.class, () -> {
+ READER.without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
+ .readValue(Long.MIN_VALUE + ".1");
+ });
}
/*
@@ -174,7 +176,8 @@ public void testDeserializationAsFloatEdgeCase07() throws Exception
* Numbers with very large exponents can take a long time, but still result in zero.
* https://github.com/FasterXML/jackson-databind/issues/2141
*/
- @Test(timeout = 100)
+ @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
+ @Test
public void testDeserializationAsFloatEdgeCase08() throws Exception
{
String input = "1e10000000";
@@ -183,7 +186,8 @@ public void testDeserializationAsFloatEdgeCase08() throws Exception
assertEquals(0, value.getSeconds());
}
- @Test(timeout = 100)
+ @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
+ @Test
public void testDeserializationAsFloatEdgeCase09() throws Exception
{
String input = "-1e10000000";
@@ -195,7 +199,8 @@ public void testDeserializationAsFloatEdgeCase09() throws Exception
/**
* Same for large negative exponents.
*/
- @Test(timeout = 100)
+ @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
+ @Test
public void testDeserializationAsFloatEdgeCase10() throws Exception
{
String input = "1e-10000000";
@@ -204,7 +209,8 @@ public void testDeserializationAsFloatEdgeCase10() throws Exception
assertEquals(0, value.getSeconds());
}
- @Test(timeout = 100)
+ @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
+ @Test
public void testDeserializationAsFloatEdgeCase11() throws Exception
{
String input = "-1e-10000000";
@@ -218,7 +224,7 @@ public void testDeserializationAsInt01() throws Exception
{
Duration value = READER.with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.readValue("60");
- assertEquals("The value is not correct.", Duration.ofSeconds(60L, 0), value);
+ assertEquals(Duration.ofSeconds(60L, 0), value, "The value is not correct.");
}
@Test
@@ -226,7 +232,7 @@ public void testDeserializationAsInt02() throws Exception
{
Duration value = READER.without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.readValue("60000");
- assertEquals("The value is not correct.", Duration.ofSeconds(60L, 0), value);
+ assertEquals(Duration.ofSeconds(60L, 0), value, "The value is not correct.");
}
@Test
@@ -234,7 +240,7 @@ public void testDeserializationAsInt03() throws Exception
{
Duration value = READER.with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.readValue("13498");
- assertEquals("The value is not correct.", Duration.ofSeconds(13498L, 0), value);
+ assertEquals(Duration.ofSeconds(13498L, 0), value, "The value is not correct.");
}
@Test
@@ -242,7 +248,7 @@ public void testDeserializationAsInt04() throws Exception
{
Duration value = READER.without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.readValue("13498000");
- assertEquals("The value is not correct.", Duration.ofSeconds(13498L, 0), value);
+ assertEquals(Duration.ofSeconds(13498L, 0), value, "The value is not correct.");
}
@Test
@@ -253,7 +259,7 @@ public void testDeserializationAsInt05() throws Exception
new WrapperWithReadTimestampsAsNanosEnabled(Duration.ofSeconds(13498L, 0));
WrapperWithReadTimestampsAsNanosEnabled actual =
reader.readValue(wrapperPayload(13498));
- assertEquals("The value is not correct.", expected.value, actual.value);
+ assertEquals(expected.value, actual.value, "The value is not correct.");
}
@Test
@@ -264,7 +270,7 @@ public void testDeserializationAsInt06() throws Exception
new WrapperWithReadTimestampsAsNanosDisabled(Duration.ofSeconds(13498L, 0));
WrapperWithReadTimestampsAsNanosDisabled actual =
reader.readValue(wrapperPayload(13498000));
- assertEquals("The value is not correct.", expected.value, actual.value);
+ assertEquals(expected.value, actual.value, "The value is not correct.");
}
@Test
@@ -272,7 +278,7 @@ public void testDeserializationAsString01() throws Exception
{
Duration exp = Duration.ofSeconds(60L, 0);
Duration value = READER.readValue('"' + exp.toString() + '"');
- assertEquals("The value is not correct.", exp, value);
+ assertEquals(exp, value, "The value is not correct.");
}
@Test
@@ -280,13 +286,13 @@ public void testDeserializationAsString02() throws Exception
{
Duration exp = Duration.ofSeconds(13498L, 8374);
Duration value = READER.readValue('"' + exp.toString() + '"');
- assertEquals("The value is not correct.", exp, value);
+ assertEquals(exp, value, "The value is not correct.");
}
@Test
public void testDeserializationAsString03() throws Exception
{
- assertNull("The value should be null.", READER.readValue("\" \""));
+ assertNull(READER.readValue("\" \""), "The value should be null.");
}
@Test
@@ -301,8 +307,8 @@ public void testDeserializationWithTypeInfo01() throws Exception
mapper.addMixIn(TemporalAmount.class, MockObjectConfiguration.class);
TemporalAmount value = mapper.readValue(prefix + "13498.000008374]", TemporalAmount.class);
- assertTrue("The value should be a Duration.", value instanceof Duration);
- assertEquals("The value is not correct.", duration, value);
+ assertTrue(value instanceof Duration, "The value should be a Duration.");
+ assertEquals(duration, value, "The value is not correct.");
}
@Test
@@ -315,8 +321,8 @@ public void testDeserializationWithTypeInfo02() throws Exception
mapper.addMixIn(TemporalAmount.class, MockObjectConfiguration.class);
TemporalAmount value = mapper.readValue(prefix + "13498]", TemporalAmount.class);
- assertTrue("The value should be a Duration.", value instanceof Duration);
- assertEquals("The value is not correct.", Duration.ofSeconds(13498L), value);
+ assertTrue(value instanceof Duration, "The value should be a Duration.");
+ assertEquals(Duration.ofSeconds(13498L), value, "The value is not correct.");
}
@Test
@@ -329,8 +335,8 @@ public void testDeserializationWithTypeInfo03() throws Exception
mapper.addMixIn(TemporalAmount.class, MockObjectConfiguration.class);
TemporalAmount value = mapper.readValue(prefix + "13498837]", TemporalAmount.class);
- assertTrue("The value should be a Duration.", value instanceof Duration);
- assertEquals("The value is not correct.", Duration.ofSeconds(13498L, 837000000), value);
+ assertTrue(value instanceof Duration, "The value should be a Duration.");
+ assertEquals(Duration.ofSeconds(13498L, 837000000), value, "The value is not correct.");
}
@Test
@@ -344,8 +350,8 @@ public void testDeserializationWithTypeInfo04() throws Exception
mapper.addMixIn(TemporalAmount.class, MockObjectConfiguration.class);
TemporalAmount value = mapper.readValue(prefix + '"' + duration.toString() + "\"]", TemporalAmount.class);
- assertTrue("The value should be a Duration.", value instanceof Duration);
- assertEquals("The value is not correct.", duration, value);
+ assertTrue(value instanceof Duration, "The value should be a Duration.");
+ assertEquals(duration, value, "The value is not correct.");
}
@Test
@@ -386,7 +392,7 @@ public void testDeserializationAsArrayEnabled() throws Exception {
.configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true)
.readerFor(Duration.class).readValue("[\"" + exp.toString() + "\"]");
- assertEquals("The value is not correct.", exp, value);
+ assertEquals(exp, value, "The value is not correct.");
}
@Test
@@ -423,10 +429,10 @@ public void testLenientDeserializeFromEmptyString() throws Exception {
String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, dateValAsEmptyStr));
Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr);
Duration actualDateFromEmptyStr = actualMapFromEmptyStr.get(key);
- assertEquals("empty string failed to deserialize to null with lenient setting", null, actualDateFromEmptyStr);
+ assertEquals(null, actualDateFromEmptyStr, "empty string failed to deserialize to null with lenient setting");
}
- @Test ( expected = MismatchedInputException.class)
+ @Test
public void testStrictDeserializeFromEmptyString() throws Exception {
final String key = "duration";
@@ -444,7 +450,7 @@ public void testStrictDeserializeFromEmptyString() throws Exception {
String dateValAsEmptyStr = "";
String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, dateValAsEmptyStr));
- objectReader.readValue(valueFromEmptyStr);
+ assertThrows(MismatchedInputException.class, () -> objectReader.readValue(valueFromEmptyStr));
}
/*
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeser291Test.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeser291Test.java
index 78804c0f..7081a921 100644
--- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeser291Test.java
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeser291Test.java
@@ -3,7 +3,7 @@
import java.time.Instant;
import java.util.Locale;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.ObjectReader;
@@ -13,8 +13,7 @@
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
// [modules-java8#291] InstantDeserializer fails to parse negative numeric timestamp strings for
// pre-1970 values.
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java
index 6222ac4f..66dec2cd 100644
--- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java
@@ -5,9 +5,11 @@
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
@@ -24,8 +26,9 @@
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
import static com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.ISO8601_COLONLESS_OFFSET_REGEX;
-import static org.junit.Assert.*;
-import static org.junit.Assume.assumeTrue;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class InstantDeserTest extends ModuleTestBase
{
@@ -138,12 +141,12 @@ public void testDeserializationAsFloatEdgeCase02() throws Exception
assertEquals(0, value.getNano());
}
- @Test(expected = DateTimeException.class)
+ @Test
public void testDeserializationAsFloatEdgeCase03() throws Exception
{
// Instant can't go this low
String input = Instant.MIN.getEpochSecond() + ".1";
- READER.readValue(input);
+ assertThrows(DateTimeException.class, () -> READER.readValue(input));
}
/*
@@ -151,21 +154,20 @@ public void testDeserializationAsFloatEdgeCase03() throws Exception
* for numbers outside the range of Long. Numbers less than 1e64 will result in the lower 64 bits.
* Numbers at or above 1e64 will always result in zero.
*/
-
- @Test(expected = DateTimeException.class)
+ @Test
public void testDeserializationAsFloatEdgeCase04() throws Exception
{
// 1ns beyond the upper-bound of Instant.
String input = (Instant.MAX.getEpochSecond() + 1) + ".0";
- READER.readValue(input);
+ assertThrows(DateTimeException.class, () -> READER.readValue(input));
}
- @Test(expected = DateTimeException.class)
+ @Test
public void testDeserializationAsFloatEdgeCase05() throws Exception
{
// 1ns beyond the lower-bound of Instant.
String input = (Instant.MIN.getEpochSecond() - 1) + ".0";
- READER.readValue(input);
+ assertThrows(DateTimeException.class, () -> READER.readValue(input));
}
@Test
@@ -188,14 +190,16 @@ public void testDeserializationAsFloatEdgeCase07() throws Exception
* Numbers with very large exponents can take a long time, but still result in zero.
* https://github.com/FasterXML/jackson-databind/issues/2141
*/
- @Test(timeout = 100)
+ @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
+ @Test
public void testDeserializationAsFloatEdgeCase08() throws Exception
{
Instant value = READER.readValue("1e10000000");
assertEquals(0, value.getEpochSecond());
}
- @Test(timeout = 100)
+ @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
+ @Test
public void testDeserializationAsFloatEdgeCase09() throws Exception
{
Instant value = READER.readValue("-1e10000000");
@@ -205,14 +209,16 @@ public void testDeserializationAsFloatEdgeCase09() throws Exception
/**
* Same for large negative exponents.
*/
- @Test(timeout = 100)
+ @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
+ @Test
public void testDeserializationAsFloatEdgeCase10() throws Exception
{
Instant value = READER.readValue("1e-10000000");
assertEquals(0, value.getEpochSecond());
}
- @Test(timeout = 100)
+ @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
+ @Test
public void testDeserializationAsFloatEdgeCase11() throws Exception
{
Instant value = READER.readValue("-1e-10000000");
@@ -368,7 +374,7 @@ public void testDeserializationWithTypeInfo01() throws Exception
Temporal value = m.readValue(
"[\"" + Instant.class.getName() + "\",123456789.183917322]", Temporal.class
);
- assertTrue("The value should be an Instant.", value instanceof Instant);
+ assertTrue(value instanceof Instant, "The value should be an Instant.");
assertEquals(date, value);
}
@@ -382,7 +388,7 @@ public void testDeserializationWithTypeInfo02() throws Exception
Temporal value = m.readValue(
"[\"" + Instant.class.getName() + "\",123456789]", Temporal.class
);
- assertTrue("The value should be an Instant.", value instanceof Instant);
+ assertTrue(value instanceof Instant, "The value should be an Instant.");
assertEquals(date, value);
}
@@ -397,7 +403,7 @@ public void testDeserializationWithTypeInfo03() throws Exception
"[\"" + Instant.class.getName() + "\",123456789422]", Temporal.class
);
- assertTrue("The value should be an Instant.", value instanceof Instant);
+ assertTrue(value instanceof Instant, "The value should be an Instant.");
assertEquals(date, value);
}
@@ -410,7 +416,7 @@ public void testDeserializationWithTypeInfo04() throws Exception
Temporal value = m.readValue(
"[\"" + Instant.class.getName() + "\",\"" + FORMATTER.format(date) + "\"]", Temporal.class
);
- assertTrue("The value should be an Instant.", value instanceof Instant);
+ assertTrue(value instanceof Instant, "The value should be an Instant.");
assertEquals(date, value);
}
@@ -444,12 +450,12 @@ public void testCustomPatternWithAnnotations02() throws Exception
final WrapperWithCustomPattern input = new WrapperWithCustomPattern(instant);
String json = MAPPER.writeValueAsString(input);
- assertTrue("Instant in UTC timezone was not serialized as expected.",
- json.contains(a2q("'valueInUTC':'" + valueInUTC + "'")));
+ assertTrue(json.contains(a2q("'valueInUTC':'" + valueInUTC + "'")),
+ "Instant in UTC timezone was not serialized as expected.");
WrapperWithCustomPattern result = MAPPER.readValue(json, WrapperWithCustomPattern.class);
- assertEquals("Instant in UTC timezone was not deserialized as expected.",
- input.valueInUTC, result.valueInUTC);
+ assertEquals(input.valueInUTC, result.valueInUTC,
+ "Instant in UTC timezone was not deserialized as expected.");
}
/*
@@ -586,10 +592,10 @@ public void testLenientDeserializeFromEmptyString() throws Exception {
String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, dateValAsEmptyStr));
Map actualMapFromEmptyStr = objectReader.readValue(valueFromEmptyStr);
Duration actualDateFromEmptyStr = actualMapFromEmptyStr.get(key);
- assertEquals("empty string failed to deserialize to null with lenient setting", null, actualDateFromEmptyStr);
+ assertEquals(null, actualDateFromEmptyStr, "empty string failed to deserialize to null with lenient setting");
}
- @Test ( expected = MismatchedInputException.class)
+ @Test
public void testStrictDeserializeFromEmptyString() throws Exception {
final String key = "instant";
@@ -604,7 +610,7 @@ public void testStrictDeserializeFromEmptyString() throws Exception {
assertNull(actualMapFromNullStr.get(key));
String valueFromEmptyStr = mapper.writeValueAsString(asMap(key, ""));
- objectReader.readValue(valueFromEmptyStr);
+ assertThrows(MismatchedInputException.class, () -> objectReader.readValue(valueFromEmptyStr));
}
/*
@@ -616,31 +622,31 @@ public void testStrictDeserializeFromEmptyString() throws Exception {
public void testISO8601ColonlessRegexFindsOffset() {
Matcher matcher = ISO8601_COLONLESS_OFFSET_REGEX.matcher("2000-01-01T12:00+0100");
- assertTrue("Matcher finds +0100 as an colonless offset", matcher.find());
- assertEquals("Matcher groups +0100 as an colonless offset", matcher.group(), "+0100");
+ assertTrue(matcher.find(), "Matcher finds +0100 as an colonless offset");
+ assertEquals(matcher.group(), "+0100", "Matcher groups +0100 as an colonless offset");
}
@Test
public void testISO8601ColonlessRegexFindsOffsetWithTZ() {
Matcher matcher = ISO8601_COLONLESS_OFFSET_REGEX.matcher("2000-01-01T12:00+0100[Europe/Paris]");
- assertTrue("Matcher finds +0100 as an colonless offset", matcher.find());
- assertEquals("Matcher groups +0100 as an colonless offset", matcher.group(), "+0100");
+ assertTrue(matcher.find(), "Matcher finds +0100 as an colonless offset");
+ assertEquals(matcher.group(), "+0100", "Matcher groups +0100 as an colonless offset");
}
@Test
public void testISO8601ColonlessRegexDoesNotAffectNegativeYears() {
Matcher matcher = ISO8601_COLONLESS_OFFSET_REGEX.matcher("-2000-01-01T12:00+01:00[Europe/Paris]");
- assertFalse("Matcher does not find -2000 (years) as an offset without colon", matcher.find());
+ assertFalse(matcher.find(), "Matcher does not find -2000 (years) as an offset without colon");
}
@Test
public void testISO8601ColonlessRegexDoesNotAffectNegativeYearsWithColonless() {
Matcher matcher = ISO8601_COLONLESS_OFFSET_REGEX.matcher("-2000-01-01T12:00+0100[Europe/Paris]");
- assertTrue("Matcher finds +0100 as an colonless offset", matcher.find());
- assertEquals("Matcher groups +0100 as an colonless offset", matcher.group(), "+0100");
+ assertTrue(matcher.find(), "Matcher finds +0100 as an colonless offset");
+ assertEquals(matcher.group(), "+0100", "Matcher groups +0100 as an colonless offset");
}
// [modules-java8#387]: StreamReadConstraints should limit numeric string lengths
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializerNegative359Test.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializerNegative359Test.java
new file mode 100644
index 00000000..ac4e5089
--- /dev/null
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializerNegative359Test.java
@@ -0,0 +1,37 @@
+package com.fasterxml.jackson.datatype.jsr310.deser;
+
+import java.time.Instant;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+// [modules-java8#359] InstantDeserializer deserializes the nanosecond portion of
+// fractional timestamps incorrectly: -1.000000001 deserializes to 1969-12-31T23:59:59.000000001Z
+// instead of 1969-12-31T23:59:58.999999999Z
+public class InstantDeserializerNegative359Test
+ extends ModuleTestBase
+{
+ private final ObjectReader READER = newMapper().readerFor(Instant.class);
+
+ @Test
+ public void testDeserializationAsFloat04()
+ throws Exception
+ {
+ Instant actual = READER.readValue("-1.000000001");
+ Instant expected = Instant.ofEpochSecond(-1L, -1L);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testDeserializationAsFloat05()
+ throws Exception
+ {
+ Instant actual = READER.readValue("-0.000000001");
+ Instant expected = Instant.ofEpochSecond(0L, -1L);
+ assertEquals(expected, actual);
+ }
+}
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/failing/InstanceViaBigDecimal307Test.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantViaBigDecimal307Test.java
similarity index 69%
rename from datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/failing/InstanceViaBigDecimal307Test.java
rename to datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantViaBigDecimal307Test.java
index 5f27f033..d1104a90 100644
--- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/failing/InstanceViaBigDecimal307Test.java
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantViaBigDecimal307Test.java
@@ -1,18 +1,20 @@
-package com.fasterxml.jackson.datatype.jsr310.failing;
+package com.fasterxml.jackson.datatype.jsr310.deser;
import java.time.Instant;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.cfg.JsonNodeFeature;
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
// [modules-java8#307]: Loss of precision via JsonNode for BigDecimal-valued
// things (like Instant)
-public class InstanceViaBigDecimal307Test extends ModuleTestBase
+// Solved via `JsonNodeFeature.USE_BIG_DECIMAL_FOR_FLOATS` added in Jackson 2.19
+public class InstantViaBigDecimal307Test extends ModuleTestBase
{
static class Wrapper307 {
public Instant value;
@@ -23,7 +25,10 @@ protected Wrapper307() { }
private final Instant ISSUED_AT = Instant.ofEpochSecond(1234567890).plusNanos(123456789);
- private ObjectMapper MAPPER = newMapper();
+ private ObjectMapper MAPPER = mapperBuilder()
+ // added in 2.19
+ .enable(JsonNodeFeature.USE_BIG_DECIMAL_FOR_FLOATS)
+ .build();
@Test
public void instantViaReadValue() throws Exception {
diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserTest.java
index e3fe9e92..f047643a 100644
--- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserTest.java
+++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserTest.java
@@ -9,12 +9,16 @@
import java.time.format.DateTimeParseException;
import java.time.temporal.Temporal;
import java.util.Map;
+import java.util.TimeZone;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import com.fasterxml.jackson.annotation.OptBoolean;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
-import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Feature;
@@ -27,18 +31,23 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration;
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.*;
-import static org.junit.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
public class LocalDateDeserTest extends ModuleTestBase
{
private final ObjectMapper MAPPER = newMapper();
private final ObjectReader READER = MAPPER.readerFor(LocalDate.class);
+ private final ObjectReader READER_USING_TIME_ZONE = JsonMapper.builder()
+ .addModule(new JavaTimeModule().enable(JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING))
+ .build()
+ .readerFor(LocalDate.class);
+
private final TypeReference