diff --git a/README.md b/README.md index 6338d05..de92971 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,11 @@ # Json Migration Helper -This is a template project for building new Vaadin 24 add-ons +Provides a compatibility layer for JSON handling to abstract away breaking changes introduced in Vaadin version 25. ## Features -* List the features of your add-on in here - -## Online demo - -[Online demo here](http://addonsv24.flowingcode.com/json-migration-helper) +Detects the runtime version and uses version-specific helpers to ensure that code calling its methods does not need to be aware of underlying Vaadin API changes. ## Download release diff --git a/pom.xml b/pom.xml index 19d6a42..c3f306b 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,12 @@ vaadin-core true + + tools.jackson.core + jackson-databind + 3.0.0 + true + org.projectlombok lombok diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigration.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigration.java new file mode 100644 index 0000000..947b436 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigration.java @@ -0,0 +1,108 @@ +/*- + * #%L + * Json Migration Helper + * %% + * Copyright (C) 2025 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.jsonmigration; + +import java.lang.reflect.Method; +import com.vaadin.flow.dom.Element; +import com.vaadin.flow.server.Version; +import elemental.json.JsonValue; +import lombok.SneakyThrows; + +/** + * Provides a compatibility layer for JSON handling to abstract away breaking changes + * introduced in Vaadin version 25. + *

