Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 64 additions & 3 deletions core/src/main/java/com/alibaba/fastjson2/util/JdbcSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.alibaba.fastjson2.writer.ObjectWriter;

import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.sql.Time;
Expand All @@ -26,6 +27,15 @@ public class JdbcSupport {
static Class CLASS_CLOB;
static volatile boolean CLASS_CLOB_ERROR;

static Constructor CONSTRUCTOR_TIMESTAMP;
static volatile boolean CONSTRUCTOR_TIMESTAMP_ERROR;

static Constructor CONSTRUCTOR_DATE;
static volatile boolean CONSTRUCTOR_DATE_ERROR;

static Constructor CONSTRUCTOR_TIME;
static volatile boolean CONSTRUCTOR_TIME_ERROR;

public static ObjectReader createTimeReader(Class objectClass, String format, Locale locale) {
return new TimeReader(format, locale);
}
Expand All @@ -47,15 +57,66 @@ public static ObjectWriter createTimeWriter(String format) {
}

public static Object createTimestamp(long millis) {
return new Timestamp(millis);
if (CONSTRUCTOR_TIMESTAMP == null && !CONSTRUCTOR_TIMESTAMP_ERROR) {
try {
Class<?> clazz = Class.forName("java.sql.Timestamp");
CONSTRUCTOR_TIMESTAMP = clazz.getConstructor(long.class);
} catch (Throwable e) {
CONSTRUCTOR_TIMESTAMP_ERROR = true;
}
}

if (CONSTRUCTOR_TIMESTAMP == null) {
throw new JSONException("class java.sql.Timestamp not found");
}

try {
return CONSTRUCTOR_TIMESTAMP.newInstance(millis);
} catch (Exception e) {
throw new JSONException("create java.sql.Timestamp error", e);
}
}

public static Object createDate(long millis) {
return new java.sql.Date(millis);
if (CONSTRUCTOR_DATE == null && !CONSTRUCTOR_DATE_ERROR) {
try {
Class<?> clazz = Class.forName("java.sql.Date");
CONSTRUCTOR_DATE = clazz.getConstructor(long.class);
} catch (Throwable e) {
CONSTRUCTOR_DATE_ERROR = true;
}
}

if (CONSTRUCTOR_DATE == null) {
throw new JSONException("class java.sql.Date not found");
}

try {
return CONSTRUCTOR_DATE.newInstance(millis);
} catch (Exception e) {
throw new JSONException("create java.sql.Date error", e);
}
}

public static Object createTime(long millis) {
return new java.sql.Time(millis);
if (CONSTRUCTOR_TIME == null && !CONSTRUCTOR_TIME_ERROR) {
try {
Class<?> clazz = Class.forName("java.sql.Time");
CONSTRUCTOR_TIME = clazz.getConstructor(long.class);
} catch (Throwable e) {
CONSTRUCTOR_TIME_ERROR = true;
}
}

if (CONSTRUCTOR_TIME == null) {
throw new JSONException("class java.sql.Time not found");
}

try {
return CONSTRUCTOR_TIME.newInstance(millis);
} catch (Exception e) {
throw new JSONException("create java.sql.Time error", e);
}
}

public static ObjectWriter createClobWriter(Class objectClass) {
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/util/TypeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,22 @@ public static <T> T cast(Object obj, Class<T> targetClass, ObjectReaderProvider
}
}

if (obj instanceof Date) {
long time = ((Date) obj).getTime();
String className = targetClass.getName();

switch (className) {
case "java.sql.Timestamp":
return (T) JdbcSupport.createTimestamp(time);
case "java.sql.Date":
return (T) JdbcSupport.createDate(time);
case "java.sql.Time":
return (T) JdbcSupport.createTime(time);
default:
break;
}
}

if (targetClass == String.class) {
if (obj instanceof Character) {
return (T) obj.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,11 +546,11 @@ public static BigInteger castToBigInteger(Object value) {
return com.alibaba.fastjson2.util.TypeUtils.toBigInteger(value);
}

public static Timestamp castToTimestamp(final Object value) {
public static Object castToTimestamp(final Object value) {
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the return type from Timestamp to Object is a breaking API change that will cause compilation errors for existing code. While this may match version 1.2.83's signature, it breaks type safety and forces callers to cast the result. Consider keeping the return type as Timestamp to maintain backward compatibility, or document this as a breaking change in the release notes.

Suggested change
public static Object castToTimestamp(final Object value) {
public static Timestamp castToTimestamp(final Object value) {

Copilot uses AI. Check for mistakes.
return com.alibaba.fastjson2.util.TypeUtils.cast(value, Timestamp.class);
}

public static java.sql.Date castToSqlDate(final Object value) {
public static Object castToSqlDate(final Object value) {
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the return type from java.sql.Date to Object is a breaking API change that will cause compilation errors for existing code. While this may match version 1.2.83's signature, it breaks type safety and forces callers to cast the result. Consider keeping the return type as java.sql.Date to maintain backward compatibility, or document this as a breaking change in the release notes.

Suggested change
public static Object castToSqlDate(final Object value) {
public static java.sql.Date castToSqlDate(final Object value) {

Copilot uses AI. Check for mistakes.
return com.alibaba.fastjson2.util.TypeUtils.cast(value, java.sql.Date.class);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.alibaba.fastjson2;

import com.alibaba.fastjson.util.TypeUtils;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

public class Issue3906 {
@Test
public void test() throws NoSuchMethodException {
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method signature declares throws NoSuchMethodException but this exception is never thrown by the test logic. The assertDoesNotThrow lambda wraps the call to TypeUtils.castToTimestamp, which doesn't throw this checked exception. Remove the unnecessary throws NoSuchMethodException declaration.

Suggested change
public void test() throws NoSuchMethodException {
public void test() {

Copilot uses AI. Check for mistakes.
assertDoesNotThrow(() -> TypeUtils.castToTimestamp(new java.util.Date()));
}
Comment on lines +9 to +12
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test has two issues:

  1. It only verifies that no exception is thrown, but doesn't verify that the conversion actually produces a correct Timestamp object. Consider adding assertions to verify the result is not null, is an instance of java.sql.Timestamp, and has the same time value as the input.
  2. The PR description mentions fixing both issue [BUG]fastjson兼容包报错,2.0.60版本,使用TypeUtils转换java.util.Date为Timestamp报错,1.2.83版本则是正常的 #3906 and [BUG]二方包里使用com.alibaba.fastjson.util.TypeUtils#castToTimestamp时,升级fastjson版本不兼容 #3907, but only includes a test for castToTimestamp. Add a similar test case for castToSqlDate to ensure the fix works for both methods.

Copilot uses AI. Check for mistakes.
}
Loading