This repository contains GeoJSON schemas for the Zod validation library by @colinhacks.
The schemas are based on the GeoJSON specification RFC 7946. They validate the structure of the GeoJSON objects and types, as well as the validity of the dimensions, geometries, and bounding boxes.
The schemas and inferred types are designed to be fully compatible with the @types/geojson TypeScript types.
To use these schemas, you can install the package from npm:
# With NPM
npm install zod-geojson
# With Yarn
yarn add zod-geojson
# With PNPM
pnpm add zod-geojsonThen you can use the schemas in your code:
import { GeoJSONSchema } from "zod-geojson";
const schema = GeoJSONSchema.parse({
type: "Feature",
geometry: {
type: "Point",
coordinates: [0, 0],
},
properties: {
name: "Null Island",
},
});This library exposes schemas for the individual GeoJSON types:
import {
GeoJSONSchema,
GeoJSONFeatureSchema,
GeoJSONFeatureCollectionSchema,
// Geometries
GeoJSONGeometrySchema,
GeoJSONGeometryCollectionSchema,
GeoJSONMultiPolygonSchema,
GeoJSONMultiLineStringSchema,
GeoJSONMultiPointSchema,
GeoJSONPolygonSchema,
GeoJSONLineStringSchema,
GeoJSONPointSchema,
// Utilities
GeoJSONPositionSchema,
GeoJSONPropertiesSchema,
} from "zod-geojson";It also exposes the resulting types from the schemas:
import type {
GeoJSON,
GeoJSONFeature,
GeoJSONFeatureCollection,
// Geometries
GeoJSONGeometry,
GeoJSONGeometryCollection,
GeoJSONMultiPolygon,
GeoJSONMultiLineString,
GeoJSONMultiPoint,
GeoJSONPolygon,
GeoJSONLineString,
GeoJSONPoint,
// Utilities
GeoJSONPosition,
GeoJSONProperties,
} from "zod-geojson";To maintain "out of the box" compatibility with @types/geojson the general schemas allow any position dimensionality.
import { GeoJSONPoint } from "zod-geojson";
// These are all valid
const point3D: GeoJSONPoint = { type: "Point", coordinates: [0, 0, 0] };
const point1D: GeoJSONPoint = { type: "Point", coordinates: [0] };
const point0D: GeoJSONPoint = { type: "Point", coordinates: [] };
const point4D: GeoJSONPoint = { type: "Point", coordinates: [0, 0, 0, 0] };If you want strict position checking at compile time then you can use the provided 2D and 3D schemas/types.
import type {
// 2D GeoJSON
GeoJSON2DFeatureSchema,
GeoJSON2DFeature,
// ...
// 2D GeoJSON Geometries
GeoJSON2DPointSchema,
GeoJSON2DPoint,
// ...
// 3D GeoJSON
GeoJSON3DFeatureSchema,
GeoJSON3DFeature,
// ...
// 3D GeoJSON Geometries
GeoJSON3DPointSchema,
GeoJSON3DPoint,
// ...
} from "zod-geojson";These use zod tuples to ensure positions are checked at compile time. However, to maintain interoperability
with @types/geojson, bounding box dimensionality is not enforced at the type level, only during runtime validation.
import { GeoJSON2DPoint } from "zod-geojson";
const point3D: GeoJSON2DPoint = {
type: "Point",
// This is a 3D position, and so errors during compilation!
coordinates: [0, 0, 0],
};
const point2DWith3DBBox: GeoJSON2DPoint = {
type: "Point",
coordinates: [1.0, 2.0],
bbox: [0.0, 0.0, 3.0, 4.0, 0.0, 0.0], // This is allowed at type level, but will error during validation!
};Each GeoJSON schema in this library has a generic version that allows you to customize certain aspects of the GeoJSON validation.
import {
GeoJSONGenericSchema,
GeoJSONFeatureGenericSchema,
GeoJSONFeatureCollectionGenericSchema,
// Geometries
GeoJSONGeometryGenericSchema,
GeoJSONGeometryCollectionGenericSchema,
GeoJSONMultiPolygonGenericSchema,
GeoJSONMultiLineStringGenericSchema,
GeoJSONMultiPointGenericSchema,
GeoJSONPolygonGenericSchema,
GeoJSONLineStringGenericSchema,
GeoJSONPointGenericSchema,
// Utilities
GeoJSONPositionGenericSchema,
GeoJSONPropertiesGenericSchema,
} from "zod-geojson";At geometry level you can customize:
- The dimension of the coordinates (e.g. 4D geometries)
At geojson, feature & feature collection level you can customize:
- The dimension of the coordinates (e.g. 4D geometries)
- The structure of the
propertiesfield - The type of geometries allowed in the features
For example, to customize the main GeoJSON schema you would use the GeoJSONGenericSchema function.
This function takes three parameters:
- A Zod schema defining the structure of the positions (coordinates)
- A Zod schema defining the structure of the properties field
- A Zod schema defining the structure of the geometries allowed in the GeoJSON
Even if you wish to only customize one of these aspects, you will still need to pass all three parameters to the schema function. For convenience, this library exposes the default schemas which you can use as a base for your custom schemas. These are the following "default" main schemas that you can use if you do not wish to customize a certain aspect of the schema:
GeoJSONPositionSchema- The main GeoJSON position schema which allows both 2D and 3D positionsGeoJSONPropertiesSchema- The main GeoJSON properties schema which allows any valid JSON object ornullGeoJSONGeometrySchema- The main GeoJSON geometry schema which allows all valid GeoJSON geometries with 2D or 3D coordinates
If you wish to use a different dimension (e.g. 4D geometries), you can pass a custom position schema
as the first parameter to the generic schema functions.
As discussed above, if you only wish to customize the position field, you will still need to pass valid schemas for
the properties and geometries fields. You can use the default GeoJSONPropertiesSchema properties schema and you will need to
create & pass a custom geometry schema that uses your custom position schema.
import { GeoJSONGeometryGenericSchema, GeoJSONPropertiesSchema } from "zod-geojson";
const GeoJSON4DPositionSchema = z.tuple([z.number(), z.number(), z.number(), z.number()]);
type GeoJSON4DPosition = z.infer<typeof GeoJSON4DPositionSchema>;
const GeoJSON4DGeometrySchema = GeoJSONGeometryGenericSchema(GeoJSON4DPositionSchema);
type GeoJSON4DGeometry = z.infer<typeof GeoJSON4DGeometrySchema>;
const GeoJSON4DSchema = GeoJSONGenericSchema(GeoJSON4DPositionSchema, GeoJSONPropertiesSchema, GeoJSON4DGeometrySchema);
type GeoJSON4D = z.infer<typeof GeoJSON4DSchema>;By default, the properties field of a GeoJSON Feature is defined as any valid JSON object or null. If you want to
enforce a specific structure for the properties field, you can pass a custom properties Zod schema as the second parameter to the
GeoJSONFeatureGenericSchema, GeoJSONFeatureCollectionGenericSchema, or GeoJSONGenericSchema functions.
As discussed above, if you only wish to customize the properties field, you will still need to pass valid schemas for the
positions and geometries fields. You can use the default schemas GeoJSONPositionSchema and GeoJSONGeometrySchema exposed
by this library for this purpose.
import {
GeoJSONFeatureGenericSchema,
GeoJSONPositionSchema,
GeoJSONPropertiesSchema,
GeoJSONGeometrySchema,
} from "zod-geojson";
const NonNullPropertiesGeoJSONFeatureSchema = GeoJSONFeatureGenericSchema(
GeoJSONPositionSchema,
GeoJSONPropertiesSchema.unwrap(),
GeoJSONGeometrySchema,
);
type NonNullPropertiesGeoJSONFeature = z.infer<typeof NonNullPropertiesGeoJSONFeatureSchema>;
const CustomPropertiesSchema = z.object({
name: z.string(),
description: z.string().optional(),
});
type CustomProperties = z.infer<typeof CustomPropertiesSchema>;
const CustomGeoJSONFeatureSchema = GeoJSONFeatureGenericSchema(
GeoJSONPositionSchema,
CustomPropertiesSchema,
GeoJSONGeometrySchema,
);
type CustomGeoJSONFeature = z.infer<typeof CustomGeoJSONFeatureSchema>;If you want to restrict the types of geometries allowed in a GeoJSON Feature, Feature Collection or GeoJSON object,
you can pass a custom geometry Zod schema as the third parameter to the GeoJSONFeatureGenericSchema,
GeoJSONFeatureCollectionGenericSchema, or GeoJSONGenericSchema functions.
If you wish to restrict the geometry to a collection of possible geometries, you can use Zod's
discriminatedUnion function to create a union of the allowed
geometry schemas and pass this union schema as the third parameter.
As discussed above, if you only wish to customize the geometries field, you will still need to pass valid schemas for the
positions and properties. You can use the default schemas GeoJSONPositionSchema and GeoJSONPropertiesSchema exposed
by this library for this purpose.
import {
GeoJSONFeatureGenericSchema,
GeoJSONPositionSchema,
GeoJSONPropertiesSchema,
GeoJSONPointSchema,
} from "zod-geojson";
// A GeoJSON Feature that only allows Point geometries
const PointGeoJSONFeatureSchema = GeoJSONFeatureGenericSchema(
GeoJSONPositionSchema,
GeoJSONPropertiesSchema,
GeoJSONPointSchema,
);
type PointGeoJSONFeature = z.infer<typeof PointGeoJSONFeatureSchema>;
const PointOrPolygonGeoJSONFeatureSchema = GeoJSONFeatureGenericSchema(
GeoJSONPositionSchema,
GeoJSONPropertiesSchema,
z.discriminatedUnion("type", [GeoJSONPointSchema, GeoJSONPolygonSchema]),
);By default, the geometry field of a GeoJSON Feature is defined as a non-nullable GeoJSON geometry object. This
decision was made to improve interoperability with @types/geojson which
defines the geometry field as non-nullable in the main GeoJSON type.
If you want to allow null values in feature geometry fields you can create a custom nullable geometry schema and
pass this schema as the third parameter to the GeoJSONFeatureGenericSchema, GeoJSONFeatureCollectionGenericSchema
or GeoJSONGenericSchema functions.
import {
GeoJSONFeatureGenericSchema,
GeoJSONPositionSchema,
GeoJSONPropertiesSchema,
GeoJSONGeometrySchema,
} from "zod-geojson";
const GeoJSONFeatureWithNullableGeometrySchema = GeoJSONFeatureGenericSchema(
GeoJSONPositionSchema,
GeoJSONPropertiesSchema,
GeoJSONGeometrySchema.nullable(),
);This library will throw an error if the GeoJSON object is not valid. For example, where the coordinates type does not match the geometry type:
import { GeoJSONSchema } from "zod-geojson";
const schema = GeoJSONSchema.parse({
type: "Point",
coordinates: [[0, 0]],
});It will also throw an error if the bounding box does not match the geometry:
import { GeoJSONSchema } from "zod-geojson";
const schema = GeoJSONSchema.parse({
type: "MultiPoint",
coordinates: [
[-2, 0],
[3, 4],
[2, -1],
],
bbox: [0, 0, 1, 1],
});The error messages are currently very big and not user-friendly due to the default handling of failures in nested zod unions. If you're not using it already, I recommend using zod-validation-error to simplify the error messages.
The inferred types from the main schemas (e.g. GeoJSON) are quite large due to the nested unions and the recursive
nature of the geometry collections. Since the inferred zod types only see
the full union of all possible types, without knowing the names of the intermediate schemas, the resulting types show
every single possible combination of the subtypes. This can lead to very large types that can be hard to read. Unfortunately,
I have not found a way to reduce the size of the inferred types while still maintaining the full validation. If
you know of a way to improve this, please open an issue or a pull request.
In the meantime, if you really need a smaller type you can always create a custom schema that only contains the geometry types that you need. See "Custom Geometries" section for more details.
If you find any issues with the schemas or want to add new features, feel free to open an issue or a pull request.
It is recommended to use pnpm to install the dependencies using the -r flag to install
the root and the test package dependencies.
pnpm install -rA convenience script is provided to run all checks so you don't have to run them separately. Please ensure all tests pass if you open a pull request.
pnpm checksThis project is licensed under the MIT License - see the LICENSE file for details.