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.10 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 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:
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
Metadata
Release files for marshmallow-geojson 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| marshmallow_geojson-1.0.0.tar.gz | 22.4 kB | With provenance | Details for marshmallow_geojson-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| marshmallow_geojson-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for marshmallow_geojson-1.0.0-py3-none-any.whl |
Total release size: 52.8 kB
Release files / marshmallow_geojson-1.0.0.tar.gz
| Download URL | marshmallow_geojson-1.0.0.tar.gz |
|---|---|
| Size | 22.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
542915ea70239b37ac994c26dbed7bd361bb932405450684f8d905faf7c85b44
|
|
BLAKE2b-256 checksum How to use checksums |
81b1a063c6325cda5ab2c5511aca99c6505badb51579baca1f23f3f8762e593f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 1, 2026.
Transparency logRelease files / marshmallow_geojson-1.0.0-py3-none-any.whl
| Download URL | marshmallow_geojson-1.0.0-py3-none-any.whl |
|---|---|
| Size | 30.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e2ffb8e6e43346d83211bf3b84280519b36823dd231e5ec91457bfefc068f753
|
|
BLAKE2b-256 checksum How to use checksums |
ca6a48d2e685b6a3b3a3ac36cdcd51482881a46bb90fec522d20f42b7544c7fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 1, 2026.
Transparency logSecurity for marshmallow-geojson 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for marshmallow_geojson-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.