Skip to main content

Marshmallow schema validation for GeoJson

Project description

GitHub Actions status for master branch Latest PyPI package version Codecov Downloads

marshmallow-geojson 🌍

A schema-based, Marshmallow library for validating and working with GeoJSON data according to RFC 7946 specification.

Supported GeoJSON Objects

GeoJSON Objects Status Description
Point A single geographic coordinate
MultiPoint Multiple points
LineString A sequence of connected points forming a line
MultiLineString Multiple line strings
Polygon A closed area, optionally with holes
MultiPolygon Multiple polygons
GeometryCollection Collection of different geometry types
Feature Geometry with properties
FeatureCollection Collection of features
Bbox Bounding box validation

Installation

marshmallow-geojson is compatible with Python 3.9 and up.

The recommended way to install is via poetry:

poetry add marshmallow_geojson

Using pip to install is also possible:

pip install marshmallow-geojson

Quick Start

from marshmallow_geojson import GeoJSONSchema

# Create a schema instance
schema = GeoJSONSchema()

# Load GeoJSON from JSON string
geojson_text = '{"type": "Point", "coordinates": [-105.01621, 39.57422]}'
data = schema.loads(geojson_text)
print(data)
# {'type': 'Point', 'coordinates': [-105.01621, 39.57422]}

# Load GeoJSON from dictionary
geojson_dict = {"type": "Point", "coordinates": [-105.01621, 39.57422]}
data = schema.load(geojson_dict)
print(data)
# {'type': 'Point', 'coordinates': [-105.01621, 39.57422]}

# Dump GeoJSON to JSON string
json_str = schema.dumps(geojson_dict)
print(json_str)
# '{"type": "Point", "coordinates": [-105.01621, 39.57422]}'

GeoJSON Types with Visualizations

Type Visualization Usage
Point
A single geographic coordinate
Point
from marshmallow_geojson import GeoJSONSchema

data = {
    "type": "Point",
    "coordinates": [-105.01621, 39.57422]
}
schema = GeoJSONSchema()
point = schema.load(data)

# Or with altitude
data_3d = {
    "type": "Point",
    "coordinates": [-105.01621, 39.57422, 100.5]
}
point_3d = schema.load(data_3d)

# From JSON string
json_str = '{"type": "Point", "coordinates": [-105.01621, 39.57422]}'
point = schema.loads(json_str)
MultiPoint
Multiple points
MultiPoint
from marshmallow_geojson import GeoJSONSchema

data = {
    "type": "MultiPoint",
    "coordinates": [
        [-105.01621, 39.57422],
        [-80.666513, 35.053994]
    ]
}
schema = GeoJSONSchema()
multi_point = schema.load(data)

# Dump to JSON
json_output = schema.dumps(multi_point)
LineString
A sequence of connected points forming a line
LineString
from marshmallow_geojson import GeoJSONSchema

data = {
    "type": "LineString",
    "coordinates": [
        [-99.113159, 38.869651],
        [-99.0802, 38.85682],
        [-98.822021, 38.85682],
        [-98.448486, 38.848264]
    ]
}
schema = GeoJSONSchema()
line_string = schema.load(data)

# Minimal LineString (2 points)
minimal = {
    "type": "LineString",
    "coordinates": [[-99.113159, 38.869651], [-99.0802, 38.85682]]
}
line = schema.load(minimal)
MultiLineString
Multiple line strings
MultiLineString
from marshmallow_geojson import GeoJSONSchema

data = {
    "type": "MultiLineString",
    "coordinates": [
        [[-105.019898, 39.574997],
         [-105.019598, 39.574898],
         [-105.019061, 39.574782]],
        [[-105.017173, 39.574402],
         [-105.01698, 39.574385],
         [-105.016636, 39.574385]]
    ]
}
schema = GeoJSONSchema()
multi_line_string = schema.load(data)

# With bbox
data_with_bbox = {
    "type": "MultiLineString",
    "bbox": [-180.0, -90.0, 180.0, 90.0],
    "coordinates": [[[-105.019898, 39.574997], [-105.019598, 39.574898]]]
}
result = schema.load(data_with_bbox)
Polygon
A closed area, optionally with holes
Polygon
from marshmallow_geojson import GeoJSONSchema

# Simple polygon
data = {
    "type": "Polygon",
    "coordinates": [
        [[100, 0],
         [101, 0],
         [101, 1],
         [100, 1],
         [100, 0]]
    ]
}
schema = GeoJSONSchema()
polygon = schema.load(data)

