# python-geojson: GeoJSON as Python objects instead of nested dictionaries

> The jazzband geojson package gives Python programs one class per GeoJSON object type, with a `__geo_interface__` implementation for handing geometry to other libraries. It suits people who build or consume GeoJSON documents, and its documentation lags badly behind the code it now ships.

**jazzband/geojson** — Python bindings and utilities for GeoJSON

- Repository: https://github.com/jazzband/geojson
- Website: https://pypi.python.org/pypi/geojson/
- Stars: 1,001 · Forks: 139
- Language: Python
- License: BSD-3-Clause
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/jazzband-geojson

## One Python class per GeoJSON object type

python-geojson is a small library whose job is to make GeoJSON documents look like ordinary Python objects instead of nested dictionaries. The project documentation states that the library contains functions for encoding and decoding GeoJSON formatted data, classes for all GeoJSON Objects, and an implementation of the Python `__geo_interface__` specification. The audience is narrow and clear. If you are serving a map, importing a feed of observed positions, or converting between spatial formats, the temptation is to build a dictionary such as `{"type": "Point", "coordinates": [x, y]}` and hope every producer used the same key order and the same coordinate order. This package removes that guesswork by giving each object type a class with named constructor arguments, and by making the serialised form a consequence of the class rather than of how you typed the dictionary. It is a data model, not a spatial computing library. There is no projection, no distance, no intersection, and no coordinate system handling anywhere in the documented surface.

## Coordinates nest differently in every geometry type

The mechanics are simple enough to read in one sitting, and the nesting rules are where most mistakes happen. A `Point` takes a single coordinate tuple. A `MultiPoint` takes a list of them. A `LineString` takes a list of coordinates, and a `MultiLineString` takes a list of lists, one per line. A `Polygon` takes a list of rings, where the first ring is the outer boundary and any further ring is a hole, and a `MultiPolygon` takes a list of polygons. So the same pair of coordinates sits at a different depth in each class, and the constructor arguments are where that depth gets decided. The documented examples use the coordinate order longitude first, with values such as `(-115.81, 37.24)` for a point and rings that repeat their first coordinate to close the shape. The library does not validate the order for you, and it does not check that a ring is closed, which is worth knowing before you rely on it to catch bad input from a third party feed.

## Installing the package and creating a first Point

The package is published on PyPI under the name `geojson`, and the documentation gives pip as the recommended installation route.

```bash
  pip install geojson
```

A first object is two lines of code.

```python
  >>> from geojson import Point

  >>> Point((-115.81, 37.24))  # doctest: +ELLIPSIS
  {"coordinates": [-115.8..., 37.2...], "type": "Point"}
```

The printed form is a dictionary, and the class is what produced it. The `+ELLIPSIS` marker in the example is a doctest directive, which matters for a reason explained further down: the documentation examples are executed as part of the test suite, so the shown output is the real output of the real object. The same pattern holds for the other geometry types. A `MultiPoint` is built from a list of coordinate tuples, and a `LineString` from a list of coordinates.

```python
  >>> from geojson import LineString

  >>> LineString([(8.919, 44.4074), (8.923, 44.4075)])  # doctest: +ELLIPSIS
  {"coordinates": [[8.91..., 44.407...], [8.92..., 44.407...]], "type": "LineString"}
```

## Feature and GeometryCollection wrap other objects

Two of the object types exist to hold other types, and they are where the package earns its keep. A `Feature` wraps a geometry and adds the metadata fields of the format, and the documented example shows what an empty feature looks like.

```python
  >>> from geojson import Feature, Point

  >>> my_point = Point((-3.68, 40.41))

  >>> Feature(geometry=my_point)  # doctest: +ELLIPSIS
  {"geometry": {"coordinates": [-3.68..., 40.4...], "type": "Point"}, "id": null, "properties": {}, "type": "Feature"}
```

Note the two defaults the serialised form shows, a null `id` and an empty `properties` object, which means a Feature is always emitted with both keys present even when you passed neither. A `GeometryCollection` holds a list of geometries, and its output nests the serialised children under a `geometries` key rather than a `coordinates` key.

```python
  >>> from geojson import GeometryCollection, Point, LineString

  >>> my_point = Point((23.532, -63.12))

  >>> my_line = LineString([(-152.62, 51.21), (5.21, 10.69)])

  >>> GeometryCollection([my_point, my_line])  # doctest: +ELLIPSIS
  {"geometries": [{"coordinates": [23.53..., -63.1...], "type": "Point"}, {"coordinates": [[-152.6..., 51.2...], [5.2..., 10.6...]], "type": "LineString"}], "type": "GeometryCollection"}
```

The two wrapper types are what let you assemble a full document incrementally, attaching a geometry to a feature with its properties rather than hand assembling a nested structure.

## The documentation is executed as a doctest suite

The most interesting engineering decision in the repository is in `setup.py`, and it explains why the documentation examples can be trusted. The test suite function imports `doctest` and `unittest`, and it discovers the tests directory, then adds a doctest file suite built from `README.rst`.

```python
    suite = unittest.TestLoader().discover("tests")
    suite.addTest(doctest.DocFileSuite("README.rst"))
    return suite
```

