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 Backoffice UI 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 localized message packs. It enables formatting and parsing dependent on the current user locale. The default set of format strings for the default language 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 pack of your application. For example, to use the United States date format, add the following lines:

messages.properties
dateFormat = MM/dd/yyyy
dateTimeFormat = MM/dd/yyyy HH:mm
offsetDateTimeFormat = MM/dd/yyyy HH:mm Z

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 Backoffice 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 Backoffice 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;

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 a JPA entity attribute.

First, create a JPA converter for your class:

import datamodel.ex1.entity.GeoPoint;

import javax.persistence.AttributeConverter;
import javax.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 io.jmix.core.metamodel.annotation.DatatypeDef;
import io.jmix.core.metamodel.annotation.Ddl;
import io.jmix.core.metamodel.datatype.Datatype;
import datamodel.ex1.entity.GeoPoint;

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 Backoffice 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 Backoffice 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;