# Parsing and validating

> Validate .well text with parseWell, validate objects with WellSchema and the per-object schemas, turn Zod issues into readable messages, and read legacy files.

`@welldot/core` defines what a valid `.well` file is. The same validators back the [JSON Schema](/format/reference/json-schema), the welldot.org editor and this site's [MCP validator](/ai/mcp), so every tool agrees.

## `parseWell`: text in, `Well` out

```ts
import { parseWell } from '@welldot/core';

const well = parseWell(text);
```

`parseWell(json: string): Well` does four things, in order:

1. Parses the JSON text.
2. Reads `version` and decides how to read the file, following [parser version handling](/format/spec/v2/format-reference#parser-version-handling): `1` is upgraded to v2, `2` and any `"2.x"` are read as v2, a missing version is read as v2, anything else throws an `Error` naming the value (`Unsupported .well format version: "3.0"`).
3. Validates the document against `WellSchema` and throws a `ZodError` on failure.
4. Returns the validated data with every unknown member kept, at every level, including inside array items ([extensibility](/format/spec/v2/format-reference#extensibility)).

The v1 upgrade applies the normalizations from [changes from v1](/format/spec/v2/overview#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:

```ts [validate.ts]
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] };
  }
}
```

```ts
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](/format/guides/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:

```ts
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:

```ts
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](/core/api) 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`](/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:

```ts
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` / `geologic` sub-objects;
- diameters in inches (`diam_pol`), converted to millimeters;
- the `bole_hole` typo.

`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](/format/reference/json-schema), for validation in other languages
- [`parseWell` in the API reference](/core/api/functions/parseWell)
