cityjson-json 0.6.1

Serde adapter for CityJSON 2.0, providing (de)serialization on top of the `cityjson` crate.
docs.rs failed to build cityjson-json-0.6.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

cityjson-json

cityjson-json is a CityJSON 2.0 serde adapter around the cityjson crate. It provides efficient serialization and deserialization of CityJSON documents with both owned and borrowed string storage options.

Benchmarks

Representative read benchmarks against serde_json::Value. Refresh this table from the shared benchmark suite with just bench.

Case Owned Borrowed serde_json::Value Owned vs Value Borrowed vs Value
io_basisvoorziening_3d_cityjson 282.4 MiB/s 313.5 MiB/s 251.1 MiB/s 1.13x 1.25x
io_3dbag_cityjson_cluster_4x 186.8 MiB/s 196.2 MiB/s 290.8 MiB/s 0.64x 0.67x
io_3dbag_cityjson 192.9 MiB/s 205.8 MiB/s 308.3 MiB/s 0.63x 0.67x

Full benchmark tables and plots are written to benches/results/benchmark_summary.md. Use just bench-local /path/to/file-or-directory for ad hoc local inputs without rewriting this README snapshot.

Installation

cargo add cityjson-json

Getting Started

Imports

use cityjson_json::{from_str_owned, from_str_borrowed, as_json};
use cityjson_json::{OwnedCityModel, BorrowedCityModel, SerializableCityModel};

Owned Deserialization

For simple use cases, deserialize into an owned model:

use cityjson_json::from_str_owned;

let json_str = r#"{
  "type": "CityJSON",
  "version": "2.0",
  "transform": {"scale": [1.0, 1.0, 1.0], "translate": [0.0, 0.0, 0.0]},
  "CityObjects": {},
  "vertices": []
}"#;

let model = from_str_owned(json_str)?;
# Ok::<(), cityjson_json::Error>(())

Borrowed Deserialization

For performance-critical applications, use borrowed deserialization to avoid allocations:

use cityjson_json::from_str_borrowed;

let json_str = r#"{"type":"CityJSON","version":"2.0","transform":{"scale":[1.0,1.0,1.0],"translate":[0.0,0.0,0.0]},"CityObjects":{},"vertices":[]}"#;
let model = from_str_borrowed(json_str)?;
// model holds references to json_str
# Ok::<(), cityjson_json::Error>(())

Serialization

Serialize models back to JSON using the as_json builder:

use cityjson_json::as_json;

let json_output = as_json(&model).to_string()?;

The same builder works for other output targets:

use cityjson_json::as_json;

let bytes = as_json(&model).to_vec()?;
as_json(&model).validate().to_writer(&mut writer)?;

CityJSONSeq

Read a newline-delimited CityJSONSeq stream. The first line must be a CityJSON header; each subsequent line is a self-contained CityJSONFeature:

use std::io::BufReader;
use cityjson_json::read_cityjsonseq;

let seq = concat!(
    r#"{"type":"CityJSON","version":"2.0","transform":{"scale":[0.001,0.001,0.001],"translate":[0.0,0.0,0.0]},"CityObjects":{},"vertices":[]}"#, "\n",
    r#"{"type":"CityJSONFeature","id":"f1","CityObjects":{"f1":{"type":"Building"}},"vertices":[]}"#, "\n",
);
let features = read_cityjsonseq(BufReader::new(seq.as_bytes()))?
    .collect::<cityjson_json::Result<Vec<_>>>()?;
// each element is an OwnedCityModel (CityJSONFeature) with the header transform merged in
# Ok::<(), cityjson_json::Error>(())

Write a strict CityJSONSeq stream. Supply a CityJSON base root and one or more feature models; the builder quantizes vertices and computes the geographical extent:

use cityjson_json::{from_str_owned, from_feature_str_with_base, write_cityjsonseq};

let base_input = r#"{"type":"CityJSON","version":"2.0","transform":{"scale":[1.0,1.0,1.0],"translate":[0.0,0.0,0.0]},"CityObjects":{},"vertices":[]}"#;
let base_root = from_str_owned(base_input)?;
let feature = from_feature_str_with_base(
    r#"{"type":"CityJSONFeature","id":"f1","CityObjects":{"f1":{"type":"Building","geometry":[{"type":"MultiPoint","boundaries":[0]}]}},"vertices":[[10,20,30]]}"#,
    base_input,
)?;

let mut output: Vec<u8> = Vec::new();
let report = write_cityjsonseq(&base_root, [&feature])
    .auto_transform([0.001, 0.001, 0.001])
    .write(&mut output)?;
// report.feature_count == 1, report.geographical_extent covers all feature vertices
# Ok::<(), cityjson_json::Error>(())

Documentation

todo: link to docs.rs

Library Layout

Module Contents
v2_0 CityJSON 2.0 (de)serialization entry points, feature-stream helpers, and SerializableCityModel
errors Error and Result types surfaced by the adapter
(root) Convenience re-exports: from_str_*, as_json, write_cityjsonseq, model types

Core types re-exported from cityjson:

  • OwnedCityModel: A CityJSON model with owned String storage. Self-contained and doesn't depend on external lifetimes.
  • BorrowedCityModel: A CityJSON model with borrowed string references. More memory efficient but requires careful lifetime management.

Design

The adapter is optimized around a small number of core decisions:

  • deserialization is split into root preparation and streamed model construction
  • CityObjects and geometry boundaries avoid large intermediate JSON structures
  • attributes deserialize directly into backend value types
  • serialization streams from the model with a shared write context instead of building a DOM first
  • both owned and borrowed string storage are supported through the same parsing pipeline

The full design description lives in docs/design.md.

API Stability

This crate follows semantic versioning (MAJOR.MINOR.PATCH):

  • MAJOR: incompatible API changes
  • MINOR: backwards-compatible feature additions
  • PATCH: backwards-compatible fixes

Minimum Rust Version

The minimum supported rustc version is 1.93.0.

Development

Development setup, test configuration, and benchmark workflow are documented in docs/development.md.

Contributing

Contributions are welcome in all forms. Please open an issue to discuss any potential changes before working on a patch. You can submit LLM-generated PRs for bug fixes and documentation improvements. Regardless of handwritten or LLM-generated code, the PR should follow these guidelines:

  • relatively small, focused changes, otherwise I won't be able to review it,
  • follow the existing style and conventions,
  • include unit tests and documentation for new features and bug fixes,
  • the patched code should pass:
    • just ci
  • if you remove or merge tests or examples or benchmarks, please explain why and update the documentation accordingly.

License

Licensed under either:

  • Apache License, Version 2.0 (LICENSE-APACHE)
  • MIT license (LICENSE-MIT)

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in cityjson-json by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without additional terms or conditions.

Use of AI in this project

This crate was originally developed without the use of AI. Since then, it underwent multiple significant refactors and various LLM models (Claude, ChatGPT) were used for experimenting with alternative designs, in particular for the (de)serialization strategies and borrowed-parsing paths. LLM generated code is also used for improving the test coverage and documentation and mechanical improvements. Code correctness and performance are verified by carefully curated test cases and benchmarks that cover the CityJSON 2.0 specification.

Roadmap

There are no major features planned for the near future, beyond bug fixes, test coverage, performance optimization, and documentation improvements.