Skip to main content

serde_qs/
lib.rs

1//! Serde support for querystring-style strings
2//!
3//! This library provides serialization and deserialization of querystrings
4//! with support for arbitrarily nested structures. Unlike `serde_urlencoded`,
5//! which only handles flat key-value pairs, `serde_qs` supports complex nested
6//! data using bracket notation (e.g., `user[name]=John&user[age]=30`).
7//!
8//! ## Why use `serde_qs`?
9//!
10//! - **Nested structure support**: Serialize/deserialize complex structs and maps
11//! - **Array support**: Handle vectors and sequences with indexed notation
12//! - **Framework integration**: Built-in support for Actix-web, Axum, and Warp
13//! - **Compatible syntax**: Works with `qs` (JavaScript) and Rack (Ruby)
14//!
15//!
16//! ## Basic Usage
17//!
18//! ```
19//! use serde::{Deserialize, Serialize};
20//!
21//! #[derive(Debug, PartialEq, Deserialize, Serialize)]
22//! struct Address {
23//!     city: String,
24//!     postcode: String,
25//! }
26//! #[derive(Debug, PartialEq, Deserialize, Serialize)]
27//! struct QueryParams {
28//!     id: u8,
29//!     name: String,
30//!     address: Address,
31//!     phone: u32,
32//!     user_ids: Vec<u8>,
33//! }
34//!
35//! # fn main() {
36//! let params = QueryParams {
37//!     id: 42,
38//!     name: "Acme".to_string(),
39//!     phone: 12345,
40//!     address: Address {
41//!         city: "Carrot City".to_string(),
42//!         postcode: "12345".to_string(),
43//!     },
44//!     user_ids: vec![1, 2, 3, 4],
45//! };
46//! let rec_params: QueryParams = serde_qs::from_str("\
47//!     name=Acme&id=42&phone=12345&address[postcode]=12345&\
48//!     address[city]=Carrot+City&user_ids[0]=1&user_ids[1]=2&\
49//!     user_ids[2]=3&user_ids[3]=4")
50//!     .unwrap();
51//! assert_eq!(rec_params, params);
52//!
53//! # }
54//! ```
55//!
56//! ## Supported Types
57//!
58//! `serde_qs` supports all serde-compatible types:
59//!
60//! - **Primitives**: strings, integers (u8-u64, i8-i64), floats (f32, f64), booleans
61//! - **Strings**: UTF-8 strings (invalid UTF-8 handling configurable)
62//! - **Bytes**: `Vec<u8>` and `&[u8]` for raw binary data
63//! - **Collections**: `Vec<T>`, `HashMap<K, V>`, `BTreeMap<K, V>`, arrays
64//! - **Options**: `Option<T>` (missing values deserialize to `None`)
65//! - **Structs**: Named and tuple structs with nested fields
66//! - **Enums**: Externally tagged, internally tagged, and untagged representations
67//!
68//! Note: Top-level types must be structs or maps. Primitives and sequences
69//! cannot be deserialized at the top level. And untagged representations
70//! have some limitations (see [Flatten Workaround](#flatten-workaround) section).
71//!
72//! ## Query-String vs Form Encoding
73//!
74//! By default, `serde_qs` uses **query-string encoding** which is more permissive:
75//! - Spaces encoded as `+`
76//! - Minimal percent-encoding (brackets remain unencoded)
77//! - Example: `name=John+Doe&items[0]=apple`
78//!
79//! The main benefit of query-string encoding is that it allows for more compact
80//! representations of nested structures, and supports square brackets in
81//! key names.
82//!
83//! **Form encoding** (`application/x-www-form-urlencoded`) is stricter:
84//! - Spaces encoded as `%20`
85//! - Most special characters percent-encoded
86//! - Example: `name=John%20Doe&items%5B0%5D=apple`
87//!
88//! Form encoding is useful for compability with HTML forms and other
89//! applications that eagerly encode brackets.
90//!
91//! Configure encoding mode:
92//! ```rust
93//! use serde_qs::Config;
94//!
95//! // Use form encoding
96//! # fn main() -> Result<(), serde_qs::Error> {
97//! # let my_struct = ();
98//! let config = Config::new().use_form_encoding(true);
99//! let qs = config.serialize_string(&my_struct)?;
100//! # Ok(())
101//! # }
102//! ```
103//!
104//! ### Repeated keys
105//!
106//! When parsing a query string, repeated keys are collected into a `Vec` of values.
107//!
108//! - If the deserializer expects a primitive value, we'll take the **last** value
109//! - If the deserializer expects a sequence, we'll deserialize all values into the sequence
110//!
111//! You can configure `serde_qs` to return an error when duplicate values are
112//! provided for a scalar field using [`DuplicateKeyBehavior::Error`]:
113//!
114//! ```rust
115//! use serde::Deserialize;
116//! use serde_qs::{Config, DuplicateKeyBehavior};
117//!
118//! #[derive(Deserialize)]
119//! struct Query {
120//!     single: String,
121//!     multi: Vec<String>,
122//! }
123//!
124//! let config = Config::new().duplicate_key_behavior(DuplicateKeyBehavior::Error);
125//!
126//! // This fails because `single` receives multiple values
127//! let result: Result<Query, _> = config.deserialize_str("single=a&single=b&multi=x&multi=y");
128//! assert!(result.is_err());
129//!
130//! // This succeeds because `single` only has one value (multi can have many)
131//! let result: Result<Query, _> = config.deserialize_str("single=a&multi=x&multi=y");
132//! assert!(result.is_ok());
133//! ```
134//!
135//! ### Array Formats
136//!
137//! The `array_format` option in the `Config` struct allows you to control how arrays are serialized:
138//!
139//! - `Indexed`: `a[0]=1&a[1]=2` (default)
140//! - `EmptyIndexed`: `a[]=1&a[]=2`
141//! - `Unindexed`: `a=1&a=2`
142//!
143//! Note that the `Indexed` format is the only one that can round-trip correctly
144//! for arrays of structs or maps. Without the explicit index, we cannot
145//! disambiguate which value corresponds to which key in the array.
146//!
147//! ## UTF-8 Handling
148//!
149//! By default, `serde_qs` requires valid UTF-8 in string values. If your data
150//! may contain non-UTF-8 bytes, consider serializing to `Vec<u8>` instead of
151//! `String`. Non-UTF-8 bytes in ignored fields will not cause errors.
152//!
153//! ```rust
154//! # use serde::Deserialize;
155//! #[derive(Deserialize)]
156//! struct Data {
157//!     // This field can handle raw bytes
158//!     raw_data: Vec<u8>,
159//!
160//!     // This field requires valid UTF-8
161//!     text: String,
162//! }
163//! ```
164//!
165//! ## Helpers for Common Scenarios
166//!
167//! The `helpers` module provides utilities for common patterns when working with
168//! querystrings, particularly for handling delimited values within a single parameter.
169//!
170//! ### Comma-Separated Values
171//!
172//! Compatible with OpenAPI 3.0 `style=form` parameters:
173//!
174//! ```rust
175//! use serde::{Deserialize, Serialize};
176//!
177//! #[derive(Debug, PartialEq, Deserialize, Serialize)]
178//! struct Query {
179//!     #[serde(with = "serde_qs::helpers::comma_separated")]
180//!     ids: Vec<u64>,
181//! }
182//!
183//! # fn main() {
184//! // Deserialize from comma-separated string
185//! let query: Query = serde_qs::from_str("ids=1,2,3,4").unwrap();
186//! assert_eq!(query.ids, vec![1, 2, 3, 4]);
187//!
188//! // Serialize back to comma-separated
189//! let qs = serde_qs::to_string(&query).unwrap();
190//! assert_eq!(qs, "ids=1,2,3,4");
191//! # }
192//! ```
193//!
194//! ### Other Delimiters
195//!
196//! Also supports pipe (`|`) and space delimited values:
197//!
198//! ```rust
199//! use serde::{Deserialize, Serialize};
200//!
201//! #[derive(Debug, PartialEq, Deserialize, Serialize)]
202//! struct Query {
203//!     #[serde(with = "serde_qs::helpers::pipe_delimited")]
204//!     tags: Vec<String>,
205//!     #[serde(with = "serde_qs::helpers::space_delimited")]
206//!     words: Vec<String>,
207//! }
208//!
209//! # fn main() {
210//! let query: Query = serde_qs::from_str("tags=foo|bar|baz&words=hello+world").unwrap();
211//! assert_eq!(query.tags, vec!["foo", "bar", "baz"]);
212//! assert_eq!(query.words, vec!["hello", "world"]);
213//! # }
214//! ```
215//!
216//! ### Custom Delimiters
217//!
218//! For other delimiters, use the generic helper:
219//!
220//! ```rust
221//! use serde::{Deserialize, Serialize};
222//! use serde_qs::helpers::generic_delimiter::{deserialize, serialize};
223//!
224//! #[derive(Debug, PartialEq, Deserialize, Serialize)]
225//! struct Query {
226//!     #[serde(deserialize_with = "deserialize::<_, _, '.'>")]
227//!     #[serde(serialize_with = "serialize::<_, _, '.'>")]
228//!     versions: Vec<u8>,
229//! }
230//!
231//! # fn main() {
232//! let query: Query = serde_qs::from_str("versions=1.2.3").unwrap();
233//! assert_eq!(query.versions, vec![1, 2, 3]);
234//! # }
235//! ```
236//!
237//! ## Flatten/untagged workaround
238//!
239//! A current [known limitation](https://github.com/serde-rs/serde/issues/1183)
240//! in `serde` is deserializing `#[serde(flatten)]` structs for formats which
241//! are not self-describing. This includes query strings: `12` can be an integer
242//! or a string, for example.
243//!
244//! A similar issue exists for `#[serde(untagged)]` enums, and internally-tagged enums.
245//! The default behavior using derive macros uses content buffers which defers to
246//! `deserialize_any` for deserializing the inner type. This means that any string
247//! parsing that should have happened in the deserializer will not happen,
248//! and must be done explicitly by the user.
249//!
250//! We suggest the following workaround:
251//!
252//! ```
253//! use serde::{Deserialize, Serialize};
254//! use serde_with::{serde_as, DisplayFromStr};
255//!
256//! #[derive(Deserialize, Serialize, Debug, PartialEq)]
257//! struct Query {
258//!     a: u8,
259//!     #[serde(flatten)]
260//!     common: CommonParams,
261//! }
262//!
263//! #[serde_as]
264//! #[derive(Deserialize, Serialize, Debug, PartialEq)]
265//! struct CommonParams {
266//!     #[serde_as(as = "DisplayFromStr")]
267//!     limit: u64,
268//!     #[serde_as(as = "DisplayFromStr")]
269//!     offset: u64,
270//!     #[serde_as(as = "DisplayFromStr")]
271//!     remaining: bool,
272//! }
273//!
274//! let params = "a=1&limit=100&offset=50&remaining=true";
275//! let query = Query { a: 1, common: CommonParams { limit: 100, offset: 50, remaining: true } };
276//! let rec_query: Result<Query, _> = serde_qs::from_str(params);
277//! assert_eq!(rec_query.unwrap(), query);
278//! ```
279//!
280//! ## Use with `actix_web` extractors
281//!
282//! The `actix4` or `actix3` features enable the use of `serde_qs::actix::QsQuery`, which
283//! is a direct substitute for the `actix_web::Query` and can be used as an extractor:
284//!
285//! ```ignore
286//! fn index(info: QsQuery<Info>) -> Result<String> {
287//!     Ok(format!("Welcome {}!", info.username))
288//! }
289//! ```
290//!
291//! Support for `actix-web 4.0` is available via the `actix4` feature.
292//! Support for `actix-web 3.0` is available via the `actix3` feature.
293//!
294//! ## Use with `warp` filters
295//!
296//! The `warp` feature enables the use of `serde_qs::warp::query()`, which
297//! is a substitute for the `warp::query::query()` filter and can be used like this:
298//!
299//! ```ignore
300//! serde_qs::warp::query(Config::default())
301//!     .and_then(|info| async move {
302//!         Ok::<_, Rejection>(format!("Welcome {}!", info.username))
303//!     })
304//!     .recover(serde_qs::warp::recover_fn);
305//! ```
306//!
307
308#[cfg(any(feature = "actix4", feature = "actix3"))]
309pub mod actix;
310
311#[cfg(feature = "actix")]
312compile_error!(
313    r#"The `actix` feature was removed in v0.9 due to the proliferation of actix versions.
314You must now specify the desired actix version by number.
315
316E.g.
317
318serde_qs = { version = "0.9", features = ["actix4"] }
319
320"#
321);
322
323#[cfg(feature = "actix2")]
324compile_error!(
325    r#"The `actix2` feature was removed in v0.13 due to CI issues and minimal interest in continuing support"#
326);
327
328mod config;
329#[doc(inline)]
330pub use config::{ArrayFormat, Config, DuplicateKeyBehavior};
331mod de;
332mod error;
333pub mod helpers;
334mod ser;
335
336#[doc(inline)]
337pub use de::QsDeserializer as Deserializer;
338#[doc(inline)]
339pub use de::{from_bytes, from_str};
340
341pub use error::Error;
342#[doc(inline)]
343pub use ser::{QsSerializer as Serializer, to_string, to_writer};
344
345#[cfg(feature = "axum")]
346pub mod axum;
347
348#[cfg(feature = "warp")]
349pub mod warp;
350
351#[cfg(any(feature = "actix4", feature = "actix3", feature = "axum"))]
352pub mod web;
353
354#[cfg(feature = "indexmap")]
355mod indexmap {
356    use std::borrow::Borrow;
357
358    pub use indexmap::IndexMap as Map;
359    pub use indexmap::map::Entry;
360
361    pub fn remove<K, V, Q>(map: &mut Map<K, V>, key: &Q) -> Option<V>
362    where
363        K: Borrow<Q> + std::hash::Hash + Eq,
364        Q: ?Sized + std::hash::Hash + Eq,
365    {
366        map.shift_remove(key)
367    }
368
369    pub fn pop_first<K, V>(map: &mut Map<K, V>) -> Option<(K, V)> {
370        map.shift_remove_index(0)
371    }
372}
373
374#[cfg(feature = "indexmap")]
375pub(crate) use crate::indexmap as map;
376
377#[cfg(not(feature = "indexmap"))]
378mod btree_map {
379    use std::borrow::Borrow;
380    pub use std::collections::BTreeMap as Map;
381    pub use std::collections::btree_map::Entry;
382
383    pub fn remove<K, V, Q>(map: &mut Map<K, V>, key: &Q) -> Option<V>
384    where
385        K: Borrow<Q> + Ord,
386        Q: ?Sized + Ord,
387    {
388        map.remove(key)
389    }
390
391    pub fn pop_first<K: Ord, V>(map: &mut Map<K, V>) -> Option<(K, V)> {
392        map.pop_first()
393    }
394}
395
396#[cfg(not(feature = "indexmap"))]
397pub(crate) use crate::btree_map as map;