A schema-based, Marshmallow library for validating and working with GeoJSON data according to RFC 7946 specification.
| 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 |
marshmallow-geojson is compatible with Python 3.10 and up.
The recommended way to install is via poetry:
poetry add marshmallow_geojsonUsing pip to install is also possible:
pip install marshmallow-geojsonfrom 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]}'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)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)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 objectsFor 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 ValidationErrorYou 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']) # 8336817marshmallow-geojson supports all standard Marshmallow schema features:
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 excludedfrom 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',))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 ValidationErrorHandle 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)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
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']}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']}marshmallow-geojson includes comprehensive bounding box validation:
from marshmallow.fields import Float, List
from marshmallow_geojson import PointSchema
from marshmallow_geojson.validate import Bbox
class PointWithBboxSchema(PointSchema):
bbox = List(
Float(),
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)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}), 400marshmallow-geojson is designed to work well with popular Python libraries:
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
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)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)Run the test suite:
poetry run pytestContributions are welcome! Please see CONTRIBUTING.md for guidelines.
This project is licensed under the MIT License - see the LICENSE file for details.






