Skip to content

Commit

Permalink
Added support for Pattern and Currency
Browse files Browse the repository at this point in the history
  • Loading branch information
jdereg committed Feb 8, 2025
1 parent d5f8399 commit 19aac20
Show file tree
Hide file tree
Showing 13 changed files with 80 additions and 65 deletions.
66 changes: 42 additions & 24 deletions src/main/java/com/cedarsoftware/io/Writers.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.SignStyle;
import java.time.temporal.TemporalAccessor;
import java.util.Currency;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;
import java.util.regex.Pattern;

import com.cedarsoftware.util.Converter;

Expand Down Expand Up @@ -224,36 +226,23 @@ public boolean hasPrimitiveForm(WriterContext context) {
}
}

public static class DateAsLongWriter extends DateWriter {
public static class DateWriter implements JsonWriter.JsonClassWriter {
@Override
public void writePrimitiveForm(Object o, Writer output, WriterContext context) throws IOException {
String formatted;
if (o instanceof java.sql.Date) {
// Just use the date's built-in toString - it's already in JDBC format
formatted = o.toString();
// Write just the date portion - no time, no timezone
String formatted = ((java.sql.Date) o).toLocalDate().toString();
JsonWriter.writeBasicString(output, formatted);
} else {
formatted = Long.toString(((java.util.Date) o).getTime());
output.write(formatted);
// Regular Date uses the converter's string format
String formatted = Converter.convert(o, String.class);
JsonWriter.writeBasicString(output, formatted);
}
}
}

public static class DateWriter implements JsonWriter.JsonClassWriter {
@Override
public void writePrimitiveForm(Object o, Writer output, WriterContext context) throws IOException {
String formatted = Converter.convert(o, String.class);
JsonWriter.writeBasicString(output, formatted);
}

public void write(Object obj, boolean showType, Writer output, WriterContext context) throws IOException {
if (showType) {
String key;
if (obj instanceof java.sql.Date) {
key = "sqlDate";
} else {
key = "date";
}
String key = (obj instanceof java.sql.Date) ? "sqlDate" : "date";
JsonWriter.writeBasicString(output, key);
output.write(':');
}
Expand All @@ -266,6 +255,20 @@ public boolean hasPrimitiveForm(WriterContext context) {
}
}

public static class DateAsLongWriter extends DateWriter {
@Override
public void writePrimitiveForm(Object o, Writer output, WriterContext context) throws IOException {
if (o instanceof java.sql.Date) {
// Same pure date format for sql.Date in both writers
String formatted = ((java.sql.Date) o).toLocalDate().toString();
JsonWriter.writeBasicString(output, formatted);
} else {
// Regular Date uses milliseconds
output.write(Long.toString(((java.util.Date) o).getTime()));
}
}
}

public static class LocalDateAsLong extends PrimitiveTypeWriter {
private final ZoneId zoneId;

Expand Down Expand Up @@ -484,10 +487,9 @@ protected void writePrimitiveForm(ZoneOffset offset, Writer output) throws IOExc
public static class DurationWriter implements JsonWriter.JsonClassWriter {
public void write(Object obj, boolean showType, Writer output, WriterContext context) throws IOException {
Duration duration = (Duration) obj;
output.write("\"seconds\":");
output.write("" + duration.getSeconds());
output.write(",\"nanos\":");
output.write("" + duration.getNano());
output.write("\"duration\":\"");
output.write(duration.toString());
output.write("\"");
}
}

Expand Down Expand Up @@ -558,6 +560,22 @@ public void writePrimitiveForm(Object o, Writer output, WriterContext context) t
}
}

public static class PatternWriter extends PrimitiveValueWriter {
@Override
public void writePrimitiveForm(Object o, Writer output, WriterContext context) throws IOException {
Pattern pattern = (Pattern) o;
JsonWriter.writeJsonUtf8String(output, pattern.pattern());
}
}

public static class CurrencyWriter extends PrimitiveValueWriter {
@Override
public void writePrimitiveForm(Object o, Writer output, WriterContext context) throws IOException {
Currency currency = (Currency) o;
JsonWriter.writeJsonUtf8String(output, currency.getCurrencyCode());
}
}

public static class BigDecimalWriter extends PrimitiveValueWriter {
@Override
public void writePrimitiveForm(Object o, Writer output, WriterContext context) throws IOException {
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/config/classFactory.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ java.math.BigInteger = Convertable
java.util.concurrent.atomic.AtomicBoolean = Convertable
java.util.concurrent.atomic.AtomicInteger = Convertable
java.util.concurrent.atomic.AtomicLong = Convertable
java.util.Currency = Convertable
java.util.Date = Convertable
java.util.Calendar = Convertable
java.util.GregorianCalendar = Convertable
java.util.Locale = Convertable
java.util.UUID = Convertable
java.util.TimeZone = Convertable

java.util.regex.Pattern = Convertable

# java.lang
java.lang.Class = Convertable
java.lang.String = Convertable
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/config/customWriters.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,15 @@ java.util.Calendar = com.cedarsoftware.io.Writers$CalendarWriter
java.util.concurrent.atomic.AtomicBoolean = com.cedarsoftware.io.Writers$PrimitiveValueWriter
java.util.concurrent.atomic.AtomicInteger = com.cedarsoftware.io.Writers$PrimitiveValueWriter
java.util.concurrent.atomic.AtomicLong = com.cedarsoftware.io.Writers$PrimitiveValueWriter
java.util.Currency = com.cedarsoftware.io.Writers$CurrencyWriter
java.util.Date = com.cedarsoftware.io.Writers$DateAsLongWriter
java.util.GregorianCalendar = com.cedarsoftware.io.Writers$CalendarWriter
java.util.Locale = com.cedarsoftware.io.Writers$LocaleWriter
java.util.TimeZone = com.cedarsoftware.io.Writers$TimeZoneWriter
java.util.UUID = com.cedarsoftware.io.Writers$UUIDWriter

java.util.regex.Pattern = com.cedarsoftware.io.Writers$PatternWriter

sun.util.calendar.ZoneInfo = com.cedarsoftware.io.Writers$TimeZoneWriter

# CompactMap
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/com/cedarsoftware/io/ClassTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void testNoClassNameValue()
String json = "{\"@type\":\"class\"}";
assertThatThrownBy(() -> TestUtil.toObjects(json, null))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("Map to 'Class' the map must include: [value] or [_v] as keys with associated values");
.hasMessageContaining("Map to 'Class' the map must include: [class], [value], or [_v] as key with associated value");

}

Expand Down
29 changes: 12 additions & 17 deletions src/test/java/com/cedarsoftware/io/DatesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
Expand Down Expand Up @@ -407,17 +406,18 @@ void testSqlDate()
@Test
void testSqlDate2() {
// Given
long now = 1703043551033L; // This represents the full epoch in UTC.
// Construct our expected objects (for direct comparison)
long now = 1703043551033L; // full epoch in UTC.
// Construct expected objects:
Date expectedUtilDate = new Date(now);
// For java.sql.Date, create it as a literal date (interpreting 'now' in UTC):

// Compute expected LocalDate using the same zone as your converter (e.g., Asia/Tokyo)
LocalDate expectedLD = Instant.ofEpochMilli(now)
.atZone(ZoneOffset.UTC)
.atZone(ZoneId.systemDefault())
.toLocalDate();
java.sql.Date expectedSqlDate = java.sql.Date.valueOf(expectedLD);
// For Timestamp, we expect the complete value to be included.

// For Timestamp, set up as before
Timestamp expectedTimestamp = new Timestamp(now);
// (If your Timestamp conversion uses separate fields for nanos, it should result in nanos=33000000.)
expectedTimestamp.setNanos(33000000); // ensuring the fractional part is set

String json = "{\"@type\":\"[Ljava.util.Date;\",\"@items\":[1703043551033,{\"@type\":\"java.sql.Date\", \"sqlDate\":1703043551033},{\"@type\":\"java.sql.Timestamp\",\"epochMillis\":\"1703043551000\"}]}";
Expand All @@ -429,23 +429,18 @@ void testSqlDate2() {
// Then
assertEquals(3, dates2.length);

// For a plain java.util.Date, simply compare the underlying epoch:
// For plain java.util.Date
assertEquals(expectedUtilDate.getTime(), dates2[0].getTime());

// For a java.sql.Date, compare by their literal date string (or equivalently compare LocalDate).
// (This avoids any unintended time zone arithmetic.)
assertEquals(expectedSqlDate.toString(), dates2[1].toString());

// Optionally, you could also convert to LocalDate and compare:
// For java.sql.Date, compare by string (or LocalDate)
assertEquals(expectedSqlDate, dates2[1]);
LocalDate ldFromActual = Instant.ofEpochMilli(dates2[1].getTime())
.atZone(ZoneOffset.UTC)
.atZone(ZoneId.systemDefault())
.toLocalDate();
assertEquals(expectedLD, ldFromActual);

// For the Timestamp:
// For Timestamp:
Timestamp stamp = (Timestamp) dates2[2];
// The JSON provides "epochMillis": "1703043551000" and nanos: 33000000,
// so the full value should be 1703043551000 + 33 = 1703043551033.
assertEquals(1703043551000L, stamp.getTime());
}

Expand Down
2 changes: 1 addition & 1 deletion src/test/java/com/cedarsoftware/io/InstantTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ void testOldFormats(String fileName, Instant expected) {
void testOldFormatWithNothing() {
assertThatThrownBy(() -> TestUtil.toObjects(loadJson("old-format-missing-fields.json"), Instant.class))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("Map to 'Instant' the map must include: [instant], [value], or [_v] as keys with associated values");
.hasMessageContaining("Map to 'Instant' the map must include: [instant], [value], or [_v] as key with associated value");
}

private String loadJson(String fileName) {
Expand Down
6 changes: 3 additions & 3 deletions src/test/java/com/cedarsoftware/io/LocaleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ public void testLocale()
assertEquals(locale, us);

Throwable e = assertThrows(Exception.class, () -> { TestUtil.toObjects("{\"@type\":\"java.util.Locale\"}", null); });
assertTrue(e.getMessage().contains("convert from Map to 'Locale' the map must include: [language, country (optional), script (optional), variant (optional)], [value], or [_v] as keys with associated values"));
assertTrue(e.getMessage().contains("Map to 'Locale' the map must include: [locale], [value], or [_v] as key with associated value"));

json = "{\"@type\":\"java.util.Locale\",\"language\":\"en\"}";
json = "{\"@type\":\"java.util.Locale\",\"locale\":\"en\"}";
locale = TestUtil.toObjects(json, null);
assertEquals("en", locale.getLanguage());
assertEquals("", locale.getCountry());
assertEquals("", locale.getVariant());

json = "{\"@type\":\"java.util.Locale\",\"language\":\"en\",\"country\":\"US\"}";
json = "{\"@type\":\"java.util.Locale\",\"locale\":\"en-US\"}";
locale = TestUtil.toObjects(json, null);
assertEquals("en", locale.getLanguage());
assertEquals("US", locale.getCountry());
Expand Down
18 changes: 9 additions & 9 deletions src/test/java/com/cedarsoftware/io/PrimitivesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,48 +83,48 @@ void testEmptyPrimitives()

assertThatThrownBy(() -> TestUtil.toObjects(json, null))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("To convert from Map to 'Byte' the map must include: [value] or [_v] as keys with associated values");
.hasMessageContaining("To convert from Map to 'Byte' the map must include: [value] or [_v] as key with associated value");

final String json1 = "{\"@type\":\"short\"}";

assertThatThrownBy(() -> TestUtil.toObjects(json1, null))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("convert from Map to 'Short' the map must include: [value] or [_v] as keys with associated values");
.hasMessageContaining("convert from Map to 'Short' the map must include: [value] or [_v] as key with associated value");

final String json2 = "{\"@type\":\"int\"}";
assertThatThrownBy(() -> TestUtil.toObjects(json2, null))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("convert from Map to 'Integer' the map must include: [value] or [_v] as keys with associated values");
.hasMessageContaining("convert from Map to 'Integer' the map must include: [value] or [_v] as key with associated value");

final String json3 = "{\"@type\":\"long\"}";
assertThatThrownBy(() -> TestUtil.toObjects(json3, null))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("convert from Map to 'Long' the map must include: [value] or [_v] as keys with associated values");
.hasMessageContaining("convert from Map to 'Long' the map must include: [value] or [_v] as key with associated value");

final String json4 = "{\"@type\":\"float\"}";
assertThatThrownBy(() -> TestUtil.toObjects(json4, null))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("convert from Map to 'Float' the map must include: [value] or [_v] as keys with associated values");
.hasMessageContaining("convert from Map to 'Float' the map must include: [value] or [_v] as key with associated value");

final String json5 = "{\"@type\":\"double\"}";
assertThatThrownBy(() -> TestUtil.toObjects(json5, null))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("convert from Map to 'Double' the map must include: [value] or [_v] as keys with associated values");
.hasMessageContaining("convert from Map to 'Double' the map must include: [value] or [_v] as key with associated value");

final String json6 = "{\"@type\":\"char\"}";
assertThatThrownBy(() -> TestUtil.toObjects(json6, null))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("convert from Map to 'char' the map must include: [value] or [_v] as keys with associated values");
.hasMessageContaining("convert from Map to 'char' the map must include: [value] or [_v] as key with associated value");

final String json7 = "{\"@type\":\"boolean\"}";
assertThatThrownBy(() -> TestUtil.toObjects(json7, null))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("convert from Map to 'Boolean' the map must include: [value] or [_v] as keys with associated values");
.hasMessageContaining("convert from Map to 'Boolean' the map must include: [value] or [_v] as key with associated value");

final String json8 = "{\"@type\":\"string\"}";
assertThatThrownBy(() -> TestUtil.toObjects(json8, null))
.isInstanceOf(JsonIoException.class)
.hasMessageContaining("convert from Map to 'String' the map must include: [value] or [_v] as keys with associated values");
.hasMessageContaining("convert from Map to 'String' the map must include: [value] or [_v] as key with associated value");
}

@Test
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/com/cedarsoftware/io/UUIDTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ public void testAssignUUID()

String json2 = "{\"@type\":\"" + TestUUIDFields.class.getName() + "\", \"internals\": {\"@type\": \"java.util.UUID\", \"leastSigBits\":-7929886640328317609}}";
thrown = assertThrows(JsonIoException.class, () -> { TestUtil.toObjects(json2, null); });
assert thrown.getMessage().contains("Map to 'UUID' the map must include: [UUID], [mostSigBits, leastSigBits], [value], or [_v] as keys with associated values");
assert thrown.getMessage().contains("Map to 'UUID' the map must include: [UUID], [value], [_v], or [mostSigBits, leastSigBits] as key with associated value");

String json3 = "{\"@type\":\"" + TestUUIDFields.class.getName() + "\", \"internals\": {\"@type\": \"java.util.UUID\", \"mostSigBits\":7280309849777586861}}";
thrown = assertThrows(JsonIoException.class, () -> { TestUtil.toObjects(json3, null); });
assert thrown.getMessage().contains("Map to 'UUID' the map must include: [UUID], [mostSigBits, leastSigBits], [value], or [_v] as keys with associated values");
assert thrown.getMessage().contains("Map to 'UUID' the map must include: [UUID], [value], [_v], or [mostSigBits, leastSigBits] as key with associated value");
}

@Test
Expand Down
3 changes: 1 addition & 2 deletions src/test/resources/duration/old-format-1.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"@type": "java.time.Duration",
"seconds": 420,
"nanos": null
"duration": 420000
}
3 changes: 1 addition & 2 deletions src/test/resources/duration/old-format-2.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"@type": "java.time.Duration",
"seconds": 25200,
"nanos": 2000
"duration": 25200.000002
}
3 changes: 1 addition & 2 deletions src/test/resources/duration/old-format-3.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"@type": "java.time.Duration",
"seconds": 777600,
"nanos": null
"duration": 777600000
}
3 changes: 1 addition & 2 deletions src/test/resources/duration/old-format-4.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"@type": "java.time.Duration",
"seconds": 9000,
"nanos": 9000
"duration": "9000.000009"
}

0 comments on commit 19aac20

Please sign in to comment.