# Polygon with holes (interior rings)
data_with_holes = {
    "type": "Polygon",
    "coordinates": [
        [[100, 0], [101, 0], [101, 1], [100, 1], [100, 0]],  # Exterior
        [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]  # Hole
    ]
}
polygon_with_holes = schema.load(data_with_holes)
MultiPolygon
Multiple polygons
MultiPolygon
from marshmallow_geojson import GeoJSONSchema

data = {
    "type": "MultiPolygon",
    "coordinates": [
        [[[107, 7], [108, 7],
          [108, 8], [107, 8],
          [107, 7]]],
        [[[100, 0], [101, 0],
          [101, 1], [100, 1],
          [100, 0]]]
    ]
}
schema = GeoJSONSchema()
multi_polygon = schema.load(data)

# Serialize back to dict
serialized = schema.dump(multi_polygon)
GeometryCollection
Collection of different geometry types
GeometryCollection
from marshmallow_geojson import GeoJSONSchema

data = {
    "type": "GeometryCollection",
    "geometries": [
        {
            "type": "Point",
            "coordinates": [-80.660805, 35.049392]
        },
        {
            "type": "Polygon",
            "coordinates": [[[-80.664582, 35.044965],
                             [-80.663874, 35.04428],
                             [-80.662586, 35.04558],
                             [-80.663444, 35.046036],
                             [-80.664582, 35.044965]]]
        },
        {
            "type": "LineString",
            "coordinates": [[-80.662372, 35.059509],
                            [-80.662693, 35.059263],
                            [-80.662844, 35.05893]]
        }
    ]
}
schema = GeoJSONSchema()
geometry_collection = schema.load(data)

# Empty GeometryCollection
empty = {
    "type": "GeometryCollection",
    "geometries": []
}
empty_collection = schema.load(empty)

Features and FeatureCollections

Feature

A geometry with properties.

from marshmallow_geojson import GeoJSONSchema

# Basic Feature
data = {
    "type": "Feature",
    "properties": {
        "name": "Dinagat Islands",
        "population": 10000
    },
    "geometry": {
        "type": "Polygon",
        "coordinates": [
            [
                [-80.724878, 35.265454],
                [-80.722646, 35.260338],
                [-80.720329, 35.260618],
                [-80.71681, 35.255361],
                [-80.704793, 35.268397],
                [-80.724878, 35.265454]
            ]
        ]
    }
}
schema = GeoJSONSchema()
feature = schema.load(data)

# Feature with Point geometry
point_feature = {
    "type": "Feature",
    "geometry": {
        "type": "Point",
        "coordinates": [-74.006, 40.7128]
    },
    "properties": {
        "name": "New York",
        "population": 8336817
    }
}
feature = schema.load(point_feature)

# Feature with null geometry (unlocated feature)
null_geometry_feature = {
    "type": "Feature",
    "geometry": None,
    "properties": {
        "name": "Unknown Location"
    }
}
feature = schema.load(null_geometry_feature)

# Feature with ID
feature_with_id = {
    "type": "Feature",
    "id": "feature-123",
    "geometry": {
        "type": "Point",
        "coordinates": [-105.01621, 39.57422]
    },
    "properties": {
        "name": "Test Feature"
    }
}
feature = schema.load(feature_with_id)

# Feature with bbox
feature_with_bbox = {
    "type": "Feature",
    "bbox": [-180.0, -90.0, 180.0, 90.0],
    "geometry": {
        "type": "Point",
        "coordinates": [-105.01621, 39.57422]
    },
    "properties": {}
}
feature = schema.load(feature_with_bbox)

FeatureCollection

A collection of features.

from marshmallow_geojson import GeoJSONSchema

# Basic FeatureCollection
data = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [-80.870885, 35.215151]
            },
            "properties": {
                "name": "Location 1"
            }
        },
        {
            "type": "Feature",
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [-80.724878, 35.265454],
                        [-80.722646, 35.260338],
                        [-80.720329, 35.260618],
                        [-80.704793, 35.268397],
                        [-80.724878, 35.265454]
                    ]
                ]
            },
            "properties": {
                "name": "Location 2"
            }
        }
    ]
}
schema = GeoJSONSchema()
feature_collection = schema.load(data)

# Empty FeatureCollection
empty_fc = {
    "type": "FeatureCollection",
    "features": []
}
empty_collection = schema.load(empty_fc)

