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 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); + } +}