diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONReader.java b/core/src/main/java/com/alibaba/fastjson2/JSONReader.java
index 86fad887c2..121b86a3f4 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONReader.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONReader.java
@@ -1527,6 +1527,8 @@ public LocalTime readLocalTime() {
return readLocalTime5();
case 8:
return readLocalTime8();
+ case 9:
+ return readLocalTime9();
case 10:
return readLocalTime10();
case 11:
@@ -1782,6 +1784,8 @@ public final long readMillisFromString() {
protected abstract LocalTime readLocalTime8();
+ protected abstract LocalTime readLocalTime9();
+
protected abstract LocalTime readLocalTime10();
protected abstract LocalTime readLocalTime11();
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONReaderJSONB.java b/core/src/main/java/com/alibaba/fastjson2/JSONReaderJSONB.java
index b724758586..5825473de0 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONReaderJSONB.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONReaderJSONB.java
@@ -5174,6 +5174,18 @@ protected LocalTime readLocalTime8() {
return time;
}
+ @Override
+ protected LocalTime readLocalTime9() {
+ LocalTime time;
+ if (bytes[offset] != BC_STR_ASCII_FIX_MIN + 8
+ || (time = DateUtils.parseLocalTime8(bytes, offset + 1)) == null
+ ) {
+ throw new JSONException("date only support string input");
+ }
+ offset += 10;
+ return time;
+ }
+
@Override
protected LocalTime readLocalTime12() {
LocalTime time;
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF16.java b/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF16.java
index 2fc06a83eb..8a5d8973e5 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF16.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF16.java
@@ -5532,6 +5532,26 @@ protected final LocalTime readLocalTime8() {
return time;
}
+ @Override
+ protected final LocalTime readLocalTime9() {
+ if (ch != '"' && ch != '\'') {
+ throw new JSONException("localTime only support string input");
+ }
+
+ LocalTime time = DateUtils.parseLocalTime8(chars, offset);
+ if (time == null) {
+ return null;
+ }
+
+ offset += 10;
+ next();
+ if (comma = (ch == ',')) {
+ next();
+ }
+
+ return time;
+ }
+
@Override
public final LocalDate readLocalDate8() {
if (ch != '"' && ch != '\'') {
diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java b/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java
index 94949cb379..f2ded0fade 100644
--- a/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java
+++ b/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java
@@ -7281,7 +7281,7 @@ protected final LocalTime readLocalTime5() {
@Override
protected final LocalTime readLocalTime8() {
- if (!isString()) {
+ if (ch != '"' && ch != '\'') {
throw new JSONException("localTime only support string input");
}
@@ -7299,6 +7299,26 @@ protected final LocalTime readLocalTime8() {
return time;
}
+ @Override
+ protected final LocalTime readLocalTime9() {
+ if (ch != '"' && ch != '\'') {
+ throw new JSONException("localTime only support string input");
+ }
+
+ LocalTime time = DateUtils.parseLocalTime8(bytes, offset);
+ if (time == null) {
+ return null;
+ }
+
+ offset += 10;
+ next();
+ if (comma = (ch == ',')) {
+ next();
+ }
+
+ return time;
+ }
+
@Override
protected final LocalTime readLocalTime10() {
if (!isString()) {
diff --git a/core/src/main/java/com/alibaba/fastjson2/TypeReference.java b/core/src/main/java/com/alibaba/fastjson2/TypeReference.java
index 295b0d49cd..b0064fd068 100644
--- a/core/src/main/java/com/alibaba/fastjson2/TypeReference.java
+++ b/core/src/main/java/com/alibaba/fastjson2/TypeReference.java
@@ -329,11 +329,19 @@ public static Type arrayType(Class> elementType) {
public static Type mapType(
Class extends Map> mapClass,
- Class> keyClass, Class> valueClass
+ Class> keyClass,
+ Class> valueClass
) {
return new ParameterizedTypeImpl(mapClass, keyClass, valueClass);
}
+ public static Type mapType(
+ Class> keyClass,
+ Type valueType
+ ) {
+ return new ParameterizedTypeImpl(Map.class, keyClass, valueType);
+ }
+
public static Type parametricType(Class> parametrized, Class>... parameterClasses) {
return new ParameterizedTypeImpl(parametrized, parameterClasses);
}
diff --git a/core/src/main/java/com/alibaba/fastjson2/util/DateUtils.java b/core/src/main/java/com/alibaba/fastjson2/util/DateUtils.java
index a737674e14..42aaa58f02 100644
--- a/core/src/main/java/com/alibaba/fastjson2/util/DateUtils.java
+++ b/core/src/main/java/com/alibaba/fastjson2/util/DateUtils.java
@@ -27,6 +27,7 @@ public class DateUtils {
public static final ZoneRules SHANGHAI_ZONE_RULES = SHANGHAI_ZONE_ID.getRules();
public static final String OFFSET_8_ZONE_ID_NAME = "+08:00";
public static final ZoneId OFFSET_8_ZONE_ID = ZoneId.of(OFFSET_8_ZONE_ID_NAME);
+ public static final LocalDate LOCAL_DATE_19700101 = LocalDate.of(1970, 1, 1);
static DateTimeFormatter DATE_TIME_FORMATTER_34;
static DateTimeFormatter DATE_TIME_FORMATTER_COOKIE;
@@ -240,6 +241,10 @@ public static LocalDateTime parseLocalDateTime(char[] str, int off, int len) {
String input = new String(str, off, len);
throw new DateTimeParseException("illegal input " + input, input, 0);
case 8: {
+ if (str[2] == ':' && str[5] == ':') {
+ LocalTime localTime = parseLocalTime8(str, off);
+ return LocalDateTime.of(LOCAL_DATE_19700101, localTime);
+ }
LocalDate localDate = parseLocalDate8(str, off);
if (localDate == null) {
return null;
@@ -375,55 +380,16 @@ public static LocalTime parseLocalTime8(byte[] bytes, int off) {
return null;
}
- byte c0 = bytes[off];
- byte c1 = bytes[off + 1];
- byte c2 = bytes[off + 2];
- byte c3 = bytes[off + 3];
- byte c4 = bytes[off + 4];
- byte c5 = bytes[off + 5];
- byte c6 = bytes[off + 6];
- byte c7 = bytes[off + 7];
-
- byte h0, h1, i0, i1, s0, s1;
- if (c2 == ':' && c5 == ':') {
- h0 = c0;
- h1 = c1;
- i0 = c3;
- i1 = c4;
- s0 = c6;
- s1 = c7;
- } else {
- return null;
- }
+ char c0 = (char) bytes[off];
+ char c1 = (char) bytes[off + 1];
+ char c2 = (char) bytes[off + 2];
+ char c3 = (char) bytes[off + 3];
+ char c4 = (char) bytes[off + 4];
+ char c5 = (char) bytes[off + 5];
+ char c6 = (char) bytes[off + 6];
+ char c7 = (char) bytes[off + 7];
- int hour;
- if (h0 >= '0' && h0 <= '9'
- && h1 >= '0' && h1 <= '9'
- ) {
- hour = (h0 - '0') * 10 + (h1 - '0');
- } else {
- return null;
- }
-
- int minute;
- if (i0 >= '0' && i0 <= '9'
- && i1 >= '0' && i1 <= '9'
- ) {
- minute = (i0 - '0') * 10 + (i1 - '0');
- } else {
- return null;
- }
-
- int second;
- if (s0 >= '0' && s0 <= '9'
- && s1 >= '0' && s1 <= '9'
- ) {
- second = (s0 - '0') * 10 + (s1 - '0');
- } else {
- return null;
- }
-
- return LocalTime.of(hour, minute, second);
+ return parseLocalTime(c0, c1, c2, c3, c4, c5, c6, c7);
}
public static LocalTime parseLocalTime8(char[] bytes, int off) {
@@ -440,6 +406,19 @@ public static LocalTime parseLocalTime8(char[] bytes, int off) {
char c6 = bytes[off + 6];
char c7 = bytes[off + 7];
+ return parseLocalTime(c0, c1, c2, c3, c4, c5, c6, c7);
+ }
+
+ public static LocalTime parseLocalTime(
+ char c0,
+ char c1,
+ char c2,
+ char c3,
+ char c4,
+ char c5,
+ char c6,
+ char c7
+ ) {
char h0, h1, i0, i1, s0, s1;
if (c2 == ':' && c5 == ':') {
h0 = c0;
@@ -1432,6 +1411,7 @@ public static long parseMillis(char[] chars, int off, int len, ZoneId zoneId) {
} else {
char last = chars[len - 1];
if (last == 'Z') {
+ len--;
zoneId = UTC;
}
LocalDateTime ldt = DateUtils.parseLocalDateTime(chars, off, len);
diff --git a/core/src/main/java/com/alibaba/fastjson2/util/JdbcSupport.java b/core/src/main/java/com/alibaba/fastjson2/util/JdbcSupport.java
index aaf64845f7..6b361abdda 100644
--- a/core/src/main/java/com/alibaba/fastjson2/util/JdbcSupport.java
+++ b/core/src/main/java/com/alibaba/fastjson2/util/JdbcSupport.java
@@ -258,14 +258,30 @@ public Object readObject(JSONReader jsonReader, Type fieldType, Object fieldName
} else {
String str = jsonReader.readString();
if ("0000-00-00".equals(str) || "0000-00-00 00:00:00".equals(str)) {
- return function.apply(0);
- }
-
- if (str.isEmpty() || "null".equals(str)) {
- return null;
+ millis = 0;
+ } else {
+ if (str.length() == 9 && str.charAt(8) == 'Z') {
+ LocalTime localTime = DateUtils.parseLocalTime(
+ str.charAt(0),
+ str.charAt(1),
+ str.charAt(2),
+ str.charAt(3),
+ str.charAt(4),
+ str.charAt(5),
+ str.charAt(6),
+ str.charAt(7)
+ );
+ millis = LocalDateTime.of(DateUtils.LOCAL_DATE_19700101, localTime)
+ .atZone(DateUtils.DEFAULT_ZONE_ID)
+ .toInstant()
+ .toEpochMilli();
+ } else {
+ if (str.isEmpty() || "null".equals(str)) {
+ return null;
+ }
+ return functionValueOf.apply(str);
+ }
}
-
- return functionValueOf.apply(str);
}
return function.apply(millis);
diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriter.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriter.java
index 124bbcc6ed..3d874a8fdc 100644
--- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriter.java
+++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriter.java
@@ -126,6 +126,12 @@ default void write(JSONWriter jsonWriter, Object object) {
write(jsonWriter, object, null, null, 0);
}
+ default String toJSONString(T object, JSONWriter.Feature... features) {
+ JSONWriter jsonWriter = JSONWriter.of(features);
+ write(jsonWriter, object, null, null, 0);
+ return jsonWriter.toString();
+ }
+
void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features);
default void writeWithFilter(JSONWriter jsonWriter, Object object) {
diff --git a/core/src/test/java/com/alibaba/fastjson2/date/LocalTimeTest.java b/core/src/test/java/com/alibaba/fastjson2/date/LocalTimeTest.java
index ca51213ef6..c0ddec5124 100644
--- a/core/src/test/java/com/alibaba/fastjson2/date/LocalTimeTest.java
+++ b/core/src/test/java/com/alibaba/fastjson2/date/LocalTimeTest.java
@@ -88,4 +88,12 @@ public void setTime(LocalTime time) {
this.time = time;
}
}
+
+ @Test
+ public void test5() throws Exception {
+ LocalTime time = JSON.parseObject("\"12:12:43Z\"", LocalTime.class);
+ String str = JSON.toJSONString(time);
+ LocalTime time1 = JSON.parseObject(str, LocalTime.class);
+ assertEquals(time, time1);
+ }
}
diff --git a/core/src/test/java/com/alibaba/fastjson2/date/SqlTimeTest.java b/core/src/test/java/com/alibaba/fastjson2/date/SqlTimeTest.java
index 1c9430a716..d87293da8d 100644
--- a/core/src/test/java/com/alibaba/fastjson2/date/SqlTimeTest.java
+++ b/core/src/test/java/com/alibaba/fastjson2/date/SqlTimeTest.java
@@ -5,6 +5,8 @@
import com.alibaba.fastjson2.annotation.JSONField;
import org.junit.jupiter.api.Test;
+import java.sql.Time;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
public class SqlTimeTest {
@@ -83,4 +85,20 @@ public static class Student4 {
@JSONField(format = "unixtime")
public java.sql.Time birthday;
}
+
+ @Test
+ public void test5() throws Exception {
+ Time time = JSON.parseObject("\"12:12:43Z\"", Time.class);
+ String str = JSON.toJSONString(time);
+ Time time1 = JSON.parseObject(str, Time.class);
+ assertEquals(time, time1);
+ }
+
+ @Test
+ public void test6() throws Exception {
+ Time time = JSON.parseObject("\"12:12:43\"", Time.class);
+ String str = JSON.toJSONString(time);
+ Time time1 = JSON.parseObject(str, Time.class);
+ assertEquals(time, time1);
+ }
}
diff --git a/extension/pom.xml b/extension/pom.xml
index 3f1e554760..5a172445a9 100644
--- a/extension/pom.xml
+++ b/extension/pom.xml
@@ -158,6 +158,19 @@
commons-io
test
+
+
+ org.apache.flink
+ flink-core
+ 1.17.0
+ test
+
+
+ org.apache.flink
+ flink-json
+ 1.17.0
+ test
+
diff --git a/extension/src/test/java/com/alibaba/fastjson2/flink/JsonRowDeserializationSchemaTest.java b/extension/src/test/java/com/alibaba/fastjson2/flink/JsonRowDeserializationSchemaTest.java
new file mode 100644
index 0000000000..9dde23cf85
--- /dev/null
+++ b/extension/src/test/java/com/alibaba/fastjson2/flink/JsonRowDeserializationSchemaTest.java
@@ -0,0 +1,124 @@
+package com.alibaba.fastjson2.flink;
+
+import com.alibaba.fastjson2.*;
+import com.alibaba.fastjson2.reader.ObjectReader;
+import com.alibaba.fastjson2.writer.ObjectWriter;
+import org.apache.flink.types.Row;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Type;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class JsonRowDeserializationSchemaTest {
+ @Test
+ public void test() {
+ long id = 1238123899121L;
+ String name = "asdlkjasjkdla998y1122";
+ byte[] bytes = new byte[1024];
+ ThreadLocalRandom.current().nextBytes(bytes);
+ Timestamp timestamp = Timestamp.valueOf("1990-10-14 12:12:43");
+ Date date = Date.valueOf("1990-10-14");
+ Time time = Time.valueOf("12:12:43");
+
+ Map map = new HashMap<>();
+ map.put("flink", 123L);
+
+ Map> nestedMap = new HashMap<>();
+ Map innerMap = new HashMap<>();
+ innerMap.put("key", 234);
+ nestedMap.put("inner_map", innerMap);
+
+ JSONObject root = new JSONObject();
+ root.put("id", id);
+ root.put("name", name);
+ root.put("bytes", bytes);
+ root.put("date1", "1990-10-14");
+ root.put("date2", "1990-10-14");
+ root.put("time1", "12:12:43Z");
+ root.put("time2", "12:12:43Z");
+ root.put("timestamp1", "1990-10-14T12:12:43Z");
+ root.put("timestamp2", "1990-10-14T12:12:43Z");
+ root.putObject("map").put("flink", 123);
+ root.putObject("map2map").putObject("inner_map").put("key", 234);
+
+ String[] names = new String[]{
+ "id",
+ "name",
+ "bytes",
+ "date1",
+ "date2",
+ "time1",
+ "time2",
+ "timestamp1",
+ "timestamp2",
+ "map",
+ "map2map"
+ };
+ Type[] types = new Type[]{
+ Long.class,
+ String.class,
+ byte[].class,
+ java.sql.Date.class,
+ LocalDate.class,
+ java.sql.Time.class,
+ LocalTime.class,
+ java.sql.Timestamp.class,
+ LocalDateTime.class,
+ TypeReference.mapType(String.class, Long.class),
+ TypeReference.mapType(String.class, TypeReference.mapType(String.class, Integer.class))
+ };
+ ObjectReader objectReader = JSONFactory.getDefaultObjectReaderProvider()
+ .createObjectReader(
+ names,
+ types,
+ () -> new Row(11),
+ (r, i, v) -> r.setField(i, v)
+ );
+
+ byte[] serializedJson = JSON.toJSONBytes(root);
+
+ Row row = new Row(11);
+ row.setField(0, id);
+ row.setField(1, name);
+ row.setField(2, bytes);
+ row.setField(3, date);
+ row.setField(4, date.toLocalDate());
+ row.setField(5, time);
+ row.setField(6, time.toLocalTime());
+ row.setField(7, timestamp);
+ row.setField(8, timestamp.toLocalDateTime());
+ row.setField(9, map);
+ row.setField(10, nestedMap);
+
+ ObjectWriter objectWriter = JSONFactory.getDefaultObjectWriterProvider()
+ .getCreator()
+ .createObjectWriter(
+ names,
+ types,
+ (Row r, int i) -> r.getField(i)
+ );
+
+ JSONReader jsonReader = JSONReader.of(serializedJson);
+ Row row1 = objectReader.readObject(jsonReader);
+
+ JSONWriter jsonWriter = JSONWriter.of();
+ objectWriter.write(jsonWriter, row);
+
+ String str = objectWriter.toJSONString(row, JSONWriter.Feature.PrettyFormat);
+ String str1 = objectWriter.toJSONString(row1, JSONWriter.Feature.PrettyFormat);
+
+ System.out.println(str);
+ System.out.println(str1);
+ assertEquals(str, str1);
+ }
+}