Data Types
Each non-reference entity attribute is associated with an implementation of the Datatype
interface. This interface defines methods for converting attribute values to and from strings (formatting and parsing) when displaying entities in User Interface and serializing in Generic REST.
The framework provides a set of Datatype
implementations corresponding to standard data types of entity attributes.
We use term datatype in all lowercase to refer to implementations of the Datatype interface.
|
Localized Format Strings
Many standard datatypes use a set of format strings defined in the message bundle. It enables formatting and parsing dependent on the current user locale. The default set of format strings defined in the framework is the following:
# Date/time formats
dateFormat = dd/MM/yyyy
dateTimeFormat = dd/MM/yyyy HH:mm
offsetDateTimeFormat = dd/MM/yyyy HH:mm Z
timeFormat = HH:mm
offsetTimeFormat = HH:mm Z
# Number formats
integerFormat = #,##0
doubleFormat = #,##0.###
decimalFormat = #,##0.##
# Number separators
numberDecimalSeparator = .
numberGroupingSeparator = ,
# Booleans
trueString = True
falseString = False
To provide your own format strings, add the corresponding messages to the message bundle of your application. For example, to use the United States date format with the English locale, add the following lines to your messages_en.properties
file:
dateFormat = MM/dd/yyyy
dateTimeFormat = MM/dd/yyyy HH:mm
offsetDateTimeFormat = MM/dd/yyyy HH:mm Z
Alternatively, define a separate en_US
locale and set the data format strings in the messages_en_US.properties
file.
You can configure data format strings using Studio: open the Locales tab of the Project Properties window and click Show data format strings checkbox. |
Customized Formatting and Parsing
You can customize formatting and parsing of values for particular entity attributes by creating your own datatype and assigning it to the attributes.
Suppose that some entity attributes in your application store calendar years, represented by integer numbers. Users should be able to view and edit a year, and if a user enters just two digits, the application should transform it to a year between 2000 and 2100. Otherwise, the whole entered number should be accepted as a year.
First, create the Datatype
implementation class and annotate it with @DatatypeDef
:
import com.google.common.base.Strings;
import io.jmix.core.metamodel.annotation.DatatypeDef;
import io.jmix.core.metamodel.annotation.Ddl;
import io.jmix.core.metamodel.datatype.Datatype;
import javax.annotation.Nullable;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.Locale;
@DatatypeDef(
id = "year", (1)
javaClass = Integer.class (2)
)
@Ddl("int")
public class YearDatatype implements Datatype<Integer> {
private static final String PATTERN = "##00";
@Override
public String format(@Nullable Object value) { (3)
if (value == null)
return "";
DecimalFormat format = new DecimalFormat(PATTERN);
return format.format(value);
}
@Override
public String format(@Nullable Object value, Locale locale) { (4)
return format(value);
}
@Nullable
@Override
public Integer parse(@Nullable String value) throws ParseException { (5)
if (Strings.isNullOrEmpty(value))
return null;
DecimalFormat format = new DecimalFormat(PATTERN);
int year = format.parse(value).intValue();
if (year > 2100 || year < 0)
throw new ParseException("Invalid year", 0);
if (year < 100)
year += 2000;
return year;
}
@Nullable
@Override
public Integer parse(@Nullable String value, Locale locale) throws ParseException { (6)
return parse(value);
}
}
1 | A unique identifier of the datatype. |
2 | Java class handled by the datatype. |
3 | Formatting without current user’s locale. This method is called for system-level conversion. |
4 | Formatting considering current user’s locale. This method is called in the UI. |
5 | Parsing without current user’s locale. This method is called for system-level conversion. |
6 | Parsing considering current user’s locale. This method is called in the UI. |
After creating a Datatype
implementation, you can specify it for an entity attribute using the @PropertyDatatype annotation:
@PropertyDatatype("year")
@Column(name = "YEAR_")
private Integer productionYear;
You cannot inject other beans like Messages directly into the datatype class using Instead, inject |
Support for Custom Java Type
You can use a custom Java class as a type of entity attributes.
Suppose that you have created a Java class representing a geographical coordinate:
import java.io.Serializable;
import java.util.Objects;
public class GeoPoint implements Serializable {
public final double latitude;
public final double longitude;
public GeoPoint(double latitude, double longitude) {
this.latitude = latitude;
this.longitude = longitude;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GeoPoint that = (GeoPoint) o;
return Double.compare(that.latitude, latitude) == 0 &&
Double.compare(that.longitude, longitude) == 0;
}
@Override
public int hashCode() {
return Objects.hash(latitude, longitude);
}
}
Now you want to use this class as a type of JPA entity attribute.
First, create a JPA converter for your class:
import com.company.demo.entity.GeoPoint;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@Converter(autoApply = true) (1)
public class GeoPointConverter implements AttributeConverter<GeoPoint, String> {
@Override
public String convertToDatabaseColumn(GeoPoint attribute) {
if (attribute == null)
return null;
return attribute.latitude + "|" + attribute.longitude;
}
@Override
public GeoPoint convertToEntityAttribute(String dbData) {
if (dbData == null)
return null;
String[] strings = dbData.split("\\|");
return new GeoPoint(Double.parseDouble(strings[0]), Double.parseDouble(strings[1]));
}
}
1 | With autoApply = true you don’t need to specify the converter on each attribute. The converter will be applied for all attributes of the corresponding type. |
Then create a Datatype
implementation class for GeoPoint
and annotate it with @DatatypeDef
:
import com.company.demo.entity.GeoPoint;
import io.jmix.core.metamodel.annotation.DatatypeDef;
import io.jmix.core.metamodel.annotation.Ddl;
import io.jmix.core.metamodel.datatype.Datatype;
import javax.annotation.Nullable;
import java.text.ParseException;
import java.util.Locale;
@DatatypeDef(
id = "geoPoint", (1)
javaClass = GeoPoint.class, (2)
defaultForClass = true (3)
)
@Ddl("varchar(255)") (4)
public class GeoPointDatatype implements Datatype<GeoPoint> {
@Override
public String format(@Nullable Object value) { (5)
if (value instanceof GeoPoint) {
return ((GeoPoint) value).latitude + "|" + ((GeoPoint) value).longitude;
}
return null;
}
@Override
public String format(@Nullable Object value, Locale locale) { (6)
return format(value);
}
@Nullable
@Override
public GeoPoint parse(@Nullable String value) throws ParseException { (7)
if (value == null)
return null;
String[] strings = value.split("\\|");
try {
return new GeoPoint(Double.parseDouble(strings[0]), Double.parseDouble(strings[1]));
} catch (Exception e) {
throw new ParseException(String.format("Cannot parse %s as GeoPoint: %s", value, e.toString()), 0);
}
}
@Nullable
@Override
public GeoPoint parse(@Nullable String value, Locale locale) throws ParseException { (8)
return parse(value);
}
}
1 | A unique identifier of the datatype. |
2 | Java class handled by the datatype. |
3 | defaultForClass = true means that the datatype will be automatically applied to all entity attributes of GeoPoint type. |
4 | Using @Ddl annotation, you can specify what SQL type should be used for entity attributes. Studio considers this annotation when it generates database migration scripts. |
5 | Formatting without current user’s locale. This method is called for system-level conversion. |
6 | Formatting considering current user’s locale. This method is called in the UI. |
7 | Parsing without current user’s locale. This method is called for system-level conversion. |
8 | Parsing considering current user’s locale. This method is called in the UI. |
After that, when you define an entity attribute of GeoPoint
type, the framework will use your custom JPA converter and datatype:
@Column(name = "GEO_POINT")
private GeoPoint geoPoint;