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
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": "2.0.0-beta.5",
"version": "2.0.0-beta.8",
"description": "Converts JTD into JSON Schema ",
"main": "./dist/bundle.js",
"types": "./dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface V2BaseProperty {
minimum?: number;
maximum?: number;
format?: 'date-time' | 'date' | 'time' | 'uri';
anyOf?: Array<{ $ref: string }>;
anyOf?: Array<{ $ref: string } | { oneOf: Array<{ const: any; title?: string }> }>;
items?: V2BaseProperty;
properties?: Record<string, V2BaseProperty>;
required?: string[];
Expand Down
58 changes: 58 additions & 0 deletions src/v2/generateUISchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,60 @@ import {
groupFieldsBySection
} from './utils';

/**
* Validates V2 schema structure for known issues
*/
const validateV2Schema = (schema: V2Schema): void => {
const invalidFields: string[] = [];
const supportedFieldTypes = ['TEXT', 'NUMERIC', 'DATE_TIME', 'CHOICE_LIST', 'LOCATION', 'COLLECTION', 'ATTACHMENT'];

// Check each field for validity
Object.entries(schema.json.properties).forEach(([fieldName, property]) => {
const uiField = schema.ui.fields[fieldName];

// Skip deprecated fields without UI definitions (they won't be rendered anyway)
if (!uiField && property.deprecated) {
return;
}

if (!uiField) {
invalidFields.push(`${fieldName}: missing UI field definition`);
return;
}

// Check for unsupported field types
if (!supportedFieldTypes.includes(uiField.type)) {
invalidFields.push(`${fieldName}: unsupported field type '${uiField.type}'`);
return;
}

// Check CHOICE_LIST fields for valid structure (not content)
if (uiField.type === 'CHOICE_LIST') {
let hasValidStructure = false;

if (property.type === 'array' && property.items?.anyOf) {
// Check for oneOf arrays in anyOf items for array types (no $ref support)
hasValidStructure = property.items.anyOf.some((anyOfItem: any) =>
anyOfItem.oneOf && Array.isArray(anyOfItem.oneOf) // Empty arrays are valid
);
} else if (property.anyOf) {
// Check direct anyOf structure for string types (no $ref support)
hasValidStructure = property.anyOf.some((anyOfItem: any) =>
anyOfItem.oneOf && Array.isArray(anyOfItem.oneOf) // Empty arrays are valid
);
}

if (!hasValidStructure) {
invalidFields.push(`${fieldName}: CHOICE_LIST field requires embedded oneOf arrays - $ref not supported`);
}
}
});

if (invalidFields.length > 0) {
throw new Error(`Invalid V2 schema structure: ${invalidFields.join(', ')}`);
}
};

/**
* Generates a JSONForms-compatible UI schema from a EarthRanger V2 schema format
*
Expand All @@ -21,8 +75,12 @@ import {
*
* @param schema - EarthRanger V2 schema with json and ui properties
* @returns JSONForms UI schema with single-column VerticalLayout for React Native
* @throws Error if schema has invalid structure or unsupported field configurations
*/
export const generateUISchema = (schema: V2Schema): JSONFormsUISchema => {
// Validate schema structure first
validateV2Schema(schema);

// Get all visible (non-deprecated) fields
const visibleFields = getVisibleFields(schema);

Expand Down
13 changes: 11 additions & 2 deletions src/v2/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ export const mockV2Schema = {
"type": "string",
"anyOf": [
{
"$ref": "http://localhost:3000/api/v2.0/schemas/choices.json?field=patrol_activity"
"oneOf": [
{ "const": "routine", "title": "Routine Patrol" },
{ "const": "emergency", "title": "Emergency Response" },
{ "const": "investigation", "title": "Investigation" }
]
}
]
},
Expand Down Expand Up @@ -84,7 +88,12 @@ export const mockV2Schema = {
"type": "string",
"anyOf": [
{
"$ref": "http://localhost:3000/api/v2.0/schemas/choices.json?field=item_condition"
"oneOf": [
{ "const": "excellent", "title": "Excellent" },
{ "const": "good", "title": "Good" },
{ "const": "fair", "title": "Fair" },
{ "const": "poor", "title": "Poor" }
]
}
]
}
Expand Down
26 changes: 17 additions & 9 deletions src/v2/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,26 @@ export const createControl = (
break;

case 'CHOICE_LIST':
if (uiField.inputType === 'DROPDOWN') {
control.options!.format = 'dropdown';
if (uiField.placeholder) {
control.options!.placeholder = uiField.placeholder;
// Handle multiple choice (array type) first
if (property.type === 'array') {
control.options!.multi = true;
// For arrays, use appropriate multi-select format
if (uiField.inputType === 'DROPDOWN') {
control.options!.format = 'dropdown';
} else if (uiField.inputType === 'LIST') {
control.options!.format = 'checkbox';
}
} else {
// Single select
if (uiField.inputType === 'DROPDOWN') {
control.options!.format = 'dropdown';
} else if (uiField.inputType === 'LIST') {
control.options!.format = 'radio';
}
} else if (uiField.inputType === 'LIST') {
control.options!.format = 'radio';
}

// Handle multiple choice (array type)
if (property.type === 'array') {
control.options!.multi = true;
if (uiField.placeholder) {
control.options!.placeholder = uiField.placeholder;
}
break;

Expand Down