Marshmallow schema validation for GeoJson
Project description
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5f886c5ef7af3baecdfb84b4374b761a47a8815cc58125ad5e91711f45c89c1
|
|
| MD5 |
d788c5c0ce7b383982eecaec1d0bdd16
|
|
| BLAKE2b-256 |
3d02d997ace96d295640060a87321aecfc4901afbdbe16beb12a942e6582c749
|
Provenance
The following attestation bundles were made for marshmallow_geojson-0.6.1.tar.gz:
Publisher:
publish.yml on folt/marshmallow-geojson
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marshmallow_geojson-0.6.1.tar.gz -
Subject digest:
c5f886c5ef7af3baecdfb84b4374b761a47a8815cc58125ad5e91711f45c89c1 - Sigstore transparency entry: 702048500
- Sigstore integration time:
-
Permalink:
folt/marshmallow-geojson@0a3e72957aed7b4994b01ff9d98e1cbc2cd7f111 -
Branch / Tag:
refs/tags/0.6.1 - Owner: https://github.com/folt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0a3e72957aed7b4994b01ff9d98e1cbc2cd7f111 -
Trigger Event:
release
-
Statement type:
File details
Details for the file marshmallow_geojson-0.6.1-py3-none-any.whl.
File metadata
- Download URL: marshmallow_geojson-0.6.1-py3-none-any.whl
- Upload date:
- Size: 30.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a25e97deac5db6cc0f5bfd05cc2343c3b38beca448a6781c3e9c8c8df3d93cf
|
|
| MD5 |
44e20f184758e46abff388e1099a3f15
|
|
| BLAKE2b-256 |
4775401fa67dd44fdd816ece33ee5c66cab0b8fc4694847ed22522f9047af106
|
Provenance
The following attestation bundles were made for marshmallow_geojson-0.6.1-py3-none-any.whl:
Publisher:
publish.yml on folt/marshmallow-geojson
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marshmallow_geojson-0.6.1-py3-none-any.whl -
Subject digest:
8a25e97deac5db6cc0f5bfd05cc2343c3b38beca448a6781c3e9c8c8df3d93cf - Sigstore transparency entry: 702048501
- Sigstore integration time:
-
Permalink:
folt/marshmallow-geojson@0a3e72957aed7b4994b01ff9d98e1cbc2cd7f111 -
Branch / Tag:
refs/tags/0.6.1 - Owner: https://github.com/folt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0a3e72957aed7b4994b01ff9d98e1cbc2cd7f111 -
Trigger Event:
release
-
Statement type: