Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
<artifactId>vaadin-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.0.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
108 changes: 108 additions & 0 deletions src/main/java/com/flowingcode/vaadin/jsonmigration/JsonMigration.java
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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}.
*
* <p>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.
*
* <p>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);
}

}
Original file line number Diff line number Diff line change
@@ -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);

}
Original file line number Diff line number Diff line change
@@ -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());
}
}

}
Original file line number Diff line number Diff line change
@@ -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);
}

}