Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancements for Json support. #70

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

avrecko
Copy link
Contributor

@avrecko avrecko commented Dec 9, 2022

  • on linux unicode problems without + 10 (JsonReader#readHexChar)
  • call no-arg constructor if annotated with @before (needed for e.g. field init)
  • if long value outside javascript safe integer range write it as String
  • support reading long field as String
  • support reading boolean field as String
  • support reading String field as boolean or number

@avrecko
Copy link
Contributor Author

avrecko commented Dec 9, 2022

Hello, I've made some enhancement for Json support. I think if you do One-Nio Json <-> One-Nio Json some of this is not needed. But if there are 3rd party servers/libraries in between this is needed.

As mentioned in an issue. I can get a long field as String {"foo":"123" or "foo":123} imho this should just work out-of-the-box. Also needing to call constructor in some rare cases. On Linux noticed that I need to add + 10 for unicode to work.

Overall pretty cool code, I found it very logical and easy to change. Let me know if the changes make sense to you.

@@ -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) {
Copy link
Member

Choose a reason for hiding this comment

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

|| instead of | would follow the canonical way.
obj < JS_NUMBER_MIN_SAFE_INTEGER || JS_NUMBER_MAX_SAFE_INTEGER < obj might be slightly more readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What are your thoughts on | being potentially slightly faster than || as it allows for parallel evaluation?

I can write it like that, no issues.

Copy link
Member

Choose a reason for hiding this comment

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

I would prefer the idiomatic || way until the performance gain is supported by JMH microbenchmarks. Let JIT do its job :)

src/one/nio/serial/JsonReader.java Show resolved Hide resolved
src/one/nio/serial/JsonReader.java Outdated Show resolved Hide resolved
src/one/nio/serial/JsonReader.java Show resolved Hide resolved
import one.nio.serial.NotSerial;
import one.nio.serial.Repository;
import one.nio.serial.SerializeWith;
import one.nio.serial.*;
Copy link
Member

Choose a reason for hiding this comment

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

Wildcard imports are not welcome.

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.*;
Copy link
Member

Choose a reason for hiding this comment

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

Wildcard imports are not welcome.

test/one/nio/serial/JsonReaderTest.java Show resolved Hide resolved
test/one/nio/serial/JsonReaderTest.java Outdated Show resolved Hide resolved
public long longField;
}

public static class BeforeTestWith implements Serializable {
Copy link
Member

Choose a reason for hiding this comment

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

BeforeTestWithAnnotation?

@incubos incubos self-assigned this Jan 17, 2023
@avrecko
Copy link
Contributor Author

avrecko commented Jan 17, 2023

@incubos thanks for the feedback, after making the pull request I made some further changes. I'll update this pull request with my latest changes just unsure when.

Pretty impressed with the performance and the low level control.

  * on linux unicode problems without + 10 (JsonReader#readHexChar)
  * call no-arg constructor if annotated with @before (needed for e.g. field init)
  * if long value outside javascript safe integer range write it as text
  * support assigning parsable json text for java number and boolean fields
  * support parsing json number and json boolean for java String field
@avrecko
Copy link
Contributor Author

avrecko commented Feb 19, 2023

@incubos Finally got around to it. Committed the changes. I've also addressed inheritance with @Before annotation.

@avrecko
Copy link
Contributor Author

avrecko commented Mar 12, 2023

@incubos I've added some more features.

  • Date parsing. Can parse subset of rfc2616 and iso1806
  • Don't need to implement Serializable for just json serialization. (fwiw: jackson, gson, etc, don't require it)

Maybe you can do a review?

@incubos incubos requested a review from atimofeyev March 24, 2023 10:58
return ZonedDateTime.of(year, month, day, hour, minute, second, nanoseconds, ZoneOffset.UTC).toInstant().toEpochMilli();
}

private static long parseRfc2616(String input) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Rfc2616 is HTTP RFC, did you mean RFC_1123?
ms be we can just call ZonedDateTime.parse("", RFC_1123_DATE_TIME)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am referring to 3.3.1 They specify 3 variants:

Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format

Only supporting the 1st one.

