diff --git a/src/one/nio/serial/Before.java b/src/one/nio/serial/Before.java new file mode 100644 index 00000000..bba3ad8e --- /dev/null +++ b/src/one/nio/serial/Before.java @@ -0,0 +1,12 @@ +package one.nio.serial; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface Before { + +} diff --git a/src/one/nio/serial/Json.java b/src/one/nio/serial/Json.java index b7354eea..3f863455 100755 --- a/src/one/nio/serial/Json.java +++ b/src/one/nio/serial/Json.java @@ -24,6 +24,9 @@ public class Json { + public static final long JS_NUMBER_MAX_SAFE_INTEGER = 9007199254740991L; + public static final long JS_NUMBER_MIN_SAFE_INTEGER = -9007199254740991L; + public static void appendChar(StringBuilder builder, char c) { builder.append('"'); if (c == '"' || c == '\\') { @@ -44,6 +47,16 @@ public static void appendChars(StringBuilder builder, char[] obj) { builder.append(obj, from, obj.length - from).append('"'); } + public static void appendLong(StringBuilder builder, long obj) { + if (obj < JS_NUMBER_MIN_SAFE_INTEGER | obj > JS_NUMBER_MAX_SAFE_INTEGER) { + builder.append('"'); + builder.append(obj); + builder.append('"'); + } else { + builder.append(obj); + } + } + public static void appendString(StringBuilder builder, String s) { int length = s.length(); for (int i = 0; i < length; i++) { diff --git a/src/one/nio/serial/JsonReader.java b/src/one/nio/serial/JsonReader.java index a301df33..d40e1c1e 100644 --- a/src/one/nio/serial/JsonReader.java +++ b/src/one/nio/serial/JsonReader.java @@ -74,6 +74,9 @@ public final void expect(int b, String message) throws IOException { } public final boolean readBoolean() throws IOException { + if (next == '"') { + return Boolean.parseBoolean(readString()); + } int b = read(); if (b == 't' && read() == 'r' && read() == 'u' && read() == 'e') { return true; @@ -100,6 +103,9 @@ public final int readInt() throws IOException { } public final long readLong() throws IOException { + if (next == '"') { + return Long.parseLong(readString()); + } return Long.parseLong(readNumber()); } @@ -158,9 +164,9 @@ public final int readHexChar() throws IOException { if (b >= '0' && b <= '9') { return b - '0'; } else if (b >= 'A' && b <= 'F') { - return b - 'A'; + return b - 'A' + 10; } else if (b >= 'a' && b <= 'f') { - return b - 'a'; + return b - 'a' + 10; } throw exception("Invalid escape character"); } @@ -193,23 +199,33 @@ public Object readNull() throws IOException { } public String readString() throws IOException { - StringBuilder sb = new StringBuilder(); - expect('\"', "Expected string"); - while (next >= 0 && next != '\"') { - int b = read(); - if ((b & 0x80) == 0) { - sb.append(b == '\\' ? readEscapeChar() : (char) b); - } else if ((b & 0xe0) == 0xc0) { - sb.append((char) ((b & 0x1f) << 6 | (read() & 0x3f))); - } else if ((b & 0xf0) == 0xe0) { - sb.append((char) ((b & 0x0f) << 12 | (read() & 0x3f) << 6 | (read() & 0x3f))); - } else { - int v = (b & 0x07) << 18 | (read() & 0x3f) << 12 | (read() & 0x3f) << 6 | (read() & 0x3f); - sb.append((char) (0xd800 | (v - 0x10000) >>> 10)).append((char) (0xdc00 | (v & 0x3ff))); - } + switch (next) { + case '\"': + StringBuilder sb = new StringBuilder(); + read(); + while (next >= 0 && next != '\"') { + int b = read(); + if ((b & 0x80) == 0) { + sb.append(b == '\\' ? readEscapeChar() : (char) b); + } else if ((b & 0xe0) == 0xc0) { + sb.append((char) ((b & 0x1f) << 6 | (read() & 0x3f))); + } else if ((b & 0xf0) == 0xe0) { + sb.append((char) ((b & 0x0f) << 12 | (read() & 0x3f) << 6 | (read() & 0x3f))); + } else { + int v = (b & 0x07) << 18 | (read() & 0x3f) << 12 | (read() & 0x3f) << 6 | (read() & 0x3f); + sb.append((char) (0xd800 | (v - 0x10000) >>> 10)).append((char) (0xdc00 | (v & 0x3ff))); + } + } + expect('\"', "Unexpected end of string"); + return sb.toString(); + case 'f': + case 'F': + case 't': + case 'T': + return "" + readBoolean(); + default: + return readNumber(); } - expect('\"', "Unexpected end of string"); - return sb.toString(); } public byte[] readBinary() throws IOException { diff --git a/src/one/nio/serial/LongSerializer.java b/src/one/nio/serial/LongSerializer.java index cef512cd..988bc91d 100755 --- a/src/one/nio/serial/LongSerializer.java +++ b/src/one/nio/serial/LongSerializer.java @@ -48,7 +48,7 @@ public void skip(DataStream in) throws IOException { @Override public void toJson(Long obj, StringBuilder builder) { - builder.append(obj.longValue()); + Json.appendLong(builder, obj); } @Override diff --git a/src/one/nio/serial/gen/DelegateGenerator.java b/src/one/nio/serial/gen/DelegateGenerator.java index ce48de31..980fcf2a 100755 --- a/src/one/nio/serial/gen/DelegateGenerator.java +++ b/src/one/nio/serial/gen/DelegateGenerator.java @@ -17,12 +17,7 @@ package one.nio.serial.gen; import one.nio.gen.BytecodeGenerator; -import one.nio.serial.Default; -import one.nio.serial.FieldDescriptor; -import one.nio.serial.JsonName; -import one.nio.serial.NotSerial; -import one.nio.serial.Repository; -import one.nio.serial.SerializeWith; +import one.nio.serial.*; import one.nio.util.Hex; import one.nio.util.JavaFeatures; import one.nio.util.JavaInternals; @@ -38,10 +33,7 @@ import java.io.ObjectOutputStream; import java.lang.invoke.MethodHandleInfo; import java.lang.invoke.MethodType; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; +import java.lang.reflect.*; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; @@ -359,6 +351,10 @@ private static void generateToJson(ClassVisitor cv, Class cls, FieldDescriptor[] mv.visitMethodInsn(INVOKESTATIC, "one/nio/serial/Json", "appendChar", "(Ljava/lang/StringBuilder;C)V", false); mv.visitVarInsn(ALOAD, 2); break; + case Long: + mv.visitMethodInsn(INVOKESTATIC, "one/nio/serial/Json", "appendLong", "(Ljava/lang/StringBuilder;J)V", false); + mv.visitVarInsn(ALOAD, 2); + break; default: mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", srcType.appendSignature(), false); } @@ -391,6 +387,13 @@ private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor // Create instance mv.visitTypeInsn(NEW, Type.getInternalName(cls)); + Constructor constructor = JavaInternals.findConstructor(cls); + + if (constructor != null && constructor.getAnnotation(Before.class) != null) { + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL,Type.getInternalName(cls) , "", "()V", false); + } + // Prepare a multimap (fieldHash -> fds) for lookupswitch TreeMap fieldHashes = new TreeMap<>(); boolean isRecord = JavaFeatures.isRecord(cls); diff --git a/test/one/nio/serial/JsonReaderTest.java b/test/one/nio/serial/JsonReaderTest.java index bd57be0a..53737859 100644 --- a/test/one/nio/serial/JsonReaderTest.java +++ b/test/one/nio/serial/JsonReaderTest.java @@ -108,10 +108,56 @@ public void nullableFields() throws IOException, ClassNotFoundException { Assert.assertTrue(x.set instanceof Set && x.set.isEmpty()); } + @Test + public void unicodeEscaping() throws IOException, ClassNotFoundException { + String s = "{\"string2\":\"A\u003D\u003dZ\"}"; + JsonReader reader = new JsonReader(s.getBytes()); + Custom x = reader.readObject(Custom.class); + Assert.assertEquals("A==Z", x.string2); + } + + @Test + public void shouldReadBooleansAndNumbersForStringField() throws IOException, ClassNotFoundException { + String s = "{\"string2\": null}"; + JsonReader reader = new JsonReader(s.getBytes()); + Custom x = reader.readObject(Custom.class); + Assert.assertNull(x.string2); + + s = "{\"string2\": false}"; + reader = new JsonReader(s.getBytes()); + x = reader.readObject(Custom.class); + Assert.assertEquals("false", x.string2); + + s = "{\"string2\": true}"; + reader = new JsonReader(s.getBytes()); + x = reader.readObject(Custom.class); + Assert.assertEquals("true", x.string2); + + s = "{\"string2\": 123}"; + reader = new JsonReader(s.getBytes()); + x = reader.readObject(Custom.class); + Assert.assertEquals("123", x.string2); + } + + @Test + public void shouldReadStringForBooleanAndNumberField() throws IOException, ClassNotFoundException { + String s = "{\"aBoolean\": \"true\"}"; + JsonReader reader = new JsonReader(s.getBytes()); + Custom x = reader.readObject(Custom.class); + Assert.assertTrue(x.aBoolean); + + s = "{\"longValue\": \"123\"}"; + reader = new JsonReader(s.getBytes()); + x = reader.readObject(Custom.class); + Assert.assertEquals(123L, (long)x.longValue); + } + static class Custom implements Serializable { int intValue; Long longValue = -77L; final String string = "zzz"; + String string2; + boolean aBoolean; Object[] FB = {"A", true}; Set set = Collections.emptySet(); final Map Ea = new HashMap() {{ diff --git a/test/one/nio/serial/JsonTest.java b/test/one/nio/serial/JsonTest.java index 4fed2247..aef06684 100755 --- a/test/one/nio/serial/JsonTest.java +++ b/test/one/nio/serial/JsonTest.java @@ -16,6 +16,9 @@ package one.nio.serial; +import org.junit.Assert; +import org.junit.Test; + import java.io.IOException; import java.io.Serializable; import java.util.Arrays; @@ -37,8 +40,45 @@ public static void main(String[] args) throws IOException { System.out.println(Json.toJson(object)); } + @Test + public void testBefore() throws IOException, ClassNotFoundException { + BeforeTestWith beforeTest = Json.fromJson("{}", BeforeTestWith.class); + Assert.assertEquals("foo", beforeTest.name); + + BeforeTestWithoutAnnotation beforeTestWithout = Json.fromJson("{}", BeforeTestWithoutAnnotation.class); + Assert.assertNull(beforeTestWithout.name); + } + + @Test + public void testSerializeLongAsString() throws IOException, ClassNotFoundException { + TestObject obj = new TestObject(); + obj.longField = Long.MAX_VALUE; + String test = Json.toJson(obj); + Assert.assertTrue(test.contains("\"9223372036854775807\"")); + Assert.assertEquals(Long.MAX_VALUE, Json.fromJson(test, TestObject.class).longField); + } + public static class TestObject implements Serializable { @JsonName("test_name") public String name; + + public long longField; + } + + public static class BeforeTestWith implements Serializable { + public String name; + + @Before + public BeforeTestWith() { + name = "foo"; + } + } + + public static class BeforeTestWithoutAnnotation implements Serializable { + public String name; + + public BeforeTestWithoutAnnotation() { + name = "foo"; + } } }