From bf392287a830e82cc1cf210f7242b864a0150841 Mon Sep 17 00:00:00 2001 From: chuenchen309 <48723787+chuenchen309@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:38:11 +0800 Subject: [PATCH] fix: serialize array-valued GeoJSON properties as JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit geojson2csv only ran json.dumps on OrderedDict property values, so an array (list) property fell through to the CSV writer's str() and was emitted as a Python repr — e.g. ['park', 'landmark'], which is not valid JSON and not round-trippable. An object property in the same row was already emitted as JSON, so the two container types diverged. This completes the intent of the existing OrderedDict json.dumps (added per the "prints a JSON object instead of OrderedDict([(...)])" changelog entry): dump lists as well. --- CHANGELOG.rst | 1 + csvkit/convert/geojs.py | 2 +- tests/test_utilities/test_in2csv.py | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5fdf4a1a3..951ac5515 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ Unreleased - feat: :doc:`/scripts/csvclean` adds a :code:`--remove-empty-columns` option to remove empty columns from standard output. - feat: :doc:`/scripts/in2csv` guesses the ``ndjson`` format for files with :code:`.ndjson`, :code:`.jsonl` and :code:`.jl` extensions. - fix: :code:`-C/--not-columns` now excludes the last column of an open-ended range (e.g. :code:`2-`). +- fix: :doc:`/scripts/in2csv` with :code:`--format geojson` serializes array-valued properties as JSON, like object-valued properties, instead of as a Python ``repr``. 2.2.0 - December 15, 2025 ------------------------- diff --git a/csvkit/convert/geojs.py b/csvkit/convert/geojs.py index 28d2583a5..60e55b161 100644 --- a/csvkit/convert/geojs.py +++ b/csvkit/convert/geojs.py @@ -64,7 +64,7 @@ def geojson2csv(f, key=None, **kwargs): for field in property_fields: value = properties.get(field) - if isinstance(value, OrderedDict): + if isinstance(value, (OrderedDict, list)): value = json.dumps(value) row.append(value) diff --git a/tests/test_utilities/test_in2csv.py b/tests/test_utilities/test_in2csv.py index 6c9e60fd4..894291c2b 100644 --- a/tests/test_utilities/test_in2csv.py +++ b/tests/test_utilities/test_in2csv.py @@ -229,6 +229,19 @@ def test_geojson_no_inference(self): input_file.close() + def test_geojson_array_property(self): + input_file = io.BytesIO( + b'{"type": "FeatureCollection", "features": [{"geometry": null, "properties": ' + b'{"tags": ["a", "b"], "obj": {"k": "v"}}}]}') + + with stdin_as_string(input_file): + self.assertLines(['-f', 'geojson'], [ + 'id,tags,obj,geojson,type,longitude,latitude', + ',"[""a"", ""b""]","{""k"": ""v""}",null,,,', + ]) + + input_file.close() + def test_json_no_inference(self): input_file = io.BytesIO(b'[{"a": 1, "b": 2, "c": 3}]')