Extension for JSR 310 Date & Time API. Fixes #33#121
Extension for JSR 310 Date & Time API. Fixes #33#121EugenCepoi merged 18 commits intoowlike:masterfrom
Conversation
|
Let me see if we can upgrade the entire project to jdk 8. This would hopefully simplify things. Otherwise we will go with your toolchain solution, this will require however configuring travis to use it. |
|
I prepared this PR to support Java 8 in the entire project. |
|
I merged in the changes from #122 and removed the toolchain related parts out of the pom. I had to fix one test case but everything seems to be working now |
aseovic
left a comment
There was a problem hiding this comment.
This looks awesome :-)
Thank you!
genson-java-datetime/README.md
Outdated
| This module provides a genson extension to allow support for classes in the Java Date & Time API (JSR 310) | ||
|
|
||
| #### Installation | ||
| Simply add the JavaDateTimeBundle with the deseried options when creating your Genson instance |
There was a problem hiding this comment.
Good eye. I corrected the typo
genson-java-datetime/README.md
Outdated
|
|
||
| The `asTimeInMillis` property is supported but does not function exactly the same. The value only | ||
| determines whether the bundle will serialize/deserialize in the assigned TimestampFormat. It does not guarantee that the timestamp | ||
| format is actually milliseconds. Use `@TimestampFormat` to control the desired format |
There was a problem hiding this comment.
Is the annotation @TimestampFormat or @JsonTimestampFormat as the following paragraph suggests?
There was a problem hiding this comment.
The timestamp is indeed @JsonTimestampFormat . I have updated the documentation
|
@EugenCepoi @nicky9door Love it, but I do have one general, somewhat philosophical question/comment. Considering that we are now Java 8+ only, should this code really be in a separate module (and should it even be a bundle) considering the fact that it deals with standard JDK types? I mean, we don't have a bundle for numbers, collections, and types such as I can argue that having a bundle is a nice way to group everything together and allow easy configuration, but at the same time it feels a bit weird to treat these types as "optional" and to have to register a bundle in order for them to work. My initial reaction is that this should be part of the core, always available, and that we also need to add support for |
|
@aseovic Thanks for pointing out the issues in the documentation. I have corrected the errors you pointed out. I originally created this as a separate bundle because genson was still using Java 1.7 at the time. Now that we are at Java 8, there is nothing really stopping us from integrating this into the core library from a technical standpoint. A possible benefit from keeping this as a separate module is that it keeps the size of the core library smaller but it does create one more dependency to remember to add to your project. I still think we need to keep the DateTime code as bundle simply to allow users to control how these types are serialized. Though perhaps there is a way of including a default configuration while allowing users to overwrite the configuration explicitly? |
|
Yup, I get it. At the time it was the only way to do it, but now it just seems a bit strange. I'm not worried about the size, and I know @EugenCepoi prefers not having additional JARs/dependencies, so I think merging it into the main project makes sense. I'd actually put it in I hear your configuration argument, but I think we can expose the "official" way to change the configuration on the Anyway, that's just my personal opinion. Let's wait for @EugenCepoi to chime in and then we can either merge it as-is, or move things around as suggested and merge it. |
|
I agree with mostly everything you guys say above. There are already a few date related configs and annotations. I think the ideal solution would be consistent with them (reuse if possible, evolve or replace otherwise). But maybe it's not always possible, like registering only one date format and use it for datetime and the old date, etc. If you are worried of bloating the builder with too much config you could:
I see that you have a nice readme. This doc could probably be merged with the official docs here. Thank you guys for these great contributions :) |
|
My vote goes for option 2, However, instead of adding something like Basically, instead of users writing something like this: builder.withDateTimeConfig(new DateTimeConfig().setFormatter(...))they would do builder.withDateTimeConfig(config -> config.setFormatter(...))and we'd call The difference is subtle, but important, as this approach has several benefits:
We can use the similar approach for other config options in order to clean up the builder API in 2.0 and group relevant config options together. For example, we could have Thoughts? |
|
Most of it sounds good. I've been considering for some time to move bits of the builder options to a structured config. I see you considering only the advantages, let me focus on the disadvantages then :p
Could you expand a bit on 3, more specifically on the part "delegate the existing useDateFormat and useDateAsTimestamp methods to", I wonder concretely how you think the config object will help us there. Thanks :) |
|
First, let me address disadvantages you mentioned:
builder.withDateTimeConfig(new DateTimeConfig().setFormatter(...))is any simpler or easier to understand than builder.withDateTimeConfig(config -> config.setFormatter(...))Ultimately, that's how users would see the difference, and I'd say it's 6 of one or half dozen of the other. I think it is more important that we are consistent and do it the same way for everything -- either approach is simple enough from the end user perspective.
It may not matter much for For example, custom With the approach I suggested modifying the chain becomes much simpler -- I can define a visitor, give it my standard chain once builder.withConverterFactory(factory -> factory.find(NullConverterFactory.class)
.withNext(new SerializationSupportConverter.Factory(this)))The code above will effectively insert my custom The same is true for resolvers -- we need to be able to control ordering, so we can prefer our annotations to any third party annotations (JAXB, JSON-B or Jackson, for example), and we need to make sure that our standard, non-annotation based resolvers are there as a fallback at the very end.
At the moment, nothing prevents you from doing: m_dateTimeBundle = new DateTimeBundle().setFormatter(LocalDate.class, ABC);
Genson genson = new GensonBuilder().withBundle(m_dateTimeBundle).create();
// and then some time later, who knows when...
m_dateTimeBundle.setFormatter(LocalDate.class, XYZ);Ultimately, I don't think it really matters. I think users understand (or at least they will learn the hard way) that they shouldn't modify configuration after If we really care about that, we could provide copy constructors for all of our config objects that would ensure that whatever config objects users may end up holding a reference to are disconnected from the actual config objects Which brings me to my final point from the previous message, the one you seem to be most interested in ;-) Basically, if you have only a config object and expect users to create it and pass it in, delegating existing methods to it is impossible, as the config object could be However, by separating the actual config object from its modifier, we could ensure that we always have a config modifier instance to delegate to: private Function<DateTimeConfig, DateTimeConfig> dateTimeConfigModifier = Function.identity(); // no-op config modifier
// now we *can* do this
public GensonBuilder useDateFormat(DateFormat dateFormat) {
this.dateTimeConfigModifier.andThen(config -> config.setFormatter(Date.class, dateFormat));
return this;
}
public GensonBuilder useDateAsTimestamp(boolean enabled) {
this.dateTimeConfigModifier.andThen(config -> config.setDateAsTimestamp(enabled));
return this;
}We don't have to worry about nulls, we don't have to worry about merging, we don't have to worry about ordering -- all we have to do is apply the modifiers, in the order they were defined both by the builder methods and any configured bundles, simply by doing the following in the DateTimeConfig config = dateTimeConfigModifier.apply(new DateTimeConfig());
// or, if we want config to be truly immutable and we have copy constructor on the config object
DateTimeConfig config = new DateTimeConfig(dateTimeConfigModifier.apply(new DateTimeConfig()));Hope you agree that the benefits far outweigh the downsides. I'd also like to hear what @nicky9door thinks, as he's the one who needs to make the changes to this PR based on all this ;-) |
|
BTW, I've used
@FunctionalInterface
public interface Modifier<T> {
T apply(T obj);
default Modifier<T> andThen(Modifier<T> after) {
Objects.requireNonNull(after);
return t -> after.apply(apply(t));
}
static <T> Modifier<T> identity() {
return t -> t;
}
}
public class DateTimeConfig {
private DateTimeConfig(DateTimeConfig from) {
// copy ctor
}
public static create(Modifier<DateTimeConfig> modifier) {
return new DateTimeConfig(modifier.apply(new DateTimeConfig()));
}
}
DateTimeConfig dtc = DateTimeConfig.create(dateTimeConfigModifier);That way we can a) encapsulate creation logic within config objects and ensure immutability; and b) make it really easy to create various config objects within the builder. |
|
This will be a great addition to Genson. I've been testing this with our product and found that Converter implementations for Duration, ZoneId, and ZoneOffset would be desirable. |
Build note: I was able to get the whole project to compile while calling maven using JDK 1.7 by configuring a toolchain in the genson-java-datetime pom. This will require a 1.8 JDK to be configured in
~/.m2/toolchains.xml
Calling maven with a 1.8 JDK will build this module but genson-scala fails due to Java 1.8 adding additional properties to
TypeVariablewhich are not configured in the scala classes that extend this type.I'm not knowledgeable in scala so I'm not sure what the best solution would be