Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions common/mockData/formatterMockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -884,4 +884,81 @@ export const JSON_SCHEMA_DEFAULT_VALUES = '{\n' +
' "type": "textarea"\n' +
' }\n' +
' ]\n' +
'}';

export const JSON_SCHEMA_COMMA_DECIMAL_NUMBERS = '{\n' +
' "schema": {\n' +
' "type": "object",\n' +
' "properties": {\n' +
' "price_with_comma": {\n' +
' "type": "number",\n' +
' "title": "Price with comma decimal",\n' +
' "default": "12,99",\n' +
' "minimum": "5,50",\n' +
' "maximum": "100,00"\n' +
' },\n' +
' "negative_number": {\n' +
' "type": "number",\n' +
' "title": "Negative number with comma",\n' +
' "default": "-15,75"\n' +
' },\n' +
' "regular_number": {\n' +
' "type": "number",\n' +
' "title": "Regular number with period",\n' +
' "default": 25.50,\n' +
' "minimum": 0.01\n' +
' },\n' +
' "string_field": {\n' +
' "type": "string",\n' +
' "title": "String field (should not be affected)",\n' +
' "default": "12,99 text"\n' +
' }\n' +
' }\n' +
' },\n' +
' "definition": [\n' +
' "price_with_comma",\n' +
' "negative_number",\n' +
' "regular_number",\n' +
' "string_field"\n' +
' ]\n' +
'}';

export const JSON_SCHEMA_EDGE_CASE_NUMBERS = '{\n' +
' "schema": {\n' +
' "type": "object",\n' +
' "properties": {\n' +
' "thousands_separator": {\n' +
' "type": "number",\n' +
' "title": "Number with thousands separator (should not convert)",\n' +
' "default": "1.234,56"\n' +
' },\n' +
' "multiple_commas": {\n' +
' "type": "number",\n' +
' "title": "Multiple commas (should not convert)",\n' +
' "default": "1,234,567"\n' +
' },\n' +
' "empty_default": {\n' +
' "type": "number",\n' +
' "title": "Empty default",\n' +
' "default": ""\n' +
' },\n' +
' "null_default": {\n' +
' "type": "number",\n' +
' "title": "Null default",\n' +
' "default": null\n' +
' },\n' +
' "zero_comma": {\n' +
' "type": "number",\n' +
' "title": "Zero with comma",\n' +
' "default": "0,00"\n' +
' }\n' +
' }\n' +
' },\n' +
' "definition": [\n' +
' "thousands_separator",\n' +
' "multiple_commas",\n' +
' "empty_default",\n' +
' "null_default",\n' +
' "zero_comma"\n' +
' ]\n' +
'}'
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@earthranger/react-native-jsonforms-formatter",
"version": "1.0.2",
"version": "1.0.3",
"type": "module",
"description": "Converts JTD into JSON Schema ",
"main": "./dist/bundle.js",
Expand Down
38 changes: 38 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,44 @@ export const isSchemaFieldSet = (definition: any[]) => {

export const isString = (item: any) => typeof item === STRING_TYPE;

export const normalizeDecimalSeparators = (value: string | number): string | number => {
// If it's already a number, return as-is
if (typeof value === 'number') {
return value;
}

// If it's not a string, return as-is
if (typeof value !== 'string') {
return value;
}

// Trim whitespace
const trimmedValue = value.trim();

// If empty string, return as-is
if (trimmedValue === '') {
return value;
}

// Check if it looks like a number with comma decimal separator
// Pattern: optional minus, digits, comma, digits (e.g., "12,34", "-5,678")
const commaDecimalPattern = /^-?\d+,\d+$/;

if (commaDecimalPattern.test(trimmedValue)) {
// Replace comma with period for decimal separator
const normalizedValue = trimmedValue.replace(',', '.');

// Validate that the result is a valid number
const numValue = parseFloat(normalizedValue);
if (!isNaN(numValue)) {
return normalizedValue;
}
}

// Return original value if no conversion needed or possible
return value;
};

// Helper function to recursively traverse and process the schema
export const traverseSchema = (
schema: any,
Expand Down
31 changes: 31 additions & 0 deletions src/validateJsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isRequiredProperty,
isSchemaFieldSet,
isString,
normalizeDecimalSeparators,
REQUIRED_PROPERTY,
traverseSchema,
} from './utils/utils';
Expand Down Expand Up @@ -226,6 +227,33 @@ const validateDefinition = (validations: any, item: any, schema: any, parentItem
}
};

const normalizeNumberFields = (schema: any) => {
traverseSchema(schema, (node) => {
if (node.type === 'object' && node.properties) {
Object.entries(node.properties).forEach(([, property]: [string, any]) => {
if (property.type === 'number') {
// Normalize default values
if (property.default !== undefined) {
property.default = normalizeDecimalSeparators(property.default);
}

// Normalize minimum values
if (property.minimum !== undefined) {
property.minimum = normalizeDecimalSeparators(property.minimum);
}

// Normalize maximum values
if (property.maximum !== undefined) {
property.maximum = normalizeDecimalSeparators(property.maximum);
}
}
});
}
});

return schema;
};