# FeatureCollection with mixed geometry types
mixed_fc = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {"type": "Point", "coordinates": [-105.01621, 39.57422]},
            "properties": {"type": "point"}
        },
        {
            "type": "Feature",
            "geometry": {
                "type": "LineString",
                "coordinates": [[-99.113159, 38.869651], [-99.0802, 38.85682]]
            },
            "properties": {"type": "line"}
        },
        {
            "type": "Feature",
            "geometry": {
                "type": "Polygon",
                "coordinates": [[[100, 0], [101, 0], [101, 1], [100, 1], [100, 0]]]
            },
            "properties": {"type": "polygon"}
        }
    ]
}
mixed_collection = schema.load(mixed_fc)

# FeatureCollection with bbox
fc_with_bbox = {
    "type": "FeatureCollection",
    "bbox": [-180.0, -90.0, 180.0, 90.0],
    "features": [
        {
            "type": "Feature",
            "geometry": {"type": "Point", "coordinates": [-105.01621, 39.57422]},
            "properties": {}
        }
    ]
}
collection = schema.load(fc_with_bbox)

# Serialize FeatureCollection to JSON
json_output = schema.dumps(feature_collection)

Universal GeoJSONSchema

marshmallow-geojson provides a universal GeoJSONSchema that automatically handles all GeoJSON object types. This is a unique feature not available in other GeoJSON libraries.

from marshmallow_geojson import GeoJSONSchema

schema = GeoJSONSchema()

# Automatically handles Point
point_data = {"type": "Point", "coordinates": [-105.01621, 39.57422]}
point = schema.load(point_data)

# Automatically handles Feature
feature_data = {
    "type": "Feature",
    "geometry": {"type": "Point", "coordinates": [-105.01621, 39.57422]},
    "properties": {"name": "Test"}
}
feature = schema.load(feature_data)

# Automatically handles FeatureCollection
fc_data = {
    "type": "FeatureCollection",
    "features": [feature_data]
}
feature_collection = schema.load(fc_data)

# Works with mixed types in many=True mode
schema_many = GeoJSONSchema(many=True)
mixed_data = [point_data, feature_data, fc_data]
results = schema_many.load(mixed_data)

# Load from JSON string - automatically detects type
json_point = '{"type": "Point", "coordinates": [-105.01621, 39.57422]}'
point = schema.loads(json_point)

json_feature = '{"type": "Feature", "geometry": {"type": "Point", "coordinates": [-105.01621, 39.57422]}, "properties": {}}'
feature = schema.loads(json_feature)

# Get specific schema for a type
point_schema_class = schema.get_schema("Point")
feature_schema_class = schema.get_schema("Feature")

# Handle multiple objects with many=True
json_array = '[{"type": "Point", "coordinates": [-105.01621, 39.57422]}, {"type": "LineString", "coordinates": [[-99.113159, 38.869651], [-99.0802, 38.85682]]}]'
schema_many = GeoJSONSchema(many=True)
results = schema_many.loads(json_array)
# Returns list of validated objects

GeometriesSchema

For working with geometry objects only (excluding Feature and FeatureCollection), use GeometriesSchema:

from marshmallow_geojson import GeometriesSchema

schema = GeometriesSchema()

# Works with all geometry types
point_data = {"type": "Point", "coordinates": [-105.01621, 39.57422]}
point = schema.load(point_data)

polygon_data = {"type": "Polygon", "coordinates": [[[100, 0], [101, 0], [101, 1], [100, 1], [100, 0]]]}
polygon = schema.load(polygon_data)

# Rejects Feature and FeatureCollection
# schema.load({"type": "Feature", ...})  # Raises ValidationError

Custom Properties Schemas

You can define typed properties schemas for type-safe feature properties:

from marshmallow.fields import Str, Int, Nested
from marshmallow_geojson import GeoJSONSchema, PropertiesSchema, FeatureSchema

class CityPropertiesSchema(PropertiesSchema):
    name = Str(required=True)
    population = Int(required=True)
    country = Str(required=True)

class CityFeatureSchema(FeatureSchema):
    properties = Nested(
        CityPropertiesSchema,
        required=True,
    )

class CityGeoJSONSchema(GeoJSONSchema):
    feature_schema = CityFeatureSchema