+ * This utility class detects the runtime version and uses version-specific helpers + * to ensure that code calling its methods does not need to be aware of underlying + * Vaadin API changes. + * + * @author Javier Godoy + */ +public class JsonMigration { + + private static final JsonMigrationHelper helper = initializeHelper(); + + @SneakyThrows + private static JsonMigrationHelper initializeHelper() { + if (Version.getMajorVersion()>24) { + Class helperType = Class.forName(JsonMigration.class.getName()+"Helper25"); + return (JsonMigrationHelper) helperType.getConstructor().newInstance(); + } else { + return new LegacyJsonMigrationHelper(); + } + } + + private static final Class BASE_JSON_NODE = lookup_BaseJsonNode(); + + private static Class lookup_BaseJsonNode() { + try { + return Class.forName("tools.jackson.databind.node.BaseJsonNode"); + } catch (ClassNotFoundException e) { + return null; + } + } + + /** + * Converts a given Java object into a {@code JsonValue}. + * + *

This method delegates the conversion to a version-specific helper to handle + * any differences in the serialization process. + * + * @param object the object to convert + * @return the {@code JsonValue} representation of the object + */ + public static JsonValue convertToJsonValue(Object object) { + return helper.convertToJsonValue(object); + } + + @SneakyThrows + private static Object invoke(Method method, Object instance, Object... args) { + return helper.invoke(method, instance, args); + } + + + private static Method Element_setPropertyJson = lookup_setPropertyJson(); + + @SneakyThrows + private static Method lookup_setPropertyJson() { + if (Version.getMajorVersion()>24) { + return Element.class.getMethod("setPropertyJson", String.class, BASE_JSON_NODE); + } else { + return Element.class.getMethod("setPropertyJson", String.class, JsonValue.class); + } + } + + /** + * Sets a JSON-valued property on a given {@code Element}, transparently handling + * version-specific method signatures. + * + *

This method uses reflection to call the appropriate {@code setPropertyJson} method + * on the {@code Element} class, which has a different signature for its JSON + * parameter in library versions before and after Vaadin 25. + * + * @param element the {@code Element} on which to set the property + * @param name the name of the property to set + * @param json the {@code JsonValue} to be set as the property's value + */ + public static void setPropertyJson(Element element, String name, JsonValue json) { + invoke(Element_setPropertyJson, element, name, json); + } + +} diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigrationHelper.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigrationHelper.java new file mode 100644 index 0000000..c51f778 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigrationHelper.java @@ -0,0 +1,33 @@ +/*- + * #%L + * Json Migration Helper + * %% + * Copyright (C) 2025 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.jsonmigration; + +import elemental.json.JsonValue; + +import java.lang.reflect.Method; +import com.vaadin.flow.dom.Element; + +interface JsonMigrationHelper { + + JsonValue convertToJsonValue(Object object); + + Object invoke(Method method, Object instance, Object... args); + +} diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigrationHelper25.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigrationHelper25.java new file mode 100644 index 0000000..1aa9697 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigrationHelper25.java @@ -0,0 +1,138 @@ +/*- + * #%L + * Json Migration Helper + * %% + * Copyright (C) 2025 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.jsonmigration; + +import java.lang.reflect.Method; +import java.util.Arrays; +import elemental.json.Json; +import elemental.json.JsonArray; +import elemental.json.JsonObject; +import elemental.json.JsonValue; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.BaseJsonNode; +import tools.jackson.databind.node.JsonNodeFactory; +import tools.jackson.databind.node.ObjectNode; + +@NoArgsConstructor +class JsonMigrationHelper25 implements JsonMigrationHelper { + + @Override + public JsonValue convertToJsonValue(Object object) { + if (object instanceof JsonValue) { + return (JsonValue) object; + } else if (object instanceof JsonNode) { + return convertToJsonValue((JsonNode) object); + } else if (object == null) { + return null; + } else { + throw new ClassCastException( + object.getClass().getName() + " cannot be converted to elemental.json.JsonObject"); + } + } + + @Override + @SneakyThrows + public Object invoke(Method method, Object instance, Object... args) { + Object[] convertedArgs = null; + Class parameterTypes[] = method.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + if (args[i] instanceof JsonValue && parameterTypes[i] == BaseJsonNode.class) { + + if (convertedArgs == null) { + convertedArgs = Arrays.copyOf(args, args.length); + } + convertedArgs[i] = convertToJsonNode((JsonValue) args[i]); + } + } + if (convertedArgs == null) { + convertedArgs = args; + } + return method.invoke(instance, convertedArgs); + } + + private static JsonValue convertToJsonValue(JsonNode jsonNode) { + switch (jsonNode.getNodeType()) { + case OBJECT: + JsonObject jsonObject = Json.createObject(); + JsonObject source = (JsonObject)jsonNode; + for (String key : source.keys()) { + jsonObject.put(key, convertToJsonValue(source.get(key))); + } + return jsonObject; + case ARRAY: + JsonArray jsonArray = Json.createArray(); + for (int i = 0; i < jsonNode.size(); i++) { + jsonArray.set(i, convertToJsonValue(jsonNode.get(i))); + } + return jsonArray; + case STRING: + return Json.create(jsonNode.asText()); + case NUMBER: + return Json.create(jsonNode.asDouble()); + case BOOLEAN: + return Json.create(jsonNode.asBoolean()); + case NULL: + return Json.createNull(); + default: + throw new IllegalArgumentException("Unsupported JsonNode type: " + jsonNode.getNodeType()); + } + } + + private static final JsonNodeFactory nodeFactory = JsonNodeFactory.instance; + + private static BaseJsonNode convertToJsonNode(JsonValue jsonValue) { + switch (jsonValue.getType()) { + case OBJECT: + JsonObject jsonObject = (JsonObject) jsonValue; + ObjectNode objectNode = nodeFactory.objectNode(); + for (String key : jsonObject.keys()) { + objectNode.set(key, convertToJsonNode(jsonObject.get(key))); + } + return objectNode; + + case ARRAY: + JsonArray jsonArray = (JsonArray) jsonValue; + ArrayNode arrayNode = nodeFactory.arrayNode(jsonArray.length()); + for (int i = 0; i < jsonArray.length(); i++) { + arrayNode.set(i, convertToJsonNode(jsonArray.get(i))); + } + return arrayNode; + + case STRING: + return nodeFactory.textNode(jsonValue.asString()); + + case NUMBER: + return nodeFactory.numberNode(jsonValue.asNumber()); + + case BOOLEAN: + return nodeFactory.booleanNode(jsonValue.asBoolean()); + + case NULL: + return nodeFactory.nullNode(); + + default: + throw new IllegalArgumentException("Unsupported JsonValue type: " + jsonValue.getType()); + } + } + +} diff --git a/src/main/java/com/flowingcode/vaadin/jsonmigration/LegacyJsonMigrationHelper.java b/src/main/java/com/flowingcode/vaadin/jsonmigration/LegacyJsonMigrationHelper.java new file mode 100644 index 0000000..425f213 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/jsonmigration/LegacyJsonMigrationHelper.java @@ -0,0 +1,46 @@ +/*- + * #%L + * Json Migration Helper + * %% + * Copyright (C) 2025 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.jsonmigration; + +import java.lang.reflect.Method; +import elemental.json.JsonValue; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; + +@NoArgsConstructor +class LegacyJsonMigrationHelper implements JsonMigrationHelper { + + @Override + public JsonValue convertToJsonValue(Object object) { + if (object instanceof JsonValue) { + return (JsonValue) object; + } else { + throw new ClassCastException( + object.getClass().getName() + " cannot be converted to elemental.json.JsonObject"); + } + } + + @Override + @SneakyThrows + public Object invoke(Method method, Object instance, Object... args) { + return method.invoke(instance, args); + } + +}