Parsing and validating
@welldot/core defines what a valid .well file is. The same validators back the JSON Schema, the welldot.org editor and this site's MCP validator, so every tool agrees.
parseWell: text in, Well out
import { parseWell } from '@welldot/core';
const well = parseWell(text);
parseWell(json: string): Well does four things, in order:
- Parses the JSON text.
- Reads
versionand decides how to read the file, following parser version handling:1is upgraded to v2,2and any"2.x"are read as v2, a missing version is read as v2, anything else throws anErrornaming the value (Unsupported .well format version: "3.0"). - Validates the document against
WellSchemaand throws aZodErroron failure. - Returns the validated data with every unknown member kept, at every level, including inside array items (extensibility).
The v1 upgrade applies the normalizations from changes from v1: flat lat, lng and elevation become location, fgdc_texture becomes texture, screen_slot_mm becomes screen_slot, and the bole_hole typo becomes bore_hole.
Readable error messages
A ZodError carries an issues array. Each issue has a path (field names and array indexes) and a message. This helper turns them into the field[0].name: message form used across the docs:
import { parseWell, type Well } from '@welldot/core';
type Issue = { path: (string | number)[]; message: string };
export type Result =
| { valid: true; well: Well }
| { valid: false; errors: string[] };
function formatPath(path: (string | number)[]): string {
return path.reduce<string>(
(out, seg) =>
typeof seg === 'number' ? `${out}[${seg}]` : out ? `${out}.${seg}` : seg,
'',
);
}
export function validate(text: string): Result {
try {
return { valid: true, well: parseWell(text) };
} catch (error) {
if (error instanceof Error && error.name === 'ZodError') {
const { issues } = error as Error & { issues: Issue[] };
return {
valid: false,
errors: issues.map(i => `${formatPath(i.path)}: ${i.message}`),
};
}
// Invalid JSON or an unsupported version
return { valid: false, errors: [(error as Error).message] };
}
}
validate(
'{"version":"2.4","bore_hole":[{"from":0,"to":"60 m","diameter":254}]}',
);
// { valid: false, errors: [
// 'bore_hole[0].to: Expected number, received string',
// 'well_case: Required', …
// ] }
Validating explains the common messages.
WellSchema and the object schemas
WellSchema is the Zod schema of a whole document. Use it to check an object you already have, for example one assembled by a form:
import { WellSchema } from '@welldot/core';
const result = WellSchema.safeParse(candidate);
if (!result.success) console.log(result.error.issues);
WellSchema checks a v2 object as it is: it does not upgrade v1 data and does not look at version beyond its shape. For files, use parseWell.
Every object has its own schema too, which is handy for validating one record before adding it:
import { LithologySchema, WellScreenSchema } from '@welldot/core';
LithologySchema.parse({
from: 8,
to: 30,
description: 'Silty sandstone',
color: '#c9a27a',
texture: { code: 612, vocabulary: 'fgdc' },
geologic_unit: 'Botucatu Formation',
aquifer_unit: 'Guarani',
});
WellScreenSchema.safeParse({ from: 36, to: 54, type: 'pvc', diameter: 168.3 });
// fails: screen_slot is required
They are ordinary Zod schemas, so .extend, .pick and .refine work as usual. The list is in the API reference under Variables (BoreHoleSchema, WellCaseSchema, HydrodynamicEventSchema, PermitSchema, WaterSampleSchema, …).
What the validators check, and what they do not
They check the schema: required fields, types, closed vocabularies, formats such as YYYY-MM-DD dates and RFC 3339 instants with an offset, and the cross-field rules the spec makes errors (for example, a water quality result has exactly one of value, presence or text).
They do not add rules the spec does not have, and they do not check geometry (from below to, casings inside the hole) or references between records. Those are warnings in the spec, computed by @welldot/utils, for example getPermitWarnings and getWaterSampleWarnings.
Legacy files: deserializeWell
Files written by early versions of Well Profiler, before the format had a version field, used other shapes. deserializeWell reads them:
import { deserializeWell, parseWell, serializeWell } from '@welldot/core';
const restored = deserializeWell(oldText); // Well, or null for an unversioned file with no profile
const well = restored && parseWell(serializeWell(restored)); // then validate
It accepts v2 and v1 files and also:
- the old
constructive/geologicsub-objects; - diameters in inches (
diam_pol), converted to millimeters; - the
bole_holetypo.
deserializeWell normalizes but does not validate, and it fills a missing well_depth with the depth calculated from the construction. It throws on invalid JSON and on an unknown version. Use it when importing old archives, then validate the result as above; use parseWell everywhere else.
Related
- JSON Schema, for validation in other languages
parseWellin the API reference