# Usage
schema = CityGeoJSONSchema()
data = {
    "type": "Feature",
    "properties": {
        "name": "New York",
        "population": 8336817,
        "country": "USA"
    },
    "geometry": {
        "type": "Point",
        "coordinates": [-74.006, 40.7128]
    }
}
city = schema.load(data)
print(city['properties']['name'])  # "New York"
print(city['properties']['population'])  # 8336817

Marshmallow-Specific Features

marshmallow-geojson supports all standard Marshmallow schema features:

Field Filtering (only/exclude)

from marshmallow_geojson import GeoJSONSchema

# Include only specific fields
schema = GeoJSONSchema(only=('type', 'geometry'))
data = schema.load(feature_data)
# Only 'type' and 'geometry' fields are included

# Exclude specific fields
schema = GeoJSONSchema(exclude=('properties',))
data = schema.load(feature_data)
# 'properties' field is excluded

Partial Loading

from marshmallow_geojson import GeoJSONSchema

schema = GeoJSONSchema(partial=True)
# Allows partial data loading
partial_data = {
    "type": "Feature",
    "geometry": {"type": "Point", "coordinates": [-105.01621, 39.57422]}
}
result = schema.load(partial_data, partial=('properties',))

Unknown Field Handling

from marshmallow_geojson import GeoJSONSchema

# Exclude unknown fields
schema = GeoJSONSchema(unknown='exclude')
data_with_extra = {
    "type": "Point",
    "coordinates": [-105.01621, 39.57422],
    "extra_field": "extra_value"
}
result = schema.load(data_with_extra)
# 'extra_field' is automatically excluded

# Raise error on unknown fields
schema = GeoJSONSchema(unknown='raise')
# schema.load(data_with_extra)  # Raises ValidationError

Many Mode

Handle multiple GeoJSON objects at once:

from marshmallow_geojson import GeoJSONSchema

schema = GeoJSONSchema(many=True)

# Load multiple objects
data_list = [
    {"type": "Point", "coordinates": [-105.01621, 39.57422]},
    {"type": "LineString", "coordinates": [[-99.113159, 38.869651], [-99.0802, 38.85682]]},
    {"type": "Feature", "geometry": {"type": "Point", "coordinates": [-80.870885, 35.215151]}, "properties": {}}
]
results = schema.load(data_list)

# Works with JSON strings too
json_str = '[{"type": "Point", "coordinates": [-105.01621, 39.57422]}]'
results = schema.loads(json_str)

Validation

marshmallow-geojson automatically validates:

  • Coordinate ranges: Longitude must be between -180 and 180, latitude between -90 and 90
  • Geometry types: Ensures correct type strings according to RFC 7946
  • Structure: Validates GeoJSON object structure and required fields
  • Linear Rings: Polygon rings must have at least 4 positions and be closed
  • LineString: Must have at least 2 positions
  • Bounding Box: Validates bbox structure (2, 4, or 6 elements) and coordinate order
  • Type mixing: Prevents forbidden members according to RFC 7946 Section 7.1

Example: Invalid Coordinates

from marshmallow_geojson import GeoJSONSchema
from marshmallow.exceptions import ValidationError

schema = GeoJSONSchema()
try:
    data = {"type": "Point", "coordinates": [200, 50]}  # Invalid longitude (> 180)
    schema.load(data)
except ValidationError as e:
    print(e.messages)
    # {'coordinates': ['Longitude must be between -180, 180']}

Example: Invalid Polygon

from marshmallow_geojson import GeoJSONSchema
from marshmallow.exceptions import ValidationError

schema = GeoJSONSchema()
try:
    # Polygon ring must have at least 4 positions and be closed
    data = {
        "type": "Polygon",
        "coordinates": [[[100, 0], [101, 0], [100, 0]]]  # Only 3 positions, not closed
    }
    schema.load(data)
except ValidationError as e:
    print(e.messages)
    # {'coordinates': ['Linear Ring must have at least 4 positions']}

Bounding Box Validation

marshmallow-geojson includes comprehensive bounding box validation:

from marshmallow.fields import List, Number
from marshmallow_geojson import PointSchema
from marshmallow_geojson.validate import Bbox

class PointWithBboxSchema(PointSchema):
    bbox = List(
        Number(),
        required=False,
        allow_none=True,
        validate=Bbox()
    )

schema = PointWithBboxSchema()

# Valid 2D bbox
data = {
    "type": "Point",
    "coordinates": [-105.01621, 39.57422],
    "bbox": [-180.0, -90.0, 180.0, 90.0]
}
result = schema.load(data)

