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

Implement JSON-B 3 Polymorphism #100

Merged
merged 22 commits into from
Apr 20, 2023
Merged

Conversation

jungm
Copy link
Member

@jungm jungm commented Apr 12, 2023

Copy link
Contributor

@rmannibucau rmannibucau left a comment

Choose a reason for hiding this comment

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

Did a full review now we agreed upon the design, it is mainly a few enhancements of the runtime and get back the user contract on some points but looks very close to be mergeable to me. Very good work and kudo to have enhanced the default polymorphism support which was not as great as we aim at!

@@ -31,6 +31,7 @@
import org.apache.johnzon.mapper.Mapper;
Copy link
Contributor

Choose a reason for hiding this comment

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

guess except the setMappingsFactory the rest of the diff is no more needed?

@@ -78,21 +78,28 @@ private static <T extends Annotation> T getIndirectAnnotation(final Class<T> api
return null;
}

public static <T extends Annotation> T getDirectAnnotation(final Class<?> clazz, final Class<T> api) {
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure why there is a diff in this class, it is already on master and should stay private while the usage should be getAnnotation in all the rest of the diff 🤔

return clazz.isAnnotationPresent(JsonbTypeInfo.class) || !getParentClassesWithTypeInfo(clazz).isEmpty();
}

public List<Map.Entry<String, String>> getPolymorphismPropertiesToSerialize(Class<?> clazz, Collection<String> otherProperties) {
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like the returned type should be an array of pairs (or Map.Entry this part is a detail) instead of a list to make the iteration faster at runtime, wdyt?

}

public List<Map.Entry<String, String>> getPolymorphismPropertiesToSerialize(Class<?> clazz, Collection<String> otherProperties) {
List<Map.Entry<String, String>> entries = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

why not a plain LinkedHashMap which will enable to simplify the "found more than once" check and keep the ordering (will just need to sorted(reverse) when converting to an array), wdyt? Overall idea is to make the intent to provide a partial object (generator usage) more explicit.

Copy link
Member Author

Choose a reason for hiding this comment

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

The "TypeInfo key only once" check can actually be computed in advance, so I kept it as a List.
list.add(0, ...) is way more convenient than converting a LinkedHashMap to a reversed Map.Entry<>[] imho

* @throws JsonbException validation failed
*/
private void validateOnlyOneParentWithTypeInfo(Class<?> classToValidate) {
if (getParentClassesWithTypeInfo(classToValidate).size() > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this looks called too often (at least twice per type if I'm not mistaken, maybe reuse the same instance for both needs?)

@@ -497,57 +497,6 @@ in `MessageDecoder`.
}



### JSON-B Extra
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe keep it and explain:

  1. very high level what is in the spec
  2. how to migrate from extras module to the standard impl

Copy link
Member Author

Choose a reason for hiding this comment

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

don't know if migrating from jsonb-extras to jsonb3 will even be something users want to do - generated JSON format is different ({"_type": "bla", "_value": {"someValue": 1}} vs {"_type": "bla", "someValue": 1})

Don't really want to add support for this format in jsonb module directly. It's out of spec and jsonb-extras was portable and is probably used on top of other jsonb impls
Maybe jsonb-extras shouldn't actually be deleted? It was pretty leightweight anyways

Copy link
Contributor

Choose a reason for hiding this comment

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

:), fine to revert the deletion, while the feature is there all good for me.

Copy link
Member Author

Choose a reason for hiding this comment

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

reverted the deletion + updated docs that extras polymorphism shouldn't be used anymore

}

public Class<?> getTypeToDeserialize(JsonObject jsonObject, Class<?> clazz) {
validateJsonbTypeInfo(clazz);
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be done one per class somehow since it is a runtime class (compared to getPolymorphismPropertiesToSerialize which is a classmapping init computation only) so maybe use ClassMapping to do the validation only one, can likely become a Supplier<Class<?>> using the supplier "pre" part to do the validation and supplier#get to impl the runtime only (current line 91+).

}

JsonValue typeValue = jsonObject.get(typeInfo.key());
if (!(typeValue instanceof JsonString)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

typeValue != null && typeValue.getValueType() != STRING is faster due to JVM instanceof impl (to avoid when possible)

}

String typeValueString = ((JsonString) typeValue).getString();
for (JsonbSubtype subtype : typeInfo.value()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

should be a map for runtime probably

* @param clazz base class
* @return List of classes with JsonbTypeInfo annotation
*/
private List<Class<?>> getParentClassesWithTypeInfo(Class<?> clazz) {
Copy link
Contributor

Choose a reason for hiding this comment

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

wonder if the following pattern isnt more relevant (pseudo code):

final var superClass = clazz.getSuperclass();
if (superClass != null && hasTypeInfo(superClass)) {
  return superClass;
}
return Stream.of(clazz.getInterfaces()).filter(it -> hasTypeInfo(it)).findFirst().orElse(null);

Overall idea is to avoid to create a list to keep only the first element. For interface case it will not change a lot but for parent case it will make it way more efficient. wdyt?

@tandraschko
Copy link
Member

great work Markus, looking forward for this contribution!

Copy link
Contributor

@rmannibucau rmannibucau left a comment

Choose a reason for hiding this comment

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

a small autoformat fix to do and getTypeToDeserialize to make runtime friendly and we are good to me

@@ -512,7 +512,9 @@ This module provides some extension to JSON-B.

#### Polymorphism

This extension provides a way to handle polymorphism:
This extension shouldn't be used anymore if you don't absolutely rely on the JSON format it generates/parses.
Copy link
Contributor

Choose a reason for hiding this comment

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

What about "If your aim is only to be polymorphic you can use the built-in JSON-B feature but some cases remain unhandled and this extension enable them (mainly JSON-B to JSON-B cases like configurations and service to service calls but unlikely javascript to java cases)".

@@ -18,11 +18,16 @@
*/
package org.apache.johnzon.mapper;

import static java.util.Arrays.asList;
Copy link
Contributor

Choose a reason for hiding this comment

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

oops (autoformat)?

return result.toArray(Map.Entry[]::new);
}

public Class<?> getTypeToDeserialize(JsonObject jsonObject, Class<?> clazz) {
Copy link
Contributor

Choose a reason for hiding this comment

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

implemented like that this must not be called at runtime, overall consider any reflection is forbidden at runtime except to discover the passed instance type so this impl should be limited to (pseudo code) typeInfos.get(jsonObject.getString("...")) so the reflection should have been stored in the handler earlier (likely in validate or getPolymorphismPropertiesToSerialize to avoid to do it > once) and the lookup shouldnt be a loop but a map access

Copy link
Member Author

Choose a reason for hiding this comment

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

reworked and reflection free now. Except for Class#getName, is using it okay? Or should we possibly completely move away from having the class as a parameter?

Copy link
Contributor

Choose a reason for hiding this comment

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

this one is only in error case and not so costly so no worries for me

Copy link
Contributor

@rmannibucau rmannibucau left a comment

Choose a reason for hiding this comment

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

Mappings diff can drop (autoformat?) then it looks good to me (early approval to avoid to need another pass just for style).
Very good work, thanks a lot for the effort!

@jungm
Copy link
Member Author

jungm commented Apr 19, 2023

@rmannibucau dropped mappings diff

@rmannibucau rmannibucau merged commit 9080e29 into apache:master Apr 20, 2023
@jungm jungm deleted the jsonb3-polymorphism branch April 20, 2023 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants