Type Conversion Between Java and TypeScript
TypeScript to Java
When calling a Java endpoint method from TypeScript, ConnectClient serializes TypeScript call parameters to JSON and sends them to Java backend where they are deserialized into Java types using the Jackson JSON processing library. The return value of the Java endpoint method is sent back to TypeScript through the same pipeline in the opposite direction.
The default Vaadin JSON ObjectMapper closely follows the Spring Boot auto-configuration defaults. One notable difference is that in Vaadin, the default object mapper is configured to discover private properties. I.e. all the fields, getters, setters or constructors are discoverable even if they are declared as private. This is done in order to make serialization / deserialization of custom objects easier.
The visibility level of the default ObjectMapper can be configured by setting the spring.jackson.visibility property (in common application properties). Other properties of the default ObjectMapper can be customized by following the Spring Boot documentation on the subject. Alternatively, the entire ObjectMapper can be replaced with a custom one by providing an ObjectMapper bean with the qualifier defined in com.vaadin.connect.VaadinConnectController#VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER.
The default ObjectMapper always converts TypeScript values to JSON object before sending them to the backend, so that the values need to be compliant with the JSON specification which only accepts values from the following types: string, number, array, boolean, JSON object or null. This implies that NaN and Infinity are non-compliant. If sent, the server will return an error response (400 Bad Request). Sending undefined parameter from TypeScript results as default values for primitive types, null for Java object, or Optional.empty() for Optional.
The default conversion rules are summarized as follows (the TypeScript compliant values are converted to the corresponding values, otherwise the backend returns an error message):
To receive primitive types in Java
Type boolean:
TypeScript compliant values:
A boolean value:
trueβtrueandfalseβfalse
Noncompliant values:
Any value that is not a valid
booleantype in TypeScript.
Type char:
TypeScript compliant values:
A single character string:
'a'β'a'
Noncompliant values:
Any string value that has more than one characters.
Any value that is not a valid
stringtype in TypeScript.
UTF-16 and Unicode: Both Java and TypeScript internally use UTF-16 for string encoding. This makes string conversion between backend and frontend trivial. However, using UTF-16 has its limitations and corner cases. Most notably, a string like
"π₯"might seem like a single-character which can be passed to Java as achar. However, both in TypeScript and Java it is actually a two-character string (because theU+1F951symbol takes 2 characters in UTF-16:\uD83E\uDD51). Thus, it is not a valid value for the Javachartype.
Type byte:
TypeScript compliant values:
An integer or decimal number in range of
-129 < X < 256:100,100.0and100.9β100
Noncompliant values:
Any value which is not a number in TypeScript.
Any number value which is out of the compliant range.
Overflow number: if TypeScript sends a value which is greater than Javaβs
Byte.MAX_VALUE(28 - 1), the bits gets rolled over. For example, sends a value128(Byte.MAX_VALUE + 1), Java side receives-128(Byte.MIN_VALUE).Underflow number: if Java side expects a
bytevalue but TypeScript sends an underflow number, e.g.-129(Byte.MIN_VALUE - 1), the backend returns an error.
Type short:
TypeScript compliant values:
An integer or decimal number in range of
-216 < X < 216 - 1:100,100.0and100.9β100
Noncompliant values:
Any value which is not a number in TypeScript.
Any number value which is out of the compliant range.
Overflow and underflow numbers are not accepted for
short.
Type int:
TypeScript compliant values:
An integer or decimal number:
100,100.0and100.9β100
Noncompliant values:
Any value which is not a number in TypeScript.
Overflow number: if TypeScript sends a value which is greater than Javaβs
Integer.MAX_VALUE(231 - 1), the bits gets rolled over. For example, sending a value231(Integer.MAX_VALUE + 1), Java side receives-231(Integer.MIN_VALUE).Underflow number: it is vice versa with overflow number. Sending
-231 - 1(Integer.MIN_VALUE - 1), Java side gets231 - 1(Integer.MAX_VALUE).
Type long:
TypeScript compliant values:
An integer or decimal number:
100,100.0and100.9β100
Noncompliant values:
Any value which is not a number in TypeScript.
Overflow and underflow numbers: bits get rolled over when receiving overflow/underflow number i.e.
263β-263,-263 - 1β263 - 1
Type float and double:
TypeScript compliant values:
An integer or decimal number:
100and100.0β100.0,100.9β100.9
Noncompliant values:
Any value which is not a number in TypeScript.
Overflow and underflow numbers are converted to
Infinityand-Infinityrespectively.
To receive a String in Java
Any String values are kept the same when sent from TypeScript to Java backend.
To receive date time types in Java
java.util.Date
TypeScript compliant values:
A string that represents an epoch timestamp in milliseconds:
'1546300800000'is converted to ajava.util.Dateinstance which contains value of the date2019-01-01T00:00:00.000+0000.
Noncompliant values:
A non-number string:
'foo'
java.time.Instant
TypeScript compliant values:
A string that represents an epoch timestamp in seconds:
'1546300800'is converted to ajava.time.Instantinstance which contains value of the2019-01-01T00:00:00Z.
Noncompliant values:
A non-number string:
'foo'
java.time.LocalDate
TypeScript compliant values:
A string which follows the
java.time.format.DateTimeFormatter#ISO_LOCAL_DATEformatyyyy-MM-dd:'2018-12-16','2019-01-01'.
Noncompliant values:
An incorrect format string:
'foo'
java.time.LocalDateTime
TypeScript compliant values:
A string which follows the
java.time.format.DateTimeFormatter#ISO_LOCAL_DATE_TIMEformat:With full time:
'2019-01-01T12:34:56'Without seconds:
'2019-01-01T12:34'With full time and milliseconds:
'2019-01-01T12:34:56.78'
Noncompliant values:
An incorrect format string:
'foo'
To receive an Enum in Java
TypeScript compliant value:
A string with the same name as an enum: assume that we have an [enum-declaration], then sending
"FIRST"from TypeScript would result an instance ofFIRSTwithvalue=1in Java.
public enum TestEnum {
FIRST(1), SECOND(2), THIRD(3);
private final int value;
TestEnum(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}Noncompliant values:
A non-matched string with name of the expected Enum type.
Any other types: boolean, object or array.
To receive an array in Java
TypeScript compliant values:
An array of items with expected type in Java, for example:
Expected in Java
int[]:[1, 2, 3]β[1,2,3],[1.9, 2, 3]β[1,2,3]Expected in Java
String[]:["foo","bar"]β["foo","bar"]Expected in Java
Object[]:["foo", 1, null, "bar"]β["foo", 1, null, "bar"]
Noncompliant values:
A non-array input:
"foo","[1,2,3]",1
To receive a collection in Java
TypeScript compliant values:
An array of items with expected type in Java (or types which can be converted to expected types), for example, if you expected in Java:
Collection<Integer>:[1, 2, 3]β[1,2,3]Collection<String>:["foo","bar"]β["foo","bar"]Set<Integer>:[1, 2, 2, 3, 3, 3]β[1, 2, 3]
Noncompliant values:
A non-array input:
"foo","[1,2,3]",1
To receive a map in Java
TypeScript compliant value:
A TypeScript object with
stringkey and value in expected type in Java. For example: the expected type in Java isMap<String, Integer>, the compliant object in TypeScript should be in type of{ [key: string]: number; }, e.g.{one: 1, two: 2}.
Noncompliant values:
Any value from other types.
Note |
Due to the fact that the TypeScript code is generated from OpenAPI (TypeScript Endpoints Generator) and the OpenAPI specification has a limitation for map type, the map key is always a string in TypeScript.
|
To receive a bean in Java
A bean is parsed from the input JSON object which maps the keys of JSON object to the property name of the bean object. You can also use Jacksonβs annotation to customize your bean object. For more information about the annotations, please have a look at Jackson Annotations.
Example: assume that we have [bean-example], a valid input for the bean looks like
{
"name": "MyBean",
"address": "MyAddress",
"age": 10,
"isAdmin": true,
"customProperty": "customValue"
}
public class MyBean {
public String name;
public String address;
public int age;
public boolean isAdmin;
private String customProperty;
@JsonGetter("customProperty")
public String getCustomProperty() {
return customProperty;
}
@JsonSetter("customProperty")
public void setCustomProperty(String customProperty) {
this.customProperty = customProperty;
}
}Java to TypeScript
The same object mapper used when converting from TypeScript to Java deserializes the return values in Java to the corresponding JSON object before sending them to client-side.
Serialization can be customized by using annotations to the object to serialize as described in the Customizing Serialization article.
Type number
All the Java types which extend java.lang.Number are deserialized to number in TypeScript. There are a few exceptional cases with extremely large or small numbers. The safe integer range is from -(253 - 1) to 253 - 1. It means only numbers in this range can be represented exactly and correctly compared them (more information about safe integer).
Practically, not all long number in Java can be converted correctly in TypeScript since its range is -263 to 263 - 1. The unsafe numbers are rounded using the rules defined in IEEE-754 standard.
The special values such as NaN, POSITIVE_INFINITY and NEGATIVE_INFINITY are converted into string when sent to TypeScript.
Type string
The primitive type char, its boxed type Character and String in Java are converted to string type in TypeScript.
Array of items
Normal array types such as int[], MyBean[] and all the types which implement or extend java.lang.Collection becomes array when they are sent to TypeScript.
Object
Any kinds of objects in Java are converted to corresponding defined types in TypeScript. For example, if your endpoint methods returns a MyBean type, so when you called the method, you will receive an object in type of MyBean. In case of the generator canβt get information about your bean, it returns an object in any.
Map
All types which inherit from java.lang.Map becomes objects in TypeScript with string keys and values in corresponding type. For instance: Map<String, Integer> β { [key: string]: number; }.
Datetime
By default, the ObjectMapper converts Javaβs date time to a string in TypeScript with the following formats:
java.util.Dateof00:00:00 January 1st, 2019β'2019-01-01T00:00:00.000+0000'java.time.Instantof00:00:00 January 1st, 2019β'2019-01-01T00:00:00Z'java.time.LocalDateof00:00:00 January 1st, 2019β'2019-01-01'java.time.LocalDateTimeof00:00:00 January 1st, 2019β'2019-01-01T00:00:00'