f:convertNumber parses to double not BigDecimal
The default behaviour of the f:convertNumber, implemented with NumberConverter, in converting a decimal String of "1.11", is to convert it to a double and then JSF converts that to a
Looking into
You can't easily change the NumberConverter but you can very simply add your own. To do so, I grabbed the source of NumberConverter from Mojarra 2.0.2 and copied all of it into my own class:
And to use:
BigDecimal
. Most of the time you won't notice this happening until you use CallableStatement.setBigDecimal to invoke a stored procedure. In MS SQL Server, you get the following error:com.microsoft.sqlserver.jdbc.SQLServerException: Error converting data type nvarchar to decimal.The cause of this is because inside the BigDecimal it stored "1100000000000000976996261670137755572795867919921875" as a result of converting the
String
to a double
. This floating point error is the motivation for using BigDecimal
in the first place. And of course because the SQL Server JDBC driver didn't know how to handle the BigDecimal properly.Looking into
ConvertNumber
, it uses one of two converters (DecimalFormat
or NumberFormat
) depending on whether 'pattern' is used. In Java 5, they added setParseBigDecimal(boolean newValue) in order to yield the desired output. You can't easily change the NumberConverter but you can very simply add your own. To do so, I grabbed the source of NumberConverter from Mojarra 2.0.2 and copied all of it into my own class:
net.devgrok.BigDecimalConverter
.@FacesConverter(forClass = BigDecimal.class, value="bigdecimal") public class BigDecimalConverter implements Converter, PartialStateHolder { // ... existing code public Object getAsObject(FacesContext context, UIComponent component, String value) { // ... existing code // Create and configure the parser to be used parser = getNumberFormat(locale); if (((pattern != null) && pattern.length() != 0) || "currency".equals(type)) { configureCurrency(parser); } parser.setParseIntegerOnly(isIntegerOnly()); boolean groupSepChanged = false; // BEGIN BigDecimal HACK if (parser instanceof DecimalFormat) { ((DecimalFormat) parser).setParseBigDecimal(true); } // END BigDecimal HACK // ... existing code } // ... existing code }Then just create a file META-INF/custom.taglib.xml:
<?xml version="1.0" encoding="UTF-8"?> <facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd" version="2.0"> <namespace>http://devgrok.net/facestags</namespace> <tag> <tag-name>convertBigDecimal</tag-name> <converter> <converter-id>bigdecimal</converter-id> </converter> </tag> </facelet-taglib>
And to use:
<?xml version="1.0" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:fn="http://java.sun.com/jsp/jstl/functions" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:dg="http://devgrok.net/facestags" > from goes here etc <h:inputText id="txtPrice" value="#{bean.item.price}"> <dg:convertBigDecimal pattern="##0.00" /> </h:inputText> </ui:composition
Christopher,
ReplyDeleteI used a converter as you described above and discovered interesting news when upgrading to Mojarra 2.1.2 recently.
My converter stopped working, and drilling down to the problem I found that Mojarra now contains the following code in NumberConverter::getAsObject():
ValueExpression ve = component.getValueExpression("value");
if (ve != null) {
Class expectedType = ve.getType(context.getELContext());
if (expectedType != null && expectedType.isAssignableFrom(BigDecimal.class)) {
dParser.setParseBigDecimal(true);
}
}
It turns out that BigDecimal actually is natively supported now.
Cheers,
Dominik