So every `>>>` example in the documentation is a live test that asserts on the exact output, which is why the examples carry the `+ELLIPSIS` marker to allow for the rounding of coordinates. Two consequences follow. The examples cannot drift away from the code without the suite failing, and the suite covers the documented surface rather than an internal one. The repository layout is small and conventional for a library of this size, with a `geojson/` package directory, a `tests/` directory, `setup.py`, `CHANGELOG.rst`, `MANIFEST.in` and a `.travis.yml` file, and the package installs with `setuptools` as its only requirement. There is no compiled extension and no runtime dependencies to audit.

## The __geo_interface__ is the integration point

The package implements the Python `__geo_interface__` specification, and that single feature is what makes it interoperable rather than merely convenient. The `__geo_interface__` convention is a way for a geometry object to expose itself in a shape other Python libraries can consume, so a `Point` or `Polygon` from this package can be handed to a spatial library that speaks that protocol without being serialised to JSON and parsed again. The functions for encoding and decoding complete the picture, so data can move in both directions: build objects in code, encode them for a response, or decode a document into objects for processing. The practical benefit is that the same object can serve two audiences, a JSON body for a client and an in-memory geometry for a computation. The limitation is that the protocol covers shape, not meaning. Coordinate order, axis orientation and the datum are yours to get right, and nothing in this package will complain if you swap latitude and longitude.

## Old compatibility notes against a setup.py that still gates on Python 2

The documentation and the code disagree about the present. The installation notes state that the library is compatible with Python 2.6, 2.7, 3.2, 3.3 and 3.4, and `setup.py` still carries a version gate that writes a message about supporting only Python 2.6, 2.7 and 3.x and exits for anything else, using `unittest2` as a fallback on 2.6. Meanwhile the package metadata declares `version="1.0.8"`, author Sean Gillies, maintainer Corey Farwell, and classifies the project as `Development Status:: 5 - Production/Stable`. The repository has no GitHub releases, and the default branch is named `fix-type-bug`, which suggests the working branch is carrying a type fix. The last push was on 2026-10-05, so the code has moved since whatever is published on PyPI. For an adopter this means the honest reading is that the package is small, pure Python and probably fine, but that the released metadata and the branch state may not tell the same story. The licence is BSD-3-Clause, which is permissive for commercial use and requires that the copyright notice and licence conditions travel with redistributed copies.

## Typed objects versus hand assembled dictionaries

The alternative is to skip the dependency and use the standard library. A `json.loads` call gives you dictionaries, and `json.dumps` gives you back bytes, and for a pipeline that only reads and writes GeoJSON, that is genuinely enough. The difference in approach is the point at which mistakes are caught. With dictionaries, a wrong key or a wrongly nested list is a runtime `KeyError` or a silent pass of a malformed document to a client. With classes, the nesting is decided by the constructor signature, so the depth of the coordinates is fixed by which class you chose, and the type of the top level object is part of the value. The cost is a dependency and a slightly heavier object, plus the fact that you still write a `json.dumps` call to get bytes out. The `__geo_interface__` tips the balance further, because it is a way for other libraries to consume your objects directly. A third option is to keep the raw JSON and validate it against the format specification at the edge, which is the right approach when the producer is unknown and untrusted, and the wrong one when you are generating the geometry yourself in Python.

## Conclusion

Adopt python-geojson if your Python code needs to build or parse GeoJSON documents and you want typed objects and a `__geo_interface__` rather than hand assembled dictionaries. Skip it if you only need to move bytes through `json.loads` and `json.dumps`, since the standard library covers that without a dependency, and skip it if you need coordinate transformations or geometry operations, which this package does not provide. Verify three things before installing: that the version on PyPI matches the `1.0.8` in `setup.py` given that the last push was on 2026-10-05, that the default branch `fix-type-bug` is not masking a pending type fix, and that the `__geo_interface__` of the geometry library you use agrees with the property names this package emits.

## FAQ

### How do I install the geojson package for Python?

Install it from PyPI with pip, using `pip install geojson`. The package is pure Python and lists `setuptools` as its only install requirement, so there is nothing to compile.

### Which GeoJSON object types does python-geojson support?

The library implements all GeoJSON Objects, with classes for Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon and GeometryCollection, plus Feature and the encoding and decoding functions.

### How do I build a Polygon with a hole in python-geojson?

Pass a list of rings to the `Polygon` constructor, where the first ring is the outer boundary and each additional ring is a hole. Each ring is a list of coordinates that repeats its first coordinate to close the shape.

### What is the __geo_interface__ in python-geojson?

It is the Python specification the package implements, which lets a geometry object expose itself in a form other libraries can consume directly. The package states that it includes an implementation of that specification alongside the object classes and the encoding and decoding functions.

### Are the examples in the python-geojson documentation tested?

Yes. The test suite in `setup.py` discovers the tests directory and then adds a doctest file suite for `README.rst`, so every documented example is executed and its output asserted during the test run.

## Sources

- [Issues](https://github.com/jazzband/geojson/issues)
- [jazzband/geojson on GitHub](https://github.com/jazzband/geojson)
- [License: BSD-3-Clause](https://github.com/jazzband/geojson/blob/fix-type-bug/LICENSE)
- [Project website](https://pypi.python.org/pypi/geojson/)
- [README](https://github.com/jazzband/geojson/blob/fix-type-bug/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jazzband-geojson
