https://github.com/folt/marshmallow-geojson
Marshmallow schema validation for GeoJson
https://github.com/folt/marshmallow-geojson
geo geojson gis json json-schema linux marshmallow openapi python python3 rest swagger
Last synced: 15 days ago
JSON representation
Marshmallow schema validation for GeoJson
- Host: GitHub
- URL: https://github.com/folt/marshmallow-geojson
- Owner: folt
- License: mit
- Created: 2020-09-21T09:19:28.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2026-03-01T12:05:32.000Z (25 days ago)
- Last Synced: 2026-03-01T14:38:02.784Z (25 days ago)
- Topics: geo, geojson, gis, json, json-schema, linux, marshmallow, openapi, python, python3, rest, swagger
- Language: Python
- Homepage:
- Size: 533 KB
- Stars: 16
- Watchers: 1
- Forks: 4
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
- Authors: AUTHORS
Awesome Lists containing this project
README
[](https://github.com/folt/marshmallow-geojson/actions?query=workflow%3A%22Python+package%22)
[](https://pypi.org/project/marshmallow-geojson/)
[](https://codecov.io/gh/folt/marshmallow-geojson)
[](https://pepy.tech/project/marshmallow-geojson)
# marshmallow-geojson 🌍
A schema-based, Marshmallow library for validating and working with [GeoJSON](http://geojson.org/) data according to [RFC 7946](https://tools.ietf.org/html/rfc7946) 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.10 and up.
The recommended way to install is via [poetry](https://python-poetry.org/):
```shell
poetry add marshmallow_geojson
```
Using pip to install is also possible:
```shell
pip install marshmallow-geojson
```
## Quick Start
```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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.
```python
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.
```python
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.
```python
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`:
```python
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:
```python
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)
```python
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
```python
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
```python
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:
```python
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
```python
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
```python
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:
```python
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)
```
## Flask Integration
marshmallow-geojson works seamlessly with Flask for building GeoJSON APIs:
```python
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:
```python
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:
```python
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:
```shell
poetry run pytest
```
## Contributing
Contributions are welcome! Please see [CONTRIBUTING.md](https://github.com/folt/marshmallow-geojson/blob/master/CONTRIBUTING.md) for guidelines.
## License
This project is licensed under the MIT License - see the [LICENSE](https://github.com/folt/marshmallow-geojson/blob/master/LICENSE) file for details.
## Links
- [GitHub Repository](https://github.com/folt/marshmallow-geojson)
- [PyPI Package](https://pypi.org/project/marshmallow-geojson/)
- [GeoJSON Specification](http://geojson.org/)
- [RFC 7946](https://tools.ietf.org/html/rfc7946)
- [Marshmallow Documentation](https://marshmallow.readthedocs.io/)