# Valid 3D bbox
data = {
    "type": "Point",
    "coordinates": [-105.01621, 39.57422],
    "bbox": [-180.0, -90.0, -100.0, 180.0, 90.0, 100.0]
}
result = schema.load(data)

Flask Integration

marshmallow-geojson works seamlessly with Flask for building GeoJSON APIs:

from flask import Flask, request, jsonify
from marshmallow_geojson import GeoJSONSchema
from marshmallow.exceptions import ValidationError

app = Flask(__name__)
schema = GeoJSONSchema()

@app.route('/geojson', methods=['POST'])
def create_geojson():
    try:
        data = schema.loads(request.data)
        # Your business logic here
        return jsonify(schema.dump(data)), 201
    except ValidationError as e:
        return jsonify({'errors': e.messages}), 400

@app.route('/geojson/many', methods=['POST'])
def create_geojson_many():
    schema_many = GeoJSONSchema(many=True)
    try:
        data = schema_many.loads(request.data)
        return jsonify(schema_many.dump(data)), 201
    except ValidationError as e:
        return jsonify({'errors': e.messages}), 400

Compatibility with Other Libraries

marshmallow-geojson is designed to work well with popular Python libraries:

Marshmallow

Built on Marshmallow, so you get all the benefits:

  • Schema-based validation
  • JSON serialization/deserialization
  • Field filtering and partial loading
  • Custom validators
  • Integration with Flask, FastAPI, and other frameworks

GeoPandas

You can convert between marshmallow-geojson and GeoPandas:

import geopandas as gpd
from marshmallow_geojson import GeoJSONSchema

# Convert FeatureCollection to GeoDataFrame
schema = GeoJSONSchema()
feature_collection = schema.load(fc_data)
geojson_dict = schema.dump(feature_collection)
gdf = gpd.GeoDataFrame.from_features(geojson_dict["features"])

# Convert GeoDataFrame to FeatureCollection
features = gdf.to_dict("records")
fc_data = {"type": "FeatureCollection", "features": features}
feature_collection = schema.load(fc_data)

Shapely

Convert to/from Shapely geometries:

from shapely.geometry import Point as ShapelyPoint
from marshmallow_geojson import GeoJSONSchema

schema = GeoJSONSchema()

# Marshmallow GeoJSON to Shapely
point_data = {"type": "Point", "coordinates": [-105.01621, 39.57422]}
point = schema.load(point_data)
shapely_point = ShapelyPoint(point['coordinates'][0], point['coordinates'][1])

# Shapely to Marshmallow GeoJSON
shapely_geom = ShapelyPoint(-105.01621, 39.57422)
point_data = {
    "type": "Point",
    "coordinates": [shapely_geom.x, shapely_geom.y]
}
point = schema.load(point_data)

Testing

Run the test suite:

poetry run pytest

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

marshmallow_geojson-0.6.1.tar.gz (22.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

marshmallow_geojson-0.6.1-py3-none-any.whl (30.4 kB view details)

Uploaded Python 3

File details

Details for the file marshmallow_geojson-0.6.1.tar.gz.

File metadata

  • Download URL: marshmallow_geojson-0.6.1.tar.gz
  • Upload date:
  • Size: 22.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for marshmallow_geojson-0.6.1.tar.gz
Algorithm Hash digest
SHA256 c5f886c5ef7af3baecdfb84b4374b761a47a8815cc58125ad5e91711f45c89c1
MD5 d788c5c0ce7b383982eecaec1d0bdd16
BLAKE2b-256 3d02d997ace96d295640060a87321aecfc4901afbdbe16beb12a942e6582c749

See more details on using hashes here.

Provenance

The following attestation bundles were made for marshmallow_geojson-0.6.1.tar.gz:

Publisher: publish.yml on folt/marshmallow-geojson

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file marshmallow_geojson-0.6.1-py3-none-any.whl.

File metadata

File hashes

Hashes for marshmallow_geojson-0.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8a25e97deac5db6cc0f5bfd05cc2343c3b38beca448a6781c3e9c8c8df3d93cf
MD5 44e20f184758e46abff388e1099a3f15
BLAKE2b-256 4775401fa67dd44fdd816ece33ee5c66cab0b8fc4694847ed22522f9047af106

See more details on using hashes here.

Provenance

The following attestation bundles were made for marshmallow_geojson-0.6.1-py3-none-any.whl:

Publisher: publish.yml on folt/marshmallow-geojson

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page