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
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@

import com.vaadin.flow.component.page.PendingJavaScriptResult;
import com.vaadin.flow.function.SerializableConsumer;
import com.vaadin.flow.internal.JsonCodec;
import elemental.json.JsonValue;
import java.io.Serializable;
import java.util.concurrent.CompletableFuture;

/**
* A pending result from a JavaScript snippet sent to the browser for evaluation. This interface
Expand Down Expand Up @@ -87,4 +89,94 @@ default void then(SerializableConsumer<JsonValue> resultHandler) {
then(resultHandler, null);
}

/**
* Adds a typed handler that will be run for a successful execution and a
* handler that will be run for a failed execution. One of the handlers will
* be invoked asynchronously when the result of the execution is sent back
* to the server.
* <p>
* Handlers can only be added before the execution has been sent to the
* browser.
*
* @param targetType
* the type to convert the JavaScript return value to, not
* <code>null</code>
* @param resultHandler
* a handler for the return value from a successful execution,
* not <code>null</code>
* @param errorHandler
* a handler for an error message in case the execution failed,
* or <code>null</code> to ignore errors
*/
default <T> void then(Class<T> targetType,
SerializableConsumer<T> resultHandler,
SerializableConsumer<String> errorHandler) {
if (targetType == null) {
throw new IllegalArgumentException("Target type cannot be null");
}
if (resultHandler == null) {
throw new IllegalArgumentException("Result handler cannot be null");
}

SerializableConsumer<JsonValue> convertingResultHandler = value -> resultHandler
.accept(JsonCodec.decodeAs(value, targetType));

then(convertingResultHandler, errorHandler);
}

/**
* Adds a typed handler that will be run for a successful execution. The
* handler will be invoked asynchronously if the execution was successful.
* In case of a failure, no handler will be run.
* <p>
* A handler can only be added before the execution has been sent to the
* browser.
*
* @param targetType
* the type to convert the JavaScript return value to, not
* <code>null</code>
* @param resultHandler
* a handler for the return value from a successful execution,
* not <code>null</code>
*/
default <T> void then(Class<T> targetType,
SerializableConsumer<T> resultHandler) {
then(targetType, resultHandler, null);
}

/**
* Creates a typed completable future that will be completed with the result
* of the execution. It will be completed asynchronously when the result of
* the execution is sent back to the server. It is not possible to
* synchronously wait for the result of the execution while holding the
* session lock since the request handling thread that makes the result
* available will also need to lock the session.
* <p>
* A completable future can only be created before the execution has been
* sent to the browser.
*
* @param targetType
* the type to convert the JavaScript return value to, not
* <code>null</code>
*
* @return a completable future that will be completed based on the
* execution results, not <code>null</code>
*/
<T> CompletableFuture<T> toCompletableFuture(Class<T> targetType);

/**
* Creates an untyped completable future that will be completed with the
* result of the execution. It will be completed asynchronously when the
* result of the execution is sent back to the server.
* <p>
* A completable future can only be created before the execution has been
* sent to the browser.
*
* @return a completable future that will be completed based on the
* execution results, not <code>null</code>
*/
default CompletableFuture<JsonValue> toCompletableFuture() {
return toCompletableFuture(JsonValue.class);
}

}
104 changes: 104 additions & 0 deletions src/main/java/com/flowingcode/vaadin/jsonmigration/JsonCodec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*-
* #%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;
/*
* Copyright 2000-2020 Vaadin Ltd.
*
* 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.
*/

import com.vaadin.flow.component.Component;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.internal.ReflectTools;
import elemental.json.JsonType;
import elemental.json.JsonValue;

/**
* Utility for encoding objects to and from JSON.
* <p>
* Supported types are
* <ul>
* <li>{@link String}
* <li>{@link Boolean} and <code>boolean</code>
* <li>{@link Integer} and <code>int</code>
* <li>{@link Double} and <code>double</code> (<code>NaN</code> and infinity not
* supported)
* <li>{@link JsonValue} and all its sub types
* <li>{@link Element} (encoded as a reference to the element)
* <li>{@link Component} (encoded as a reference to the root element)
* </ul>
*
* <p>
* @author Vaadin Ltd
*/
class JsonCodec {

/**
* Decodes the given JSON value as the given type.
* <p>
* Supported types are {@link String}, {@link Boolean}, {@link Integer},
* {@link Double} and primitives boolean, int, double
*
* @param <T>
* the decoded type
* @param json
* the JSON value
* @param type
* the type to decode as
* @return the value decoded as the given type
* @throws IllegalArgumentException
* if the type was unsupported
*/
@SuppressWarnings("unchecked")
public static <T> T decodeAs(JsonValue json, Class<T> type) {
assert json != null;
if (json.getType() == JsonType.NULL && !type.isPrimitive()) {
return null;
}
Class<?> convertedType = ReflectTools.convertPrimitiveType(type);
if (type == String.class) {
return type.cast(json.asString());
} else if (convertedType == Boolean.class) {
return (T) convertedType.cast(Boolean.valueOf(json.asBoolean()));
} else if (convertedType == Double.class) {
return (T) convertedType.cast(Double.valueOf(json.asNumber()));
} else if (convertedType == Integer.class) {
return (T) convertedType
.cast(Integer.valueOf((int) json.asNumber()));
} else if (JsonValue.class.isAssignableFrom(type)) {
return type.cast(json);
} else {
throw new IllegalArgumentException(
"Unknown type " + type.getName());
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import elemental.json.JsonArray;
import elemental.json.JsonObject;
import elemental.json.JsonValue;
import java.util.concurrent.CompletableFuture;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
Expand Down Expand Up @@ -156,13 +157,40 @@ private static SerializableConsumer wrap(SerializableConsumer<JsonValue> resultH
return (SerializableConsumer<JsonNode>) node -> resultHandler.accept(convertToJsonValue(node));
};

private static <T> T decodeAs(JsonNode node, Class<T> type) {
return JsonCodec.decodeAs(convertToJsonValue(node), type);
}

@Override
@SuppressWarnings("unchecked")
public void then(SerializableConsumer<JsonValue> resultHandler,
SerializableConsumer<String> errorHandler) {
delegate.then(wrap(resultHandler), errorHandler);
}

@Override
@SuppressWarnings("unchecked")
public <T> void then(Class<T> targetType, SerializableConsumer<T> resultHandler,
SerializableConsumer<String> errorHandler) {
if (targetType != null && JsonValue.class.isAssignableFrom(targetType)) {
delegate.then(JsonNode.class, wrap(value->{
resultHandler.accept(JsonCodec.decodeAs(value, targetType));
}), errorHandler);
} else {
delegate.then(targetType, resultHandler, errorHandler);
}
}

@Override
public <T> CompletableFuture<T> toCompletableFuture(Class<T> targetType) {
if (JsonValue.class.isAssignableFrom(targetType)) {
return delegate.toCompletableFuture(JsonNode.class)
.thenApply(node -> decodeAs(node, targetType));
} else {
return delegate.toCompletableFuture(targetType);
}
}

}

}