const validateSchema = (validations: any, schema: any) => {
const { hasInactiveChoices, hasEnums } = validations;
if (hasInactiveChoices) {
Expand Down Expand Up @@ -272,6 +300,9 @@ export const validateJSONSchema = (stringSchema: string) => {

const schemaValidations = getSchemaValidations(stringSchema);

// Normalize decimal separators in number fields
normalizeNumberFields(schema.schema);

validateSchema(schemaValidations, schema);

// JSON forms library does not support JSON Type Definition JTD
Expand Down
79 changes: 79 additions & 0 deletions test/JsonFormatter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import {
COLLECTION_FIELD_HEADER_FAKE_DATA,
FIELD_SET_HEADER_FAKE_DATA,
JSON_SCHEMA_COLLECTION_FIELD_FAKE_DATA,
JSON_SCHEMA_COMMA_DECIMAL_NUMBERS,
JSON_SCHEMA_DATE_TIME_FIELD_SETS,
JSON_SCHEMA_DEFAULT_VALUES,
JSON_SCHEMA_DUPLICATED_CHOICES_SINGLE_SELECT_FAKE_DATA,
JSON_SCHEMA_DUPLICATED_REQUIRED_PROPERTIES_FAKE_DATA,
JSON_SCHEMA_EDGE_CASE_NUMBERS,
JSON_SCHEMA_EMPTY_CHOICES_FAKE_DATA,
JSON_SCHEMA_FIELD_SETS_FAKE_DATA,
JSON_SCHEMA_ID_$SCHEMA_FAKE_DATA,
Expand All @@ -26,6 +28,7 @@ import expectedUISchema from "../common/mockData/uiSchemaExpectedMock.json";
import expectedFieldSetUISchema from "../common/mockData/uiSchemaFielSetExpectedMock.json";
import { generateUISchema } from "../src/generateUISchema";
import { validateJSONSchema } from "../src/validateJsonSchema";
import { normalizeDecimalSeparators } from "../src/utils/utils";

describe("JSON Schema validation", () => {
it("Special chars should throw an exception", () => {
Expand Down Expand Up @@ -170,6 +173,82 @@ describe("JSON Schema validation", () => {
const validSchema = validateJSONSchema(JSON.stringify(jsonSchema));
expect(validSchema).toMatchObject(expectedSchema);
});

it("Validate comma decimal separators are converted to periods", () => {
const validSchema = validateJSONSchema(JSON_SCHEMA_COMMA_DECIMAL_NUMBERS);

// Check that comma decimals are converted to periods
expect(validSchema.schema.properties.price_with_comma.default).toBe("12.99");
expect(validSchema.schema.properties.price_with_comma.minimum).toBe("5.50");
expect(validSchema.schema.properties.price_with_comma.maximum).toBe("100.00");

// Check negative numbers
expect(validSchema.schema.properties.negative_number.default).toBe("-15.75");

// Check that regular numbers are unchanged
expect(validSchema.schema.properties.regular_number.default).toBe(25.50);
expect(validSchema.schema.properties.regular_number.minimum).toBe(0.01);

// Check that string fields are not affected
expect(validSchema.schema.properties.string_field.default).toBe("12,99 text");
});

it("Validates edge cases for decimal separator conversion", () => {
const validSchema = validateJSONSchema(JSON_SCHEMA_EDGE_CASE_NUMBERS);

// Should NOT convert numbers with thousands separators (European format: 1.234,56)
expect(validSchema.schema.properties.thousands_separator.default).toBe("1.234,56");

// Should NOT convert numbers with multiple commas (thousands: 1,234,567)
expect(validSchema.schema.properties.multiple_commas.default).toBe("1,234,567");

// Should leave empty defaults unchanged
expect(validSchema.schema.properties.empty_default.default).toBe("");

// Should leave null defaults unchanged
expect(validSchema.schema.properties.null_default.default).toBe(null);

// Should convert simple comma decimal (0,00 -> 0.00)
expect(validSchema.schema.properties.zero_comma.default).toBe("0.00");
});
});

describe("Decimal separator normalization", () => {
it("Converts comma decimal separators to periods", () => {
expect(normalizeDecimalSeparators("12,99")).toBe("12.99");
expect(normalizeDecimalSeparators("-15,75")).toBe("-15.75");
expect(normalizeDecimalSeparators("0,01")).toBe("0.01");
expect(normalizeDecimalSeparators("1000,50")).toBe("1000.50");
});

it("Leaves period decimal separators unchanged", () => {
expect(normalizeDecimalSeparators("12.99")).toBe("12.99");
expect(normalizeDecimalSeparators("-15.75")).toBe("-15.75");
expect(normalizeDecimalSeparators("0.01")).toBe("0.01");
});

it("Leaves numbers unchanged", () => {
expect(normalizeDecimalSeparators(12.99)).toBe(12.99);
expect(normalizeDecimalSeparators(-15.75)).toBe(-15.75);
expect(normalizeDecimalSeparators(0)).toBe(0);
});

it("Does not affect strings that are not decimal numbers", () => {
expect(normalizeDecimalSeparators("12,99 text")).toBe("12,99 text");
expect(normalizeDecimalSeparators("text 12,99")).toBe("text 12,99");
expect(normalizeDecimalSeparators("12,99,00")).toBe("12,99,00"); // Multiple commas
expect(normalizeDecimalSeparators("abc")).toBe("abc");
expect(normalizeDecimalSeparators("")).toBe("");
expect(normalizeDecimalSeparators(" ")).toBe(" ");
});

it("Handles edge cases properly", () => {
expect(normalizeDecimalSeparators(null)).toBe(null);
expect(normalizeDecimalSeparators(undefined)).toBe(undefined);
expect(normalizeDecimalSeparators(" 12,99 ")).toBe("12.99"); // Trims whitespace
expect(normalizeDecimalSeparators("12,")).toBe("12,"); // No digits after comma
expect(normalizeDecimalSeparators(",99")).toBe(",99"); // No digits before comma
});
});

describe("JSON UI Schema generation", () => {
Expand Down