You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ja...@apache.org on 2026/06/10 21:00:00 UTC
(grails-core) 01/01: fix: parse ASCII minus in locale-aware number binding
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a commit to branch fix/negative-number-binding-locale
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit a0809320907be8851bc5b875cb7a4a876103e8c4
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Wed Jun 10 16:59:09 2026 -0400
fix: parse ASCII minus in locale-aware number binding
Accept browser-submitted ASCII hyphen-minus when binding numbers for locales whose DecimalFormat minus sign is U+2212. This keeps display formatting unchanged while allowing Long, BigDecimal, and BigInteger converters to parse negative values for nb_NO.
Fixes #15714
Assisted-by: Hephaestus:gpt-5.5 oracle
---
.../web/LocaleAwareNumberConverter.groovy | 17 ++++++-
.../web/LocaleAwareNumberConverterSpec.groovy | 54 ++++++++++++++++++++++
2 files changed, 69 insertions(+), 2 deletions(-)
diff --git a/grails-databinding/src/main/groovy/org/grails/databinding/converters/web/LocaleAwareNumberConverter.groovy b/grails-databinding/src/main/groovy/org/grails/databinding/converters/web/LocaleAwareNumberConverter.groovy
index 407c946cb8..f41494f230 100644
--- a/grails-databinding/src/main/groovy/org/grails/databinding/converters/web/LocaleAwareNumberConverter.groovy
+++ b/grails-databinding/src/main/groovy/org/grails/databinding/converters/web/LocaleAwareNumberConverter.groovy
@@ -18,6 +18,7 @@
*/
package org.grails.databinding.converters.web
+import java.text.DecimalFormat
import java.text.NumberFormat
import java.text.ParsePosition
@@ -56,14 +57,26 @@ class LocaleAwareNumberConverter implements ValueConverter {
@Override
Object convert(Object value) {
def trimmedValue = value.toString().trim()
+ def formatter = numberFormatter
+ def valueToParse = replaceAsciiMinusWithLocaleMinus(formatter, trimmedValue)
def parsePosition = new ParsePosition(0)
- def result = numberFormatter.parse((String) value, parsePosition).asType(getTargetType())
- if (parsePosition.index != trimmedValue.size()) {
+ def result = formatter.parse(valueToParse, parsePosition).asType(getTargetType())
+ if (parsePosition.index != valueToParse.size()) {
throw new NumberFormatException("Unable to parse number [${value}]")
}
result
}
+ protected String replaceAsciiMinusWithLocaleMinus(NumberFormat formatter, String value) {
+ if (formatter instanceof DecimalFormat && value.startsWith('-')) {
+ char minusSign = formatter.decimalFormatSymbols.minusSign
+ if (minusSign != '-' as char) {
+ return String.valueOf(minusSign) + value.substring(1)
+ }
+ }
+ value
+ }
+
protected NumberFormat getNumberFormatter() {
NumberFormat.getInstance(getLocale())
}
diff --git a/grails-databinding/src/test/groovy/org/grails/databinding/converters/web/LocaleAwareNumberConverterSpec.groovy b/grails-databinding/src/test/groovy/org/grails/databinding/converters/web/LocaleAwareNumberConverterSpec.groovy
new file mode 100644
index 0000000000..dc4bc7c19b
--- /dev/null
+++ b/grails-databinding/src/test/groovy/org/grails/databinding/converters/web/LocaleAwareNumberConverterSpec.groovy
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.grails.databinding.converters.web
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class LocaleAwareNumberConverterSpec extends Specification {
+
+ private Locale defaultLocale
+
+ void setup() {
+ defaultLocale = Locale.default
+ }
+
+ void cleanup() {
+ Locale.default = defaultLocale
+ }
+
+ @Unroll
+ void 'converts #targetType.simpleName value with ASCII minus for #locale'() {
+ given:
+ Locale.default = locale
+ converter.targetType = targetType
+
+ expect:
+ converter.convert(value) == expectedValue
+
+ where:
+ locale | converter | targetType | value | expectedValue
+ new Locale('nb', 'NO') | new LocaleAwareNumberConverter() | Long | '-1' | -1L
+ new Locale('nb', 'NO') | new LocaleAwareBigDecimalConverter() | BigDecimal | '-1,25' | new BigDecimal('-1.25')
+ new Locale('nb', 'NO') | new LocaleAwareBigDecimalConverter() | BigInteger | '-1' | new BigInteger('-1')
+ Locale.US | new LocaleAwareNumberConverter() | Long | '-1' | -1L
+ Locale.US | new LocaleAwareBigDecimalConverter() | BigDecimal | '-1.25' | new BigDecimal('-1.25')
+ Locale.US | new LocaleAwareBigDecimalConverter() | BigInteger | '-1' | new BigInteger('-1')
+ }
+}