Assert.assertEquals(778841377000L, DateParser.parse("Tue, 06 Sep 1994 08:49:37 GMT"));
Assert.assertEquals(781433377000L, DateParser.parse("Thu, 06 Oct 1994 08:49:37 GMT"));
Assert.assertEquals(784111777000L, DateParser.parse("Sun, 06 Nov 1994 08:49:37 GMT"));
Assert.assertEquals(786703777000L, DateParser.parse("Sun, 06 Dec 1994 08:49:37 GMT"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

this date is incorrect, java parser RFC_1123_DATE_TIME failed on it.
It should be "Tue, 06 Dec 1994 08:49:37 GMT"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't verify if day of week is correct.

Mon, 06 Dec 1994 08:49:37 GMT is the same as Tue, 06 Dec 1994 08:49:37 GMT, same as Wed, 06 Dec 1994 08:49:37 GMT, etc. for the parser. It just looks for month and the day of the month while ignoring the day of week.

Day of week validation can be added. Let me know what you think.

return parseIso1806(input);
}

private static long parseIso1806(String input) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

did you mean ISO-8601?
ZonedDateTime.parse("", ISO_ZONED_DATE_TIME)

could you please tell about use cases where we need support all of it ISO-8601 variants?
ISO_ZONED_DATE_TIME do not support all your variants

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, made a typo with 1806 instead of 8601.

My use case is to parse an output from a C++ program. The C++ program encodes microseconds as part of the ISO8601 style date string. Example date string put in json by the C++ program. "2022-08-18T12:01:27.967875+0200".

@@ -70,6 +75,18 @@ public static void appendObject(StringBuilder builder, Object obj) throws IOExce
}
}

public static void appendLong(StringBuilder builder, long value) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe writing long as string should be configurable (as in JACKSON/GSON)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fine with me.

*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR})
public @interface Before {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it not enough @default annotation on fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, I'll get back to you on this one.

@@ -39,12 +39,15 @@ public class GeneratedSerializer extends Serializer {
static final AtomicInteger renamedFields = new AtomicInteger();
static final AtomicInteger unsupportedFields = new AtomicInteger();

private final boolean jsonOnlySerialization;
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you think about creating separate JsonSerializer with own configuration ?
It seems that json serialization can be extended in the fututre and it whould be to heavy for java serializer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great idea. I'll see what I can come up with.

@incubos
Copy link
Member

incubos commented Mar 28, 2023

@avrecko ResponseTest.testResponseJsonNotSerializable() doesn't pass anymore. Could you please look at it?

Copy link
Member

@incubos incubos left a comment

Choose a reason for hiding this comment

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

There are some comments and a compatibility issue we need to eliminate somehow. But the unit tests are awesome!

src/one/nio/serial/Json.java Outdated Show resolved Hide resolved
}
} else {
serializer = new InvalidSerializer(cls);
Copy link
Member

Choose a reason for hiding this comment

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

Replacement of InvalidSerializer by GeneratedSerializer (JSON-only) looks like a serious behavior change, because GeneratedSerializer implements Serializer methods, but InvalidSerializer throws exceptions. So a user of the upstream version would get an exception if she forgot to implement Serializable, but in this version she would not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I think as suggested by @atimofeyev maybe it makes sense to make a separete JsonSerializer with a bit different behavior. Such as not needing to implement Serializable.

generateRead(cv, cls, fds, defaultFields, className);
generateSkip(cv, fds);

if (!jsonOnly) {
Copy link
Member

Choose a reason for hiding this comment

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

if with a negated condition and both branches present doesn't look idiomatic, don't you think? Can we switch the branches and eliminate negation.

src/one/nio/serial/gen/DelegateGenerator.java Outdated Show resolved Hide resolved
@@ -433,6 +472,19 @@ private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor
// Create instance
mv.visitTypeInsn(NEW, Type.getInternalName(cls));

// support for calling constructor annotated with @Before
for (Class searchClass = cls; searchClass != null; searchClass = searchClass.getSuperclass()) {
Copy link
Member

Choose a reason for hiding this comment

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

The cycle might look tricky to a seasoned reader (I've spent a minute or two to parse the meaning), but I have no suggestions how to simplify that, unfortunately.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I am not a fan of constructor call either. Will go trough a few use cases and see if @Default would work.

}

@Test
public void testBeforeAnnotationSupport() throws IOException, ClassNotFoundException {
Copy link
Member

Choose a reason for hiding this comment

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

It is up to you, but it is safe and might be more concise to use throws Exception in unit tests.

avrecko and others added 2 commits March 28, 2023 19:26
Co-authored-by: Vadim Tsesko <[email protected]>
Co-authored-by: Vadim Tsesko <[email protected]>
@avrecko
Copy link
Contributor Author

avrecko commented Mar 28, 2023

Thank you for doing the review.

I've also added support for not serializing default values. I have not yet synced the changes with this branch.

It does look like it makes a lot of sense to split the Json code in a separete JsonSerializer that is also configurable a little bit.

I'll update the code when I can and we can do the review cycle again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants