Quantity Formatting And Parsing
- Quantity Formatting And Parsing
- Terms and Concepts
- Persistence
- Using KindOfQuantities to Retrieve Formats
- Examples of Usage
- Numeric Format
- Composite Format
- Parsing Values
- Using a FormatsProvider
- Retrieving a FormatProp, and a PersistenceUnit with only a KindOfQuantity Name and through schemas
- Using a MutableFormatsProvider
- Using a FormatSetFormatsProvider
- Registering a SchemaFormatsProvider on IModelConnection open
- Mathematical Operation Parsing
The @itwin/core-quantity package contains classes for quantity formatting and parsing. For detailed API documentation, see our iTwin.js reference documentation.
If you're developing a frontend application that takes advantage of the core quantity APIs, also check out the iTwin.js frontend quantity formatting learning section.
Terms and Concepts
Common Terms
- Unit - A named unit of measure which can be located by its name or label. The definition of any unit is represented through its UnitProps.
- UnitsProvider - An interface that locates the
UnitPropsfor a unit given name or label. This interface also provides methods for UnitConversion to allow converting from one unit to another. - Unit Family/Phenomenon - A physical quantity that can be measured (e.g., length, temperature, pressure). Only units in the same phenomenon can be converted between.
- Persistence Unit - The unit used to store the quantity value in memory or to persist the value in an editable iModel. iModels define the persistence unit through KindOfQuantity objects.
- KindOfQuantity - An object that defines a persistence unit and presentation formats.
- Format - The display format for the quantity value. For example, an angle may be persisted in radians but formatted and shown to user in degrees.
- CompositeValue - An addition to the format specification that allows the explicit specification of a unit label, it also allows the persisted value to be displayed as up to 4 sub-units. Typical multi-unit composites are used to display
feet'-inches"anddegree°minutes'seconds". - FormatterSpec - Holds the format specification as well as the UnitConversion between the persistence unit and all units defined in the format. This is done to avoid any async calls by the
UnitsProviderduring the formatting process. - ParserSpec - Holds the format specification as well as the UnitConversion between the persistence unit and all other units in the same
Phenomenon. This is done to avoid async calls by theUnitsProviderand to allow users to input quantities in different unit systems than specified. For instance, if a metric unit system is set, a user could enter43inand have the result properly converted to meters. - Formatter - A class that holds methods to format a quantity value into a text string. Given a
FormatterSpecobject — which includes one or more unit definitions, each with their own conversion information and a specifiedFormat— and a single magnitude number, theFormattercan convert this number into a text string, adhering to the properties specified informatTraits. - Parser - A class that holds methods to parse a text string into a single number. Given a
ParserSpecobject containing aFormatUnitsand their unit conversions, as well as an input string, the Parser can either return an objectQuantityParseResultthat contains the magnitude of typenumber, or an objectParseQuantityError.
FormatProps
For a detailed description of all the setting supported by FormatProp see the EC documentation on Format.
Station Format Properties
Station formatting in iTwin.js supports properties that control how values are broken down into major and minor station components:
stationOffsetSize
The stationOffsetSize property specifies the number of decimal places for calculating the station offset magnitude. This must be a positive integer greater than 0. This works with stationBaseFactor to determine the effective station offset using the formula: effective offset = stationBaseFactor * 10^stationOffsetSize.
stationBaseFactor
The stationBaseFactor property provides additional flexibility for station formatting by acting as a multiplier for the base offset calculation. This allows for non-standard station intervals that aren't simple powers of 10. The default value is 1.
Note: The
stationBaseFactorproperty is currently implemented as an iTwin.js-specific extension and is not supported in the native EC (Entity Context) layer. This feature will eventually be incorporated into the ECFormat specification to provide broader compatibility across the iTwin ecosystem.
Station Format Examples
| stationOffsetSize | stationBaseFactor | Value | Effective Offset | Formatted |
|---|---|---|---|---|
| 2 | 1 | 1055.5 | 100 | 10+55.50 |
| 3 | 1 | 1055.5 | 1000 | 1+055.50 |
| 2 | 5 | 1055.5 | 500 | 2+55.50 |
In the examples above:
- With
stationOffsetSize=2andstationBaseFactor=1: effective offset = 1 × 10² = 100 - With
stationOffsetSize=3andstationBaseFactor=1: effective offset = 1 × 10³ = 1000 - With
stationOffsetSize=2andstationBaseFactor=5: effective offset = 5 × 10² = 500
Concepts
Formats Provider
The FormatDefinition interface is an extension of FormatProps to help identify formats.
A FormatsProvider interface helps provide all the necessary Formats for displaying formatted quantity values, while also enabling users to add formats of their own.
A MutableFormatsProvider interface extends the read-only FormatsProvider above by allowing adding or removing Formats to the provider.
The SchemaFormatsProvider takes in a SchemaContext, to provide Formats coming from schemas. The SchemaFormatsProvider also requires a UnitSystemKey passed in to filter the FormatDefinition returned, according to the current unit system set in the SchemaFormatsProvider. When getting a format, the SchemaFormatsProvider will throw an error if it receives a non-valid EC full name.
The FormatSetFormatsProvider is a mutable format provider that manages format definitions within a FormatSet. This provider automatically updates the underlying format set when formats are added or removed, making it ideal for applications that need to persist format changes. It also supports an optional fallback provider to provide formats not found in the format set.
Units Provider
To appropriately parse and output formatted values, a units provider is used to define all available units and provides conversion factors between units. There are several implementations of the UnitsProvider across iTwin.js:
The BasicUnitsProvider holds many common units and their conversions between each other.
The SchemaUnitProvider is used to load unit definitions of schemas from an iModel. This holds more extensive units through the Units schema, while also allowing users to define their own units.
The AlternateUnitLabelsProvider interface allows users to specify a set of alternate labels which may be encountered during parsing of strings. By default only the input unit label and the labels of other units in the same Unit Family/Phenomenon, as well as the label of units in a Composite format are used.
Unit Conversion
Unit conversion is performed through a UnitConversionSpec. These objects are generated by a UnitsProvider, with the implementation determined by each specific provider. During initialization, a ParserSpec or FormatterSpec can ask for UnitConversionSpec objects provided via the UnitsProvider. During parsing and formatting, the specification will retrieve the UnitConversionSpec between the source and destination units to apply the unit conversion.
Parser Behavior
The Parser converts text strings into numeric quantity values by tokenizing the input and matching unit labels to known units. The parsing process follows these steps:
Tokenization: The input string is broken down into tokens representing numbers, unit labels, and mathematical operators (if enabled).
Unit Label Matching: For each unit label token found, the parser attempts to match it against:
- Units explicitly defined in the format specification
- Units from the same phenomenon (unit family) provided by the
UnitsProvider - Alternate labels defined through the
AlternateUnitLabelsProvider
Error Handling: The parser's behavior when encountering unrecognized unit labels depends on the format configuration:
- Unitless Format (no units defined in format): If a unit label is provided but cannot be matched to any known unit, the parser returns
ParseError.UnitLabelSuppliedButNotMatched. This prevents silent failures where typos like "12 im" (instead of "12 in") would incorrectly parse as "12 meters" when the persistence unit is meters. - Format with Units (units explicitly defined): If an unrecognized unit label is provided (e.g., "12 ABCDEF"), the parser falls back to the format's default unit for backward compatibility. For example, with a feet format, "12 ABCDEF" would parse as "12 feet".
- Unitless Format (no units defined in format): If a unit label is provided but cannot be matched to any known unit, the parser returns
Default Unit Behavior: If no unit label is provided in the input (e.g., just "12"), the parser uses the default unit specified in the format. For unitless formats, if the input contains multiple unit labels, the first successfully matched unit becomes the default for subsequent unitless values in the same expression.
Unit Conversion: Once units are matched, the parser applies the appropriate unit conversions to produce a value in the persistence unit specified by the
ParserSpec.
This error handling ensures that parsing errors are caught in unitless format contexts, preventing data corruption from unrecognized or mistyped unit labels, while maintaining backward compatibility for formats with explicitly defined units.
Persistence
We expose APIs and interfaces to support persistence of formats. Different from KindOfQuantity, which enables persistence of formats at the schema level, this section covers persistence at the application level.
FormatSet
FormatSet defines properties necessary to support persistence of a set of Formats.
Each
Formatdefined in aFormatSetneed to be mapped to a valid ECName for a KindOfQuantity. During an application's runtime, theFormatassociated to aKindofQuantitywithin aFormatSetwould take precedence and be used over the default presentation formats of thatKindOfQuantity.
The
unitSystemproperty uses a UnitSystemKey to specify the unit system for the format set. This provides better type safety and leads to less dependency onactiveUnitSysteminIModelApp.quantityFormatter. Tools using the new formatting API can then listen to only theonFormatsChangedevent fromIModelApp.formatsProviderinstead ofIModelApp.quantityFormatter.onActiveUnitSystemChanged.The
formatsproperty accepts either a FormatDefinition or a string reference to another KindOfQuantity. This allows one format to reference another format's definition, reducing duplication when multiple KindOfQuantities should share the same format specification. For example,"AecUnits.LENGTH": "RoadRailUnits.LENGTH"allowsAecUnits.LENGTHto use the same format fromRoadRailUnits.LENGTH.
The naming convention for a valid format within a FormatSet is
: .
Example of a metric-based FormatSet as JSON
Example of a imperial-based FormatSet as JSON
Using KindOfQuantities to Retrieve Formats
Building off of FormatSet, Tools and components that format quantities across applications should be linked to a KindOfQuantity and a Persistence Unit. See Domains for available schemas, including AecUnits and RoadRailUnits, which define many KindOfQuantity values.
The table below lists common measurements with their typical KindOfQuantity and Persistence Unit. This allows tools to request a default KindOfQuantity from IModelApp.formatsProvider and a Persistence Unit from IModelApp.quantityFormatter to create a FormatterSpec for quantity formatting.
| Measurement | Actual KindOfQuantity (EC Full Name) | Persistence Unit |
|---|---|---|
| Length | AecUnits.LENGTH | Units.M |
| Angle | AecUnits.ANGLE | Units.RAD |
| Area | AecUnits.AREA | Units.SQ_M |
| Volume | AecUnits.VOLUME | Units.CUB_M |
| Latitude/Longitude | AecUnits.ANGLE | Units.RAD |
| Coordinate | AecUnits.LENGTH_COORDINATE | Units.M |
| Stationing | RoadRailUnits.STATION | Units.M |
| Length (Survey Feet) | RoadRailUnits.LENGTH | Units.M |
| Bearing | RoadRailUnits.BEARING | Units.RAD |
| Weight | AecUnits.WEIGHT | Units.KG |
| Time | AecUnits.TIME | Units.S |
Examples of Usage
Numeric Format
The example below uses a simple numeric format and generates a formatted string with 4 decimal place precision. For numeric formats there is no conversion to other units; the unit passed in is the unit returned with the unit label appended if showUnitLabel trait is set.
Example Code
Composite Format
For the composite format below, we provide a unit in meters and produce a formatted string showing feet and inches to a precision of 1/8th inch.
Example Code
Parsing Values
Example Code
Using a FormatsProvider
The example below uses the SchemaFormatsProvider, an implementation of a FormatsProvider, found in ecschema-metadata to format values associated with the length of an object.
Example of Formatting
The example below uses the SchemaFormatsProvider, an implementation of a FormatsProvider, found in ecschema-metadata to parse values associated with the length of an object.
Example of Parsing
When retrieving a format from a schema, users might want to ensure the format they get matches the unit system they are currently using. They can either pass in the unit system on initialization, or change them after initialization, like so:
Example of Formatting with Unit System
Retrieving a FormatProp, and a PersistenceUnit with only a KindOfQuantity Name and through schemas
When working with formats, developers often need to retrieve a format and determine the appropriate persistence unit. When you only have a KindOfQuantity name, you can utilize a SchemaContext to find the schema item for that KindOfQuantity and then access its persistence unit:
Using a SchemaContext to get KindOfQuantity and a persistence unit
const formatsProvider = new SchemaFormatsProvider(schemaContext, "metric");
const unitsProvider = new SchemaUnitProvider(schemaContext);
const kindOfQuantityName = "AecUnits.LENGTH";
// Get the format definition
const formatDef = await formatsProvider.getFormat(kindOfQuantityName);
if (!formatDef)
throw new Error(`Format not found for ${kindOfQuantityName}`);
const kindOfQuantity = await schemaContext.getSchemaItem(kindOfQuantityName, KindOfQuantity);
if (!kindOfQuantity)
throw new Error(`KindOfQuantity not found for ${kindOfQuantityName}`);
const persistenceUnit = kindOfQuantity.persistenceUnit;
if (!persistenceUnit)
throw new Error(`Persistence unit not found for ${kindOfQuantityName}`);
const persistenceUnitProps = await unitsProvider.findUnitByName(persistenceUnit.fullName);
const format = await Format.createFromJSON(formatDef.name ?? "", unitsProvider, formatDef);
const formatterSpec = await FormatterSpec.create(
formatDef.name ?? "",
format,
unitsProvider, // Use a schema units provider
persistenceUnitProps
);
const _formattedValue = formatterSpec.applyFormatting(123.45);
Using a MutableFormatsProvider
The example below is of a MutableFormatsProvider that lets you add/remove formats during runtime.
Example of a MutableFormatsProvider implementation
Using a FormatSetFormatsProvider
The FormatSetFormatsProvider provides a convenient way to manage formats within a FormatSet while supporting runtime modifications. This provider is particularly useful when you need to persist format changes or override default schema formats.
Key Features:
- String Reference Resolution: The provider now automatically resolves string references to their target FormatDefinition. When a format references another via string (e.g.,
"AecUnits.LENGTH": "RoadRailUnits.LENGTH"), callinggetFormat("AecUnits.LENGTH")will resolve and return the actual FormatDefinition fromRoadRailUnits.LENGTH. - Chain Resolution: Supports chains of references with circular reference detection (e.g., HEIGHT → DISTANCE → LENGTH).
- Cascade Notifications: When adding or removing a format, the
onFormatsChangedevent now includes not only the modified format but also all formats that reference it (directly or indirectly). For example, ifRoadRailUnits.LENGTHis updated and bothAecUnits.LENGTHandLinearAlignment.LENGTHreference it, all three formats will be included in theformatsChangedarray, enabling proper cache invalidation. - Fallback Provider: String references can resolve through the optional fallback provider if the target format isn't found in the format set.
Here's a working example that demonstrates string reference resolution with formatting:
Example of using FormatSetFormatsProvider
Registering a SchemaFormatsProvider on IModelConnection open
The simplest way to get formats from schemas into an iTwin application is to register a new SchemaFormatsProvider through a IModelConnection.onOpen event listener, passing IModelConnection.schemaContext to the provider. The example below illustrates how that can be done.
Example of registering a SchemaFormatsProvider on IModelConnection open
const removeIModelConnectionListener = IModelConnection.onOpen.addListener((iModel: IModelConnection) => {
if (iModel.isBlankConnection()) return; // Don't register on blank connections.
const schemaFormatsProvider = new SchemaFormatsProvider(iModel.schemaContext, IModelApp.quantityFormatter.activeUnitSystem);
const removeUnitSystemListener = IModelApp.quantityFormatter.onActiveFormattingUnitSystemChanged.addListener((args) => {
schemaFormatsProvider.unitSystem = args.system;
});
iModel.onClose.addOnce(() => {
removeUnitSystemListener();
});
IModelApp.formatsProvider = schemaFormatsProvider;
});
IModelConnection.onClose.addOnce(() => {
removeIModelConnectionListener();
IModelApp.resetFormatsProvider();
});
Mathematical Operation Parsing
The quantity formatter supports parsing mathematical operations. The operation is solved, formatting each value present, according to the specified format. This makes it possible to process several different units at once.
Example Code
Limitations
Only plus(+) and minus(-) signs are supported for now.
Other operators will end up returning a parsing error or an invalid input result.
If a Format uses a spacer that conflicts with one of the operators above, additional restrictions will apply:
- Mathematical operations only apply when the operator is in front of whitespace. So
-2FT 6IN + 6INis equal to-2FT-6IN + 6IN, and-2FT-6IN - 6INis not equal to-2FT-6IN- 6IN.
Example
- For a value like
2FT 6IN-0.5, the-sign will be treated as a spacer and not subtraction. However, the0.5value will use the default unit conversion provided to the parser, because it's not a part of the composite unit when that composite is made up of only 2 units -FTandIN.
Example
Last Updated: 24 October, 2025