├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── tests ├── crate │ ├── .gitignore │ ├── test.rs │ └── Cargo.toml └── test.rs ├── LICENSE-MIT ├── Cargo.toml ├── src ├── seed.rs ├── any.rs ├── seq.rs ├── map.rs ├── int.rs ├── error.rs └── lib.rs ├── README.md └── LICENSE-APACHE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dtolnay 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /tests/crate/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /tests/crate/test.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub use serde_untagged::*; 4 | -------------------------------------------------------------------------------- /tests/crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde-untagged-test" 3 | version = "0.0.0" 4 | authors = ["David Tolnay "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [lib] 9 | path = "test.rs" 10 | 11 | [dependencies] 12 | serde-untagged = { path = "../.." } 13 | 14 | [workspace] 15 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde-untagged" 3 | version = "0.1.9" 4 | authors = ["David Tolnay "] 5 | categories = ["encoding", "no-std"] 6 | description = "Serde `Visitor` implementation for deserializing untagged enums" 7 | documentation = "https://docs.rs/serde-untagged" 8 | edition = "2021" 9 | keywords = ["serde", "untagged"] 10 | license = "MIT OR Apache-2.0" 11 | repository = "https://github.com/dtolnay/serde-untagged" 12 | rust-version = "1.68" 13 | 14 | [dependencies] 15 | erased-serde = { version = "0.4.2", default-features = false, features = ["alloc"] } 16 | serde_core = { version = "1.0.220", default-features = false, features = ["alloc"] } 17 | typeid = "1" 18 | 19 | [target.'cfg(any())'.dependencies] 20 | serde = { version = "1.0.220", default-features = false } 21 | 22 | [dev-dependencies] 23 | serde = "1.0.220" 24 | serde_derive = "1.0.220" 25 | serde_json = "1.0.110" 26 | toml = "0.9" 27 | 28 | [package.metadata.docs.rs] 29 | targets = ["x86_64-unknown-linux-gnu"] 30 | rustdoc-args = [ 31 | "--generate-link-to-definition", 32 | "--generate-macro-expansion", 33 | "--extern-html-root-url=core=https://doc.rust-lang.org", 34 | "--extern-html-root-url=alloc=https://doc.rust-lang.org", 35 | "--extern-html-root-url=std=https://doc.rust-lang.org", 36 | ] 37 | -------------------------------------------------------------------------------- /src/seed.rs: -------------------------------------------------------------------------------- 1 | use crate::any::ErasedValue; 2 | use alloc::boxed::Box; 3 | use serde::de::{DeserializeSeed, Deserializer}; 4 | 5 | pub(crate) trait ErasedDeserializeSeed<'de> { 6 | fn erased_deserialize( 7 | &mut self, 8 | deserializer: Box + '_>, 9 | ) -> Result; 10 | } 11 | 12 | impl<'de, Seed> ErasedDeserializeSeed<'de> for Option 13 | where 14 | Seed: DeserializeSeed<'de>, 15 | { 16 | fn erased_deserialize( 17 | &mut self, 18 | deserializer: Box + '_>, 19 | ) -> Result { 20 | self.take() 21 | .unwrap() 22 | .deserialize(deserializer) 23 | .map(|value| unsafe { ErasedValue::new::(value) }) 24 | } 25 | } 26 | 27 | impl<'de> DeserializeSeed<'de> for &mut dyn ErasedDeserializeSeed<'de> { 28 | type Value = ErasedValue; 29 | 30 | fn deserialize(self, deserializer: D) -> Result 31 | where 32 | D: Deserializer<'de>, 33 | { 34 | let deserializer = Box::new(::erase(deserializer)); 35 | self.erased_deserialize(deserializer) 36 | .map_err(serde::de::Error::custom) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/any.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | #[cfg(any(debug_assertions, miri))] 3 | use core::any::{self, TypeId}; 4 | use core::mem; 5 | 6 | pub(crate) struct ErasedValue { 7 | ptr: *mut (), 8 | drop: unsafe fn(*mut ()), 9 | #[cfg(any(debug_assertions, miri))] 10 | type_id: TypeId, 11 | #[cfg(any(debug_assertions, miri))] 12 | type_name: &'static str, 13 | } 14 | 15 | impl ErasedValue { 16 | pub(crate) unsafe fn new(value: T) -> Self { 17 | ErasedValue { 18 | ptr: Box::into_raw(Box::new(value)).cast(), 19 | drop: { 20 | unsafe fn drop(ptr: *mut ()) { 21 | let _ = unsafe { Box::from_raw(ptr.cast::()) }; 22 | } 23 | drop:: 24 | }, 25 | #[cfg(any(debug_assertions, miri))] 26 | type_id: typeid::of::(), 27 | #[cfg(any(debug_assertions, miri))] 28 | type_name: any::type_name::(), 29 | } 30 | } 31 | 32 | pub(crate) unsafe fn take(self) -> T { 33 | #[cfg(any(debug_assertions, miri))] 34 | assert_eq!( 35 | self.type_id, 36 | typeid::of::(), 37 | "ErasedValue mismatch: {} vs {}", 38 | self.type_name, 39 | any::type_name::(), 40 | ); 41 | 42 | let b = unsafe { Box::from_raw(self.ptr.cast::()) }; 43 | mem::forget(self); 44 | *b 45 | } 46 | } 47 | 48 | impl Drop for ErasedValue { 49 | fn drop(&mut self) { 50 | unsafe { (self.drop)(self.ptr) } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/seq.rs: -------------------------------------------------------------------------------- 1 | use crate::any::ErasedValue; 2 | use crate::error::{self, Error}; 3 | use crate::seed::ErasedDeserializeSeed; 4 | use alloc::boxed::Box; 5 | use serde::de::{Deserialize, DeserializeSeed, SeqAccess}; 6 | 7 | trait ErasedSeqAccess<'de> { 8 | fn erased_next_element_seed( 9 | &mut self, 10 | seed: &mut dyn ErasedDeserializeSeed<'de>, 11 | ) -> Result, Error>; 12 | 13 | fn erased_size_hint(&self) -> Option; 14 | } 15 | 16 | pub struct Seq<'access, 'de> { 17 | erased: Box + 'access>, 18 | } 19 | 20 | impl<'access, 'de> Seq<'access, 'de> { 21 | pub(crate) fn new(seq: A) -> Self 22 | where 23 | A: SeqAccess<'de> + 'access, 24 | { 25 | Seq { 26 | erased: Box::new(seq), 27 | } 28 | } 29 | 30 | /// Shorthand for `T::deserialize(serde::de::value::SeqAccessDeserializer::new(self))`. 31 | pub fn deserialize(self) -> Result 32 | where 33 | T: Deserialize<'de>, 34 | { 35 | T::deserialize(serde::de::value::SeqAccessDeserializer::new(self)) 36 | } 37 | } 38 | 39 | impl<'access, 'de> SeqAccess<'de> for Seq<'access, 'de> { 40 | type Error = Error; 41 | 42 | fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> 43 | where 44 | T: DeserializeSeed<'de>, 45 | { 46 | self.erased 47 | .erased_next_element_seed(&mut Some(seed)) 48 | .map(|erased_value| match erased_value { 49 | Some(value) => Some(unsafe { ErasedValue::take::(value) }), 50 | None => None, 51 | }) 52 | } 53 | 54 | fn size_hint(&self) -> Option { 55 | self.erased.erased_size_hint() 56 | } 57 | } 58 | 59 | impl<'de, Access> ErasedSeqAccess<'de> for Access 60 | where 61 | Access: SeqAccess<'de>, 62 | { 63 | fn erased_next_element_seed( 64 | &mut self, 65 | seed: &mut dyn ErasedDeserializeSeed<'de>, 66 | ) -> Result, Error> { 67 | self.next_element_seed(seed).map_err(error::erase) 68 | } 69 | 70 | fn erased_size_hint(&self) -> Option { 71 | self.size_hint() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/map.rs: -------------------------------------------------------------------------------- 1 | use crate::any::ErasedValue; 2 | use crate::error::{self, Error}; 3 | use crate::seed::ErasedDeserializeSeed; 4 | use alloc::boxed::Box; 5 | use serde::de::{Deserialize, DeserializeSeed, MapAccess}; 6 | 7 | trait ErasedMapAccess<'de> { 8 | fn erased_next_key_seed( 9 | &mut self, 10 | seed: &mut dyn ErasedDeserializeSeed<'de>, 11 | ) -> Result, Error>; 12 | 13 | fn erased_next_value_seed( 14 | &mut self, 15 | seed: &mut dyn ErasedDeserializeSeed<'de>, 16 | ) -> Result; 17 | 18 | fn erased_size_hint(&self) -> Option; 19 | } 20 | 21 | pub struct Map<'access, 'de> { 22 | erased: Box + 'access>, 23 | } 24 | 25 | impl<'access, 'de> Map<'access, 'de> { 26 | pub(crate) fn new(map: A) -> Self 27 | where 28 | A: MapAccess<'de> + 'access, 29 | { 30 | Map { 31 | erased: Box::new(map), 32 | } 33 | } 34 | 35 | /// Shorthand for `T::deserialize(serde::de::value::MapAccessDeserializer::new(self))`. 36 | pub fn deserialize(self) -> Result 37 | where 38 | T: Deserialize<'de>, 39 | { 40 | T::deserialize(serde::de::value::MapAccessDeserializer::new(self)) 41 | } 42 | } 43 | 44 | impl<'access, 'de> MapAccess<'de> for Map<'access, 'de> { 45 | type Error = Error; 46 | 47 | fn next_key_seed(&mut self, seed: T) -> Result, Self::Error> 48 | where 49 | T: DeserializeSeed<'de>, 50 | { 51 | self.erased 52 | .erased_next_key_seed(&mut Some(seed)) 53 | .map(|erased_value| match erased_value { 54 | Some(value) => Some(unsafe { ErasedValue::take::(value) }), 55 | None => None, 56 | }) 57 | } 58 | 59 | fn next_value_seed(&mut self, seed: T) -> Result 60 | where 61 | T: DeserializeSeed<'de>, 62 | { 63 | self.erased 64 | .erased_next_value_seed(&mut Some(seed)) 65 | .map(|erased_value| unsafe { ErasedValue::take::(erased_value) }) 66 | } 67 | 68 | fn size_hint(&self) -> Option { 69 | self.erased.erased_size_hint() 70 | } 71 | } 72 | 73 | impl<'de, Access> ErasedMapAccess<'de> for Access 74 | where 75 | Access: MapAccess<'de>, 76 | { 77 | fn erased_next_key_seed( 78 | &mut self, 79 | seed: &mut dyn ErasedDeserializeSeed<'de>, 80 | ) -> Result, Error> { 81 | self.next_key_seed(seed).map_err(error::erase) 82 | } 83 | 84 | fn erased_next_value_seed( 85 | &mut self, 86 | seed: &mut dyn ErasedDeserializeSeed<'de>, 87 | ) -> Result { 88 | self.next_value_seed(seed).map_err(error::erase) 89 | } 90 | 91 | fn erased_size_hint(&self) -> Option { 92 | self.size_hint() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: [cron: "40 1 * * *"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | pre_ci: 17 | uses: dtolnay/.github/.github/workflows/pre_ci.yml@master 18 | 19 | test: 20 | name: Rust ${{matrix.rust}} 21 | needs: pre_ci 22 | if: needs.pre_ci.outputs.continue 23 | runs-on: ubuntu-latest 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | rust: [nightly, beta, stable, 1.76.0] 28 | timeout-minutes: 45 29 | steps: 30 | - uses: actions/checkout@v6 31 | - uses: dtolnay/rust-toolchain@master 32 | with: 33 | toolchain: ${{matrix.rust}} 34 | - name: Enable type layout randomization 35 | run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV 36 | if: matrix.rust == 'nightly' 37 | - run: cargo test 38 | - uses: actions/upload-artifact@v6 39 | if: matrix.rust == 'nightly' && always() 40 | with: 41 | name: Cargo.lock 42 | path: Cargo.lock 43 | continue-on-error: true 44 | 45 | msrv: 46 | name: Rust 1.68.0 47 | needs: pre_ci 48 | if: needs.pre_ci.outputs.continue 49 | runs-on: ubuntu-latest 50 | timeout-minutes: 45 51 | steps: 52 | - uses: actions/checkout@v6 53 | - uses: dtolnay/rust-toolchain@1.68.0 54 | - run: cargo check --manifest-path tests/crate/Cargo.toml 55 | 56 | minimal: 57 | name: Minimal versions 58 | needs: pre_ci 59 | if: needs.pre_ci.outputs.continue 60 | runs-on: ubuntu-latest 61 | timeout-minutes: 45 62 | steps: 63 | - uses: actions/checkout@v6 64 | - uses: dtolnay/rust-toolchain@nightly 65 | - run: cargo generate-lockfile -Z minimal-versions 66 | - run: cargo check --locked 67 | 68 | doc: 69 | name: Documentation 70 | needs: pre_ci 71 | if: needs.pre_ci.outputs.continue 72 | runs-on: ubuntu-latest 73 | timeout-minutes: 45 74 | env: 75 | RUSTDOCFLAGS: -Dwarnings 76 | steps: 77 | - uses: actions/checkout@v6 78 | - uses: dtolnay/rust-toolchain@nightly 79 | - uses: dtolnay/install@cargo-docs-rs 80 | - run: cargo docs-rs 81 | 82 | clippy: 83 | name: Clippy 84 | runs-on: ubuntu-latest 85 | if: github.event_name != 'pull_request' 86 | timeout-minutes: 45 87 | steps: 88 | - uses: actions/checkout@v6 89 | - uses: dtolnay/rust-toolchain@clippy 90 | - run: cargo clippy --tests -- -Dclippy::all -Dclippy::pedantic 91 | 92 | miri: 93 | name: Miri 94 | needs: pre_ci 95 | if: needs.pre_ci.outputs.continue 96 | runs-on: ubuntu-latest 97 | timeout-minutes: 45 98 | steps: 99 | - uses: actions/checkout@v6 100 | - uses: dtolnay/rust-toolchain@miri 101 | - run: cargo miri setup 102 | - run: cargo miri test 103 | env: 104 | MIRIFLAGS: -Zmiri-strict-provenance 105 | 106 | outdated: 107 | name: Outdated 108 | runs-on: ubuntu-latest 109 | if: github.event_name != 'pull_request' 110 | timeout-minutes: 45 111 | steps: 112 | - uses: actions/checkout@v6 113 | - uses: dtolnay/rust-toolchain@stable 114 | - uses: dtolnay/install@cargo-outdated 115 | - run: cargo outdated --workspace --exit-code 1 116 | -------------------------------------------------------------------------------- /src/int.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::UntaggedEnumVisitor; 3 | use serde::de::{Unexpected, Visitor}; 4 | 5 | pub(crate) enum IntKind { 6 | I8, 7 | I16, 8 | I32, 9 | I64, 10 | I128, 11 | U8, 12 | U16, 13 | U32, 14 | U64, 15 | U128, 16 | } 17 | 18 | pub(crate) trait Integer: 19 | Copy 20 | + TryInto 21 | + TryInto 22 | + TryInto 23 | + TryInto 24 | + TryInto 25 | + TryInto 26 | + TryInto 27 | + TryInto 28 | + TryInto 29 | + TryInto 30 | { 31 | } 32 | 33 | impl Integer for T 34 | where 35 | T: Copy, 36 | i8: TryFrom, 37 | i16: TryFrom, 38 | i32: TryFrom, 39 | i64: TryFrom, 40 | i128: TryFrom, 41 | u8: TryFrom, 42 | u16: TryFrom, 43 | u32: TryFrom, 44 | u64: TryFrom, 45 | u128: TryFrom, 46 | { 47 | } 48 | 49 | impl<'closure, 'de, Value> UntaggedEnumVisitor<'closure, 'de, Value> { 50 | pub(crate) fn dispatch_integer( 51 | self, 52 | value: I, 53 | precedence: [IntKind; 10], 54 | ) -> Result 55 | where 56 | I: Integer, 57 | E: serde::de::Error, 58 | { 59 | for kind in precedence { 60 | match kind { 61 | IntKind::I8 => { 62 | if let Some(int) = i8::int_from(value) { 63 | if let Some(visit_i8) = self.visit_i8 { 64 | return visit_i8(int).map_err(error::unerase); 65 | } 66 | } 67 | } 68 | IntKind::I16 => { 69 | if let Some(int) = i16::int_from(value) { 70 | if let Some(visit_i16) = self.visit_i16 { 71 | return visit_i16(int).map_err(error::unerase); 72 | } 73 | } 74 | } 75 | IntKind::I32 => { 76 | if let Some(int) = i32::int_from(value) { 77 | if let Some(visit_i32) = self.visit_i32 { 78 | return visit_i32(int).map_err(error::unerase); 79 | } 80 | } 81 | } 82 | IntKind::I64 => { 83 | if let Some(int) = i64::int_from(value) { 84 | if let Some(visit_i64) = self.visit_i64 { 85 | return visit_i64(int).map_err(error::unerase); 86 | } 87 | } 88 | } 89 | IntKind::I128 => { 90 | if let Some(int) = i128::int_from(value) { 91 | if let Some(visit_i128) = self.visit_i128 { 92 | return visit_i128(int).map_err(error::unerase); 93 | } 94 | } 95 | } 96 | IntKind::U8 => { 97 | if let Some(int) = u8::int_from(value) { 98 | if let Some(visit_u8) = self.visit_u8 { 99 | return visit_u8(int).map_err(error::unerase); 100 | } 101 | } 102 | } 103 | IntKind::U16 => { 104 | if let Some(int) = u16::int_from(value) { 105 | if let Some(visit_u16) = self.visit_u16 { 106 | return visit_u16(int).map_err(error::unerase); 107 | } 108 | } 109 | } 110 | IntKind::U32 => { 111 | if let Some(int) = u32::int_from(value) { 112 | if let Some(visit_u32) = self.visit_u32 { 113 | return visit_u32(int).map_err(error::unerase); 114 | } 115 | } 116 | } 117 | IntKind::U64 => { 118 | if let Some(int) = u64::int_from(value) { 119 | if let Some(visit_u64) = self.visit_u64 { 120 | return visit_u64(int).map_err(error::unerase); 121 | } 122 | } 123 | } 124 | IntKind::U128 => { 125 | if let Some(int) = u128::int_from(value) { 126 | if let Some(visit_u128) = self.visit_u128 { 127 | return visit_u128(int).map_err(error::unerase); 128 | } 129 | } 130 | } 131 | } 132 | } 133 | if let Some(int) = u64::int_from(value) { 134 | return Err(E::invalid_type(Unexpected::Unsigned(int), &self)); 135 | } 136 | if let Some(int) = i64::int_from(value) { 137 | return Err(E::invalid_type(Unexpected::Signed(int), &self)); 138 | } 139 | if let Some(int) = u128::int_from(value) { 140 | return crate::DefaultVisitor::new(&self).visit_u128(int); 141 | } 142 | if let Some(int) = i128::int_from(value) { 143 | return crate::DefaultVisitor::new(&self).visit_i128(int); 144 | } 145 | unreachable!() 146 | } 147 | } 148 | 149 | trait IntFrom: Sized { 150 | fn int_from(int: I) -> Option; 151 | } 152 | 153 | impl IntFrom for T 154 | where 155 | I: TryInto, 156 | { 157 | fn int_from(int: I) -> Option { 158 | int.try_into().ok() 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use serde::de::{Deserialize, Deserializer, SeqAccess}; 2 | use serde_json::json; 3 | use serde_untagged::UntaggedEnumVisitor; 4 | 5 | #[test] 6 | fn test_string_or_array_string() { 7 | #[derive(PartialEq, Debug)] 8 | enum Value { 9 | Single(String), 10 | Multiple(Vec), 11 | } 12 | 13 | impl<'de> Deserialize<'de> for Value { 14 | fn deserialize(deserializer: D) -> Result 15 | where 16 | D: Deserializer<'de>, 17 | { 18 | UntaggedEnumVisitor::new() 19 | .string(|string| Ok(Value::Single(string.to_owned()))) 20 | .seq(|mut seq| { 21 | let mut array = Vec::new(); 22 | while let Some(element) = seq.next_element()? { 23 | array.push(element); 24 | } 25 | Ok(Value::Multiple(array)) 26 | }) 27 | .deserialize(deserializer) 28 | } 29 | } 30 | 31 | let j = r#" "..." "#; 32 | let v: Value = serde_json::from_str(j).unwrap(); 33 | assert_eq!(v, Value::Single("...".to_owned())); 34 | 35 | let j = r#" ["a","z"] "#; 36 | let v: Value = serde_json::from_str(j).unwrap(); 37 | assert_eq!(v, Value::Multiple(vec!["a".to_owned(), "z".to_owned()])); 38 | } 39 | 40 | #[test] 41 | fn test_borrowed() { 42 | #[derive(PartialEq, Debug)] 43 | enum Value<'de> { 44 | Single(&'de str), 45 | Multiple(Vec<&'de str>), 46 | } 47 | 48 | impl<'de> Deserialize<'de> for Value<'de> { 49 | fn deserialize(deserializer: D) -> Result 50 | where 51 | D: Deserializer<'de>, 52 | { 53 | UntaggedEnumVisitor::new() 54 | .borrowed_str(|string| Ok(Value::Single(string))) 55 | .seq(|seq| seq.deserialize().map(Value::Multiple)) 56 | .deserialize(deserializer) 57 | } 58 | } 59 | 60 | let j = &r#" "..." "#.to_owned(); 61 | let v: Value = serde_json::from_str(j).unwrap(); 62 | assert_eq!(v, Value::Single("...")); 63 | 64 | let j = &r#" ["a","z"] "#.to_owned(); 65 | let v: Value = serde_json::from_str(j).unwrap(); 66 | assert_eq!(v, Value::Multiple(vec!["a", "z"])); 67 | } 68 | 69 | #[test] 70 | fn test_contains_map_key() { 71 | #[derive(PartialEq, Debug)] 72 | enum Response { 73 | Success(serde_json::Value), 74 | Failure(String), 75 | } 76 | 77 | impl<'de> Deserialize<'de> for Response { 78 | fn deserialize(deserializer: D) -> Result 79 | where 80 | D: Deserializer<'de>, 81 | { 82 | UntaggedEnumVisitor::new() 83 | .map(|map| { 84 | let value: serde_json::Value = map.deserialize()?; 85 | if let Ok(failure) = String::deserialize(&value["failure"]) { 86 | Ok(Response::Failure(failure)) 87 | } else { 88 | Ok(Response::Success(value)) 89 | } 90 | }) 91 | .deserialize(deserializer) 92 | } 93 | } 94 | 95 | let j = &r#" {"failure":"..."} "#.to_owned(); 96 | let v: Response = serde_json::from_str(j).unwrap(); 97 | assert_eq!(v, Response::Failure("...".to_owned())); 98 | 99 | let j = &r#" {"ok":200} "#.to_owned(); 100 | let v: Response = serde_json::from_str(j).unwrap(); 101 | assert_eq!(v, Response::Success(json!({"ok":200}))); 102 | } 103 | 104 | #[test] 105 | fn test_expecting() { 106 | let error = UntaggedEnumVisitor::new() 107 | .seq(|_seq| Ok(())) 108 | .deserialize(&serde_json::Value::Null) 109 | .unwrap_err(); 110 | let expected_message = "invalid type: null, expected an array"; 111 | assert_eq!(error.to_string(), expected_message); 112 | 113 | let error = UntaggedEnumVisitor::new() 114 | .seq(|_seq| Ok(())) 115 | .bool(|_bool| Ok(())) 116 | .deserialize(&serde_json::Value::Null) 117 | .unwrap_err(); 118 | let expected_message = "invalid type: null, expected a boolean or array"; 119 | assert_eq!(error.to_string(), expected_message); 120 | 121 | let error = UntaggedEnumVisitor::new() 122 | .seq(|_seq| Ok(())) 123 | .bool(|_bool| Ok(())) 124 | .i8(|_int| Ok(())) 125 | .deserialize(&serde_json::Value::Null) 126 | .unwrap_err(); 127 | let expected_message = "invalid type: null, expected a boolean, integer or array"; 128 | assert_eq!(error.to_string(), expected_message); 129 | 130 | let error = UntaggedEnumVisitor::new() 131 | .seq(|_seq| Ok(())) 132 | .bool(|_bool| Ok(())) 133 | .i8(|_int| Ok(())) 134 | .i16(|_int| Ok(())) 135 | .deserialize(&serde_json::Value::Null) 136 | .unwrap_err(); 137 | let expected_message = "invalid type: null, expected a boolean, integer or array"; 138 | assert_eq!(error.to_string(), expected_message); 139 | 140 | let error = UntaggedEnumVisitor::<()>::new() 141 | .deserialize(&serde_json::Value::Null) 142 | .unwrap_err(); 143 | let expected_message = "invalid type: null, expected unspecified"; 144 | assert_eq!(error.to_string(), expected_message); 145 | 146 | let ty = "T"; 147 | let error = UntaggedEnumVisitor::<()>::new() 148 | .expecting(format_args!("foo of type {ty}")) 149 | .deserialize(&serde_json::Value::Null) 150 | .unwrap_err(); 151 | let expected_message = "invalid type: null, expected foo of type T"; 152 | assert_eq!(error.to_string(), expected_message); 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | serde-untagged 2 | ============== 3 | 4 | [github](https://github.com/dtolnay/serde-untagged) 5 | [crates.io](https://crates.io/crates/serde-untagged) 6 | [docs.rs](https://docs.rs/serde-untagged) 7 | [build status](https://github.com/dtolnay/serde-untagged/actions?query=branch%3Amaster) 8 | 9 | This crate provides a Serde `Visitor` implementation that is useful for 10 | deserializing untagged enums. 11 | 12 | ```toml 13 | [dependencies] 14 | serde-untagged = "0.1" 15 | ``` 16 | 17 |
18 | 19 | Untagged enum `Deserialize` impls look like this: 20 | 21 | ```rust 22 | use serde::de::{Deserialize, Deserializer}; 23 | use serde_untagged::UntaggedEnumVisitor; 24 | 25 | impl<'de> Deserialize<'de> for $MyType { 26 | fn deserialize(deserializer: D) -> Result 27 | where 28 | D: Deserializer<'de>, 29 | { 30 | UntaggedEnumVisitor::new() 31 | /* 32 | * 33 | */ 34 | .deserialize(deserializer) 35 | } 36 | } 37 | ``` 38 | 39 | Inside the `/* ... */`, we list each type that the untagged enum needs to 40 | support deserializing from, giving a closure that turns the input into $MyType. 41 | The following types are supported: 42 | 43 | - bool 44 | - i8, i16, i32, i64, i128, u8, u16, u32, u64, u128 45 | - f32 46 | - f64 47 | - char 48 | - string 49 | - borrowed\_str 50 | - bytes 51 | - borrowed\_bytes 52 | - byte\_buf 53 | - unit 54 | - seq 55 | - map 56 | 57 | ### Example: string or struct 58 | 59 | Cargo's `http.ssl-version` configuration supports deserialization from the 60 | following two representations: 61 | 62 | ```toml 63 | [http] 64 | ssl-version = "tlsv1.3" 65 | ``` 66 | 67 | ```toml 68 | [http] 69 | ssl-version.min = "tlsv1.2" 70 | ssl-version.max = "tlsv1.3" 71 | ``` 72 | 73 | ```rust 74 | use serde::de::{Deserialize, Deserializer}; 75 | use serde_derive::Deserialize; 76 | use serde_untagged::UntaggedEnumVisitor; 77 | 78 | pub enum SslVersionConfig { 79 | Single(String), 80 | Range(SslVersionConfigRange), 81 | } 82 | 83 | impl<'de> Deserialize<'de> for SslVersionConfig { 84 | fn deserialize(deserializer: D) -> Result 85 | where 86 | D: Deserializer<'de>, 87 | { 88 | UntaggedEnumVisitor::new() 89 | .string(|single| Ok(SslVersionConfig::Single(single.to_owned()))) 90 | .map(|map| map.deserialize().map(SslVersionConfig::Range)) 91 | .deserialize(deserializer) 92 | } 93 | } 94 | 95 | #[derive(Deserialize)] 96 | pub struct SslVersionConfigRange { 97 | pub min: Option, 98 | pub max: Option, 99 | } 100 | ``` 101 | 102 | ### Example: unit variant or bool 103 | 104 | Cargo's LTO setting in profiles supports the 5 values `false`, `true`, `"fat"`, 105 | `"thin"`, and `"off"`. 106 | 107 | ```toml 108 | [profile.release] 109 | lto = "thin" 110 | ``` 111 | 112 | ```rust 113 | use serde::de::{Deserialize, Deserializer, IntoDeserializer}; 114 | use serde_derive::Deserialize; 115 | use serde_untagged::UntaggedEnumVisitor; 116 | 117 | pub enum LinkTimeOptimization { 118 | Enabled(bool), 119 | Enum(LinkTimeOptimizationString), 120 | } 121 | 122 | impl<'de> Deserialize<'de> for LinkTimeOptimization { 123 | fn deserialize(deserializer: D) -> Result 124 | where 125 | D: Deserializer<'de>, 126 | { 127 | UntaggedEnumVisitor::new() 128 | .bool(|b| Ok(LinkTimeOptimization::Enabled(b))) 129 | .string(|string| { 130 | let de = string.into_deserializer(); 131 | LinkTimeOptimizationString::deserialize(de).map(LinkTimeOptimization::Enum) 132 | }) 133 | .deserialize(deserializer) 134 | } 135 | } 136 | 137 | #[derive(Deserialize)] 138 | #[serde(rename = "lowercase")] 139 | pub enum LinkTimeOptimizationString { 140 | Fat, 141 | Thin, 142 | Off, 143 | } 144 | ``` 145 | 146 | Since `lto = true` means the same thing as `lto = "fat"` to Cargo, there are 147 | really only 4 distinct options. This type could be implemented alternatively as: 148 | 149 | ```rust 150 | use serde::de::{Deserialize, Deserializer, Unexpected}; 151 | use serde_untagged::UntaggedEnumVisitor; 152 | 153 | pub enum LinkTimeOptimization { 154 | ThinLocal, // false 155 | Fat, // true or "fat" 156 | Thin, // "thin" 157 | Off, // "off" 158 | } 159 | 160 | impl<'de> Deserialize<'de> for LinkTimeOptimization { 161 | fn deserialize(deserializer: D) -> Result 162 | where 163 | D: Deserializer<'de>, 164 | { 165 | UntaggedEnumVisitor::new() 166 | .bool(|b| match b { 167 | false => Ok(LinkTimeOptimization::ThinLocal), 168 | true => Ok(LinkTimeOptimization::Fat), 169 | }) 170 | .string(|string| match string { 171 | "fat" => Ok(LinkTimeOptimization::Fat), 172 | "thin" => Ok(LinkTimeOptimization::Thin), 173 | "off" => Ok(LinkTimeOptimization::Off), 174 | _ => Err(serde::de::Error::invalid_value( 175 | Unexpected::Str(string), 176 | &r#""fat" or "thin" or "off""#, 177 | )), 178 | }) 179 | .deserialize(deserializer) 180 | } 181 | } 182 | ``` 183 | 184 |
185 | 186 | #### License 187 | 188 | 189 | Licensed under either of
Apache License, Version 190 | 2.0 or MIT license at your option. 191 | 192 | 193 |
194 | 195 | 196 | Unless you explicitly state otherwise, any contribution intentionally submitted 197 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 198 | be dual licensed as above, without any additional terms or conditions. 199 | 200 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use alloc::borrow::ToOwned; 2 | use alloc::string::{String, ToString}; 3 | use alloc::vec::Vec; 4 | use core::fmt::{self, Debug, Display}; 5 | use serde::de::Expected; 6 | 7 | pub struct Error { 8 | imp: ErrorImpl, 9 | } 10 | 11 | pub(crate) fn erase(err: E) -> Error { 12 | serde::de::Error::custom(err) 13 | } 14 | 15 | pub(crate) fn unerase(err: Error) -> E { 16 | err.as_serde() 17 | } 18 | 19 | impl serde::de::StdError for Error {} 20 | 21 | impl Display for Error { 22 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 23 | let error = self.as_serde::(); 24 | Display::fmt(&error, formatter) 25 | } 26 | } 27 | 28 | impl Debug for Error { 29 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 30 | let error = self.as_serde::(); 31 | Debug::fmt(&error, formatter) 32 | } 33 | } 34 | 35 | enum ErrorImpl { 36 | Custom(String), 37 | InvalidType { 38 | unexpected: Unexpected, 39 | expected: String, 40 | }, 41 | InvalidValue { 42 | unexpected: Unexpected, 43 | expected: String, 44 | }, 45 | InvalidLength { 46 | len: usize, 47 | expected: String, 48 | }, 49 | UnknownVariant { 50 | variant: String, 51 | expected: &'static [&'static str], 52 | }, 53 | UnknownField { 54 | field: String, 55 | expected: &'static [&'static str], 56 | }, 57 | MissingField { 58 | field: &'static str, 59 | }, 60 | DuplicateField { 61 | field: &'static str, 62 | }, 63 | } 64 | 65 | enum Unexpected { 66 | Bool(bool), 67 | Unsigned(u64), 68 | Signed(i64), 69 | Float(f64), 70 | Char(char), 71 | Str(String), 72 | Bytes(Vec), 73 | Unit, 74 | Option, 75 | NewtypeStruct, 76 | Seq, 77 | Map, 78 | Enum, 79 | UnitVariant, 80 | NewtypeVariant, 81 | TupleVariant, 82 | StructVariant, 83 | Other(String), 84 | } 85 | 86 | impl serde::de::Error for Error { 87 | fn custom(msg: T) -> Self { 88 | let imp = ErrorImpl::Custom(msg.to_string()); 89 | Error { imp } 90 | } 91 | 92 | fn invalid_type(unexpected: serde::de::Unexpected, expected: &dyn Expected) -> Self { 93 | let imp = ErrorImpl::InvalidType { 94 | unexpected: Unexpected::from_serde(unexpected), 95 | expected: expected.to_string(), 96 | }; 97 | Error { imp } 98 | } 99 | 100 | fn invalid_value(unexpected: serde::de::Unexpected, expected: &dyn Expected) -> Self { 101 | let imp = ErrorImpl::InvalidValue { 102 | unexpected: Unexpected::from_serde(unexpected), 103 | expected: expected.to_string(), 104 | }; 105 | Error { imp } 106 | } 107 | 108 | fn invalid_length(len: usize, expected: &dyn Expected) -> Self { 109 | let imp = ErrorImpl::InvalidLength { 110 | len, 111 | expected: expected.to_string(), 112 | }; 113 | Error { imp } 114 | } 115 | 116 | fn unknown_variant(variant: &str, expected: &'static [&'static str]) -> Self { 117 | let imp = ErrorImpl::UnknownVariant { 118 | variant: variant.to_owned(), 119 | expected, 120 | }; 121 | Error { imp } 122 | } 123 | 124 | fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self { 125 | let imp = ErrorImpl::UnknownField { 126 | field: field.to_owned(), 127 | expected, 128 | }; 129 | Error { imp } 130 | } 131 | 132 | fn missing_field(field: &'static str) -> Self { 133 | let imp = ErrorImpl::MissingField { field }; 134 | Error { imp } 135 | } 136 | 137 | fn duplicate_field(field: &'static str) -> Self { 138 | let imp = ErrorImpl::DuplicateField { field }; 139 | Error { imp } 140 | } 141 | } 142 | 143 | impl Error { 144 | fn as_serde(&self) -> E { 145 | match &self.imp { 146 | ErrorImpl::Custom(msg) => E::custom(msg), 147 | ErrorImpl::InvalidType { 148 | unexpected, 149 | expected, 150 | } => E::invalid_type(unexpected.as_serde(), &expected.as_str()), 151 | ErrorImpl::InvalidValue { 152 | unexpected, 153 | expected, 154 | } => E::invalid_value(unexpected.as_serde(), &expected.as_str()), 155 | ErrorImpl::InvalidLength { len, expected } => { 156 | E::invalid_length(*len, &expected.as_str()) 157 | } 158 | ErrorImpl::UnknownVariant { variant, expected } => { 159 | E::unknown_variant(variant, expected) 160 | } 161 | ErrorImpl::UnknownField { field, expected } => E::unknown_field(field, expected), 162 | ErrorImpl::MissingField { field } => E::missing_field(field), 163 | ErrorImpl::DuplicateField { field } => E::duplicate_field(field), 164 | } 165 | } 166 | } 167 | 168 | impl Unexpected { 169 | fn from_serde(unexpected: serde::de::Unexpected) -> Self { 170 | match unexpected { 171 | serde::de::Unexpected::Bool(value) => Unexpected::Bool(value), 172 | serde::de::Unexpected::Unsigned(value) => Unexpected::Unsigned(value), 173 | serde::de::Unexpected::Signed(value) => Unexpected::Signed(value), 174 | serde::de::Unexpected::Float(value) => Unexpected::Float(value), 175 | serde::de::Unexpected::Char(value) => Unexpected::Char(value), 176 | serde::de::Unexpected::Str(value) => Unexpected::Str(value.to_owned()), 177 | serde::de::Unexpected::Bytes(value) => Unexpected::Bytes(value.to_owned()), 178 | serde::de::Unexpected::Unit => Unexpected::Unit, 179 | serde::de::Unexpected::Option => Unexpected::Option, 180 | serde::de::Unexpected::NewtypeStruct => Unexpected::NewtypeStruct, 181 | serde::de::Unexpected::Seq => Unexpected::Seq, 182 | serde::de::Unexpected::Map => Unexpected::Map, 183 | serde::de::Unexpected::Enum => Unexpected::Enum, 184 | serde::de::Unexpected::UnitVariant => Unexpected::UnitVariant, 185 | serde::de::Unexpected::NewtypeVariant => Unexpected::NewtypeVariant, 186 | serde::de::Unexpected::TupleVariant => Unexpected::TupleVariant, 187 | serde::de::Unexpected::StructVariant => Unexpected::StructVariant, 188 | serde::de::Unexpected::Other(msg) => Unexpected::Other(msg.to_owned()), 189 | } 190 | } 191 | 192 | fn as_serde(&self) -> serde::de::Unexpected { 193 | match self { 194 | Unexpected::Bool(value) => serde::de::Unexpected::Bool(*value), 195 | Unexpected::Unsigned(value) => serde::de::Unexpected::Unsigned(*value), 196 | Unexpected::Signed(value) => serde::de::Unexpected::Signed(*value), 197 | Unexpected::Float(value) => serde::de::Unexpected::Float(*value), 198 | Unexpected::Char(value) => serde::de::Unexpected::Char(*value), 199 | Unexpected::Str(value) => serde::de::Unexpected::Str(value), 200 | Unexpected::Bytes(value) => serde::de::Unexpected::Bytes(value), 201 | Unexpected::Unit => serde::de::Unexpected::Unit, 202 | Unexpected::Option => serde::de::Unexpected::Option, 203 | Unexpected::NewtypeStruct => serde::de::Unexpected::NewtypeStruct, 204 | Unexpected::Seq => serde::de::Unexpected::Seq, 205 | Unexpected::Map => serde::de::Unexpected::Map, 206 | Unexpected::Enum => serde::de::Unexpected::Enum, 207 | Unexpected::UnitVariant => serde::de::Unexpected::UnitVariant, 208 | Unexpected::NewtypeVariant => serde::de::Unexpected::NewtypeVariant, 209 | Unexpected::TupleVariant => serde::de::Unexpected::TupleVariant, 210 | Unexpected::StructVariant => serde::de::Unexpected::StructVariant, 211 | Unexpected::Other(msg) => serde::de::Unexpected::Other(msg), 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![github]](https://github.com/dtolnay/serde-untagged) [![crates-io]](https://crates.io/crates/serde-untagged) [![docs-rs]](https://docs.rs/serde-untagged) 2 | //! 3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github 4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust 5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs 6 | //! 7 | //!
8 | //! 9 | //! This crate provides a Serde `Visitor` implementation that is useful for 10 | //! deserializing untagged enums. 11 | //! 12 | //! Untagged enum `Deserialize` impls look like this: 13 | //! 14 | //! ``` 15 | //! use serde::de::{Deserialize, Deserializer}; 16 | //! use serde_untagged::UntaggedEnumVisitor; 17 | //! 18 | //! # macro_rules! impl_deserialize { 19 | //! # ($MyType:ty) => { 20 | //! impl<'de> Deserialize<'de> for $MyType { 21 | //! fn deserialize(deserializer: D) -> Result 22 | //! where 23 | //! D: Deserializer<'de>, 24 | //! { 25 | //! UntaggedEnumVisitor::new() 26 | //! /* 27 | //! * 28 | //! */ 29 | //! .deserialize(deserializer) 30 | //! } 31 | //! } 32 | //! # }; 33 | //! # } 34 | //! # 35 | //! # struct MyType; 36 | //! # impl_deserialize!(MyType); 37 | //! ``` 38 | //! 39 | //! Inside the `/* ... */`, we list each type that the untagged enum needs to 40 | //! support deserializing from, giving a closure that turns the input into 41 | //! $MyType. The following types are supported: 42 | //! 43 | //! - bool 44 | //! - i8, i16, i32, i64, i128, u8, u16, u32, u64, u128 45 | //! - f32 46 | //! - f64 47 | //! - char 48 | //! - string 49 | //! - borrowed\_str 50 | //! - bytes 51 | //! - borrowed\_bytes 52 | //! - byte\_buf 53 | //! - unit 54 | //! - seq 55 | //! - map 56 | //! 57 | //! # Example: string or struct 58 | //! 59 | //! Cargo's `http.ssl-version` configuration supports deserialization from the 60 | //! following two representations: 61 | //! 62 | //! ```toml 63 | //! [http] 64 | //! ssl-version = "tlsv1.3" 65 | //! ``` 66 | //! 67 | //! ```toml 68 | //! [http] 69 | //! ssl-version.min = "tlsv1.2" 70 | //! ssl-version.max = "tlsv1.3" 71 | //! ``` 72 | //! 73 | //! ``` 74 | //! use serde::de::{Deserialize, Deserializer}; 75 | //! use serde_derive::Deserialize; 76 | //! use serde_untagged::UntaggedEnumVisitor; 77 | //! 78 | //! pub enum SslVersionConfig { 79 | //! Single(String), 80 | //! Range(SslVersionConfigRange), 81 | //! } 82 | //! 83 | //! impl<'de> Deserialize<'de> for SslVersionConfig { 84 | //! fn deserialize(deserializer: D) -> Result 85 | //! where 86 | //! D: Deserializer<'de>, 87 | //! { 88 | //! UntaggedEnumVisitor::new() 89 | //! .string(|single| Ok(SslVersionConfig::Single(single.to_owned()))) 90 | //! .map(|map| map.deserialize().map(SslVersionConfig::Range)) 91 | //! .deserialize(deserializer) 92 | //! } 93 | //! } 94 | //! 95 | //! #[derive(Deserialize)] 96 | //! pub struct SslVersionConfigRange { 97 | //! pub min: Option, 98 | //! pub max: Option, 99 | //! } 100 | //! ``` 101 | //! 102 | //! # Example: unit variant or bool 103 | //! 104 | //! Cargo's LTO setting in profiles supports the 5 values `false`, `true`, 105 | //! `"fat"`, `"thin"`, and `"off"`. 106 | //! 107 | //! ```toml 108 | //! [profile.release] 109 | //! lto = "thin" 110 | //! ``` 111 | //! 112 | //! ``` 113 | //! use serde::de::{Deserialize, Deserializer, IntoDeserializer}; 114 | //! use serde_derive::Deserialize; 115 | //! use serde_untagged::UntaggedEnumVisitor; 116 | //! 117 | //! pub enum LinkTimeOptimization { 118 | //! Enabled(bool), 119 | //! Enum(LinkTimeOptimizationString), 120 | //! } 121 | //! 122 | //! impl<'de> Deserialize<'de> for LinkTimeOptimization { 123 | //! fn deserialize(deserializer: D) -> Result 124 | //! where 125 | //! D: Deserializer<'de>, 126 | //! { 127 | //! UntaggedEnumVisitor::new() 128 | //! .bool(|b| Ok(LinkTimeOptimization::Enabled(b))) 129 | //! .string(|string| { 130 | //! let de = string.into_deserializer(); 131 | //! LinkTimeOptimizationString::deserialize(de).map(LinkTimeOptimization::Enum) 132 | //! }) 133 | //! .deserialize(deserializer) 134 | //! } 135 | //! } 136 | //! 137 | //! #[derive(Deserialize)] 138 | //! #[serde(rename = "lowercase")] 139 | //! pub enum LinkTimeOptimizationString { 140 | //! Fat, 141 | //! Thin, 142 | //! Off, 143 | //! } 144 | //! ``` 145 | //! 146 | //! Since `lto = true` means the same thing as `lto = "fat"` to Cargo, there are 147 | //! really only 4 distinct options. This type could be implemented alternatively 148 | //! as: 149 | //! 150 | //! ``` 151 | //! use serde::de::{Deserialize, Deserializer, Unexpected}; 152 | //! use serde_untagged::UntaggedEnumVisitor; 153 | //! 154 | //! pub enum LinkTimeOptimization { 155 | //! ThinLocal, // false 156 | //! Fat, // true or "fat" 157 | //! Thin, // "thin" 158 | //! Off, // "off" 159 | //! } 160 | //! 161 | //! impl<'de> Deserialize<'de> for LinkTimeOptimization { 162 | //! fn deserialize(deserializer: D) -> Result 163 | //! where 164 | //! D: Deserializer<'de>, 165 | //! { 166 | //! UntaggedEnumVisitor::new() 167 | //! .bool(|b| match b { 168 | //! false => Ok(LinkTimeOptimization::ThinLocal), 169 | //! true => Ok(LinkTimeOptimization::Fat), 170 | //! }) 171 | //! .string(|string| match string { 172 | //! "fat" => Ok(LinkTimeOptimization::Fat), 173 | //! "thin" => Ok(LinkTimeOptimization::Thin), 174 | //! "off" => Ok(LinkTimeOptimization::Off), 175 | //! _ => Err(serde::de::Error::invalid_value( 176 | //! Unexpected::Str(string), 177 | //! &r#""fat" or "thin" or "off""#, 178 | //! )), 179 | //! }) 180 | //! .deserialize(deserializer) 181 | //! } 182 | //! } 183 | //! ``` 184 | 185 | #![no_std] 186 | #![doc(html_root_url = "https://docs.rs/serde-untagged/0.1.9")] 187 | #![deny(unsafe_op_in_unsafe_fn)] 188 | #![allow( 189 | clippy::doc_markdown, 190 | clippy::elidable_lifetime_names, 191 | clippy::enum_glob_use, 192 | clippy::manual_assert, 193 | clippy::manual_map, 194 | clippy::missing_errors_doc, 195 | clippy::missing_panics_doc, 196 | clippy::must_use_candidate, 197 | clippy::needless_lifetimes, 198 | clippy::needless_pass_by_value, 199 | clippy::new_without_default, 200 | clippy::type_complexity 201 | )] 202 | #![allow(unknown_lints, mismatched_lifetime_syntaxes)] 203 | 204 | extern crate alloc; 205 | extern crate serde_core as serde; 206 | 207 | mod any; 208 | mod error; 209 | mod int; 210 | mod map; 211 | mod seed; 212 | mod seq; 213 | 214 | use crate::error::Error; 215 | use crate::map::Map; 216 | use crate::seq::Seq; 217 | use alloc::boxed::Box; 218 | use alloc::vec::Vec; 219 | use core::fmt::{self, Display}; 220 | use core::marker::PhantomData; 221 | use serde::de::{Deserializer, Expected, MapAccess, SeqAccess, Unexpected, Visitor}; 222 | 223 | pub mod de { 224 | pub use crate::error::Error; 225 | pub use crate::map::Map; 226 | pub use crate::seq::Seq; 227 | } 228 | 229 | pub struct UntaggedEnumVisitor<'closure, 'de, Value> { 230 | expecting: Option>, 231 | visit_bool: Option Result + 'closure>>, 232 | visit_i8: Option Result + 'closure>>, 233 | visit_i16: Option Result + 'closure>>, 234 | visit_i32: Option Result + 'closure>>, 235 | visit_i64: Option Result + 'closure>>, 236 | visit_i128: Option Result + 'closure>>, 237 | visit_u8: Option Result + 'closure>>, 238 | visit_u16: Option Result + 'closure>>, 239 | visit_u32: Option Result + 'closure>>, 240 | visit_u64: Option Result + 'closure>>, 241 | visit_u128: Option Result + 'closure>>, 242 | visit_f32: Option Result + 'closure>>, 243 | visit_f64: Option Result + 'closure>>, 244 | visit_char: Option Result + 'closure>>, 245 | visit_str: Option Result + 'closure>>, 246 | visit_borrowed_str: Option Result + 'closure>>, 247 | visit_bytes: Option Result + 'closure>>, 248 | visit_borrowed_bytes: Option Result + 'closure>>, 249 | visit_byte_buf: Option) -> Result + 'closure>>, 250 | visit_none: Option Result + 'closure>>, 251 | visit_unit: Option Result + 'closure>>, 252 | visit_seq: 253 | Option FnOnce(Seq<'access, 'de>) -> Result + 'closure>>, 254 | visit_map: 255 | Option FnOnce(Map<'access, 'de>) -> Result + 'closure>>, 256 | } 257 | 258 | impl<'closure, 'de, Value> UntaggedEnumVisitor<'closure, 'de, Value> { 259 | pub fn new() -> Self { 260 | UntaggedEnumVisitor { 261 | expecting: None, 262 | visit_bool: None, 263 | visit_i8: None, 264 | visit_i16: None, 265 | visit_i32: None, 266 | visit_i64: None, 267 | visit_i128: None, 268 | visit_u8: None, 269 | visit_u16: None, 270 | visit_u32: None, 271 | visit_u64: None, 272 | visit_u128: None, 273 | visit_f32: None, 274 | visit_f64: None, 275 | visit_char: None, 276 | visit_str: None, 277 | visit_borrowed_str: None, 278 | visit_bytes: None, 279 | visit_borrowed_bytes: None, 280 | visit_byte_buf: None, 281 | visit_none: None, 282 | visit_unit: None, 283 | visit_seq: None, 284 | visit_map: None, 285 | } 286 | } 287 | 288 | /// Provide a message stating what data this untagged enum expects to 289 | /// receive. 290 | /// 291 | /// This is used in error messages when deserialization fails. The message 292 | /// should complete the sentence _"This Visitor expects to receive …"_, for 293 | /// example the message could be _"an integer, or map containing the keys 294 | /// 'min' and 'max'"_. The message should not be capitalized and should not 295 | /// end with a period. 296 | /// 297 | /// ``` 298 | /// # use serde::de::Deserializer; 299 | /// # use serde_untagged::UntaggedEnumVisitor; 300 | /// # 301 | /// # fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> 302 | /// # where 303 | /// # D: Deserializer<'de>, 304 | /// # { 305 | /// # let max = 1; 306 | /// UntaggedEnumVisitor::new() 307 | /// .expecting(format_args!("a string or number between 0 and {max}")) 308 | /// /* ... */ 309 | /// .deserialize(deserializer) 310 | /// # } 311 | /// ``` 312 | /// 313 | /// If `expecting` is not called, then `UntaggedEnumVisitor` constructs a 314 | /// default message based on the set of closures given to it. 315 | /// 316 | /// ``` 317 | /// # use serde::de::Deserializer; 318 | /// # use serde_untagged::UntaggedEnumVisitor; 319 | /// # 320 | /// # macro_rules! methods { 321 | /// # ($($construct:ident)::*() $(.$name:ident(|$arg:ident| ...))*) => { 322 | /// # $($construct)::*() 323 | /// # $( 324 | /// # .$name(|$arg| unimplemented!()) 325 | /// # )* 326 | /// # }; 327 | /// # } 328 | /// # 329 | /// # fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> 330 | /// # where 331 | /// # D: Deserializer<'de>, 332 | /// # { 333 | /// # methods!( 334 | /// // by default, this enum expects "a boolean, string, or map" 335 | /// UntaggedEnumVisitor::new() 336 | /// .bool(|b| ...) 337 | /// .string(|s| ...) 338 | /// .map(|m| ...) 339 | /// # ) 340 | /// .deserialize(deserializer) 341 | /// # } 342 | /// ``` 343 | #[must_use] 344 | pub fn expecting(mut self, expecting: impl Display + 'closure) -> Self { 345 | if self.expecting.is_some() { 346 | panic!("UntaggedEnumVisitor::expecting already set"); 347 | } 348 | self.expecting = Some(Box::new(expecting)); 349 | self 350 | } 351 | 352 | #[must_use] 353 | pub fn bool(mut self, visit: impl FnOnce(bool) -> Result + 'closure) -> Self { 354 | if self.visit_bool.is_some() { 355 | panic!("UntaggedEnumVisitor::bool already set"); 356 | } 357 | self.visit_bool = Some(Box::new(visit)); 358 | self 359 | } 360 | 361 | #[must_use] 362 | pub fn i8(mut self, visit: impl FnOnce(i8) -> Result + 'closure) -> Self { 363 | if self.visit_i8.is_some() { 364 | panic!("UntaggedEnumVisitor::i8 already set"); 365 | } 366 | self.visit_i8 = Some(Box::new(visit)); 367 | self 368 | } 369 | 370 | #[must_use] 371 | pub fn i16(mut self, visit: impl FnOnce(i16) -> Result + 'closure) -> Self { 372 | if self.visit_i16.is_some() { 373 | panic!("UntaggedEnumVisitor::i16 already set"); 374 | } 375 | self.visit_i16 = Some(Box::new(visit)); 376 | self 377 | } 378 | 379 | #[must_use] 380 | pub fn i32(mut self, visit: impl FnOnce(i32) -> Result + 'closure) -> Self { 381 | if self.visit_i32.is_some() { 382 | panic!("UntaggedEnumVisitor::i32 already set"); 383 | } 384 | self.visit_i32 = Some(Box::new(visit)); 385 | self 386 | } 387 | 388 | #[must_use] 389 | pub fn i64(mut self, visit: impl FnOnce(i64) -> Result + 'closure) -> Self { 390 | if self.visit_i64.is_some() { 391 | panic!("UntaggedEnumVisitor::i64 already set"); 392 | } 393 | self.visit_i64 = Some(Box::new(visit)); 394 | self 395 | } 396 | 397 | #[must_use] 398 | pub fn i128(mut self, visit: impl FnOnce(i128) -> Result + 'closure) -> Self { 399 | if self.visit_i128.is_some() { 400 | panic!("UntaggedEnumVisitor::i128 already set"); 401 | } 402 | self.visit_i128 = Some(Box::new(visit)); 403 | self 404 | } 405 | 406 | #[must_use] 407 | pub fn u8(mut self, visit: impl FnOnce(u8) -> Result + 'closure) -> Self { 408 | if self.visit_u8.is_some() { 409 | panic!("UntaggedEnumVisitor::u8 already set"); 410 | } 411 | self.visit_u8 = Some(Box::new(visit)); 412 | self 413 | } 414 | 415 | #[must_use] 416 | pub fn u16(mut self, visit: impl FnOnce(u16) -> Result + 'closure) -> Self { 417 | if self.visit_u16.is_some() { 418 | panic!("UntaggedEnumVisitor::u16 already set"); 419 | } 420 | self.visit_u16 = Some(Box::new(visit)); 421 | self 422 | } 423 | 424 | #[must_use] 425 | pub fn u32(mut self, visit: impl FnOnce(u32) -> Result + 'closure) -> Self { 426 | if self.visit_u32.is_some() { 427 | panic!("UntaggedEnumVisitor::u32 already set"); 428 | } 429 | self.visit_u32 = Some(Box::new(visit)); 430 | self 431 | } 432 | 433 | #[must_use] 434 | pub fn u64(mut self, visit: impl FnOnce(u64) -> Result + 'closure) -> Self { 435 | if self.visit_u64.is_some() { 436 | panic!("UntaggedEnumVisitor::u64 already set"); 437 | } 438 | self.visit_u64 = Some(Box::new(visit)); 439 | self 440 | } 441 | 442 | #[must_use] 443 | pub fn u128(mut self, visit: impl FnOnce(u128) -> Result + 'closure) -> Self { 444 | if self.visit_u128.is_some() { 445 | panic!("UntaggedEnumVisitor::u128 already set"); 446 | } 447 | self.visit_u128 = Some(Box::new(visit)); 448 | self 449 | } 450 | 451 | #[must_use] 452 | pub fn f32(mut self, visit: impl FnOnce(f32) -> Result + 'closure) -> Self { 453 | if self.visit_f32.is_some() { 454 | panic!("UntaggedEnumVisitor::f32 already set"); 455 | } 456 | self.visit_f32 = Some(Box::new(visit)); 457 | self 458 | } 459 | 460 | #[must_use] 461 | pub fn f64(mut self, visit: impl FnOnce(f64) -> Result + 'closure) -> Self { 462 | if self.visit_f64.is_some() { 463 | panic!("UntaggedEnumVisitor::f64 already set"); 464 | } 465 | self.visit_f64 = Some(Box::new(visit)); 466 | self 467 | } 468 | 469 | #[must_use] 470 | pub fn char(mut self, visit: impl FnOnce(char) -> Result + 'closure) -> Self { 471 | if self.visit_char.is_some() { 472 | panic!("UntaggedEnumVisitor::char already set"); 473 | } 474 | self.visit_char = Some(Box::new(visit)); 475 | self 476 | } 477 | 478 | #[must_use] 479 | pub fn string(mut self, visit: impl FnOnce(&str) -> Result + 'closure) -> Self { 480 | if self.visit_str.is_some() { 481 | panic!("UntaggedEnumVisitor::string already set"); 482 | } 483 | self.visit_str = Some(Box::new(visit)); 484 | self 485 | } 486 | 487 | #[must_use] 488 | pub fn borrowed_str( 489 | mut self, 490 | visit: impl FnOnce(&'de str) -> Result + 'closure, 491 | ) -> Self { 492 | if self.visit_borrowed_str.is_some() { 493 | panic!("UntaggedEnumVisitor::borrowed_str already set"); 494 | } 495 | self.visit_borrowed_str = Some(Box::new(visit)); 496 | self 497 | } 498 | 499 | #[must_use] 500 | pub fn bytes(mut self, visit: impl FnOnce(&[u8]) -> Result + 'closure) -> Self { 501 | if self.visit_bytes.is_some() { 502 | panic!("UntaggedEnumVisitor::bytes already set"); 503 | } 504 | self.visit_bytes = Some(Box::new(visit)); 505 | self 506 | } 507 | 508 | #[must_use] 509 | pub fn borrowed_bytes( 510 | mut self, 511 | visit: impl FnOnce(&'de [u8]) -> Result + 'closure, 512 | ) -> Self { 513 | if self.visit_borrowed_bytes.is_some() { 514 | panic!("UntaggedEnumVisitor::borrowed_bytes already set"); 515 | } 516 | self.visit_borrowed_bytes = Some(Box::new(visit)); 517 | self 518 | } 519 | 520 | #[must_use] 521 | pub fn byte_buf( 522 | mut self, 523 | visit: impl FnOnce(Vec) -> Result + 'closure, 524 | ) -> Self { 525 | if self.visit_byte_buf.is_some() { 526 | panic!("UntaggedEnumVisitor::byte_buf already set"); 527 | } 528 | self.visit_byte_buf = Some(Box::new(visit)); 529 | self 530 | } 531 | 532 | #[must_use] 533 | pub fn none(mut self, visit: impl FnOnce() -> Result + 'closure) -> Self { 534 | if self.visit_none.is_some() { 535 | panic!("UntaggedEnumVisitor::none already set"); 536 | } 537 | self.visit_none = Some(Box::new(visit)); 538 | self 539 | } 540 | 541 | #[must_use] 542 | pub fn unit(mut self, visit: impl FnOnce() -> Result + 'closure) -> Self { 543 | if self.visit_unit.is_some() { 544 | panic!("UntaggedEnumVisitor::unit already set"); 545 | } 546 | self.visit_unit = Some(Box::new(visit)); 547 | self 548 | } 549 | 550 | /// Deserialize a sequence. The argument implements 551 | /// [`serde::de::SeqAccess`]. 552 | #[must_use] 553 | pub fn seq( 554 | mut self, 555 | visit: impl for<'access> FnOnce(Seq<'access, 'de>) -> Result + 'closure, 556 | ) -> Self { 557 | if self.visit_seq.is_some() { 558 | panic!("UntaggedEnumVisitor::seq already set"); 559 | } 560 | self.visit_seq = Some(Box::new(visit)); 561 | self 562 | } 563 | 564 | /// Deserialize a key-value map. The argument implements 565 | /// [`serde::de::MapAccess`]. 566 | /// 567 | /// ``` 568 | /// # use serde::de::Deserializer; 569 | /// use serde::de::MapAccess; 570 | /// use serde_untagged::UntaggedEnumVisitor; 571 | /// use std::collections::HashMap; 572 | /// 573 | /// # fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> 574 | /// # where 575 | /// # D: Deserializer<'de>, 576 | /// # { 577 | /// UntaggedEnumVisitor::new() 578 | /// .map(|mut map| { 579 | /// let mut hashmap = HashMap::new(); 580 | /// while let Some(key) = map.next_key()? { 581 | /// let value = map.next_value()?; 582 | /// hashmap.insert(key, value); 583 | /// } 584 | /// Ok(hashmap) 585 | /// }) 586 | /// .deserialize(deserializer) 587 | /// # } 588 | /// ``` 589 | /// 590 | /// If you need to inspect the contents of the map to decide how to 591 | /// deserialize, you can buffer it into some kind of `Value` and deserialize 592 | /// from there. 593 | /// 594 | /// ``` 595 | /// # use serde::de::{Deserialize, Deserializer}; 596 | /// # use serde_untagged::UntaggedEnumVisitor; 597 | /// # 598 | /// enum Response { 599 | /// // {"failure":"..."} 600 | /// Failure(String), 601 | /// // Anything else. {"ok":200} 602 | /// Success(serde_json::Value), 603 | /// } 604 | /// 605 | /// impl<'de> Deserialize<'de> for Response { 606 | /// fn deserialize(deserializer: D) -> Result 607 | /// where 608 | /// D: Deserializer<'de>, 609 | /// { 610 | /// UntaggedEnumVisitor::new() 611 | /// .map(|map| { 612 | /// let value: serde_json::Value = map.deserialize()?; 613 | /// if let Some(failure) = value["failure"].as_str() { 614 | /// Ok(Response::Failure(failure.to_owned())) 615 | /// } else { 616 | /// Ok(Response::Success(value)) 617 | /// } 618 | /// }) 619 | /// .deserialize(deserializer) 620 | /// } 621 | /// } 622 | /// ``` 623 | #[must_use] 624 | pub fn map( 625 | mut self, 626 | visit: impl for<'access> FnOnce(Map<'access, 'de>) -> Result + 'closure, 627 | ) -> Self { 628 | if self.visit_map.is_some() { 629 | panic!("UntaggedEnumVisitor::map already set"); 630 | } 631 | self.visit_map = Some(Box::new(visit)); 632 | self 633 | } 634 | 635 | pub fn deserialize(self, deserializer: D) -> Result 636 | where 637 | D: Deserializer<'de>, 638 | { 639 | deserializer.deserialize_any(self) 640 | } 641 | } 642 | 643 | impl<'closure, 'de, Value> Visitor<'de> for UntaggedEnumVisitor<'closure, 'de, Value> { 644 | type Value = Value; 645 | 646 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 647 | if let Some(expecting) = &self.expecting { 648 | return expecting.fmt(formatter); 649 | } 650 | 651 | // "a string or array" 652 | // "an integer, string, or map" 653 | let mut message = Expecting::new(formatter); 654 | if self.visit_bool.is_some() { 655 | message.push("a", "boolean")?; 656 | } 657 | if self.visit_i8.is_some() 658 | || self.visit_i16.is_some() 659 | || self.visit_i32.is_some() 660 | || self.visit_i64.is_some() 661 | || self.visit_i128.is_some() 662 | || self.visit_u8.is_some() 663 | || self.visit_u16.is_some() 664 | || self.visit_u32.is_some() 665 | || self.visit_u64.is_some() 666 | || self.visit_u128.is_some() 667 | { 668 | message.push("an", "integer")?; 669 | } 670 | if self.visit_f32.is_some() || self.visit_f64.is_some() { 671 | message.push("a", "float")?; 672 | } 673 | if self.visit_char.is_some() { 674 | message.push("a", "character")?; 675 | } 676 | if self.visit_str.is_some() { 677 | message.push("a", "string")?; 678 | } 679 | if self.visit_borrowed_str.is_some() && self.visit_str.is_none() { 680 | message.push("a", "borrowed string")?; 681 | } 682 | if self.visit_bytes.is_some() 683 | || self.visit_borrowed_bytes.is_some() 684 | || self.visit_byte_buf.is_some() 685 | { 686 | message.push("a", "byte array")?; 687 | } 688 | if self.visit_unit.is_some() || self.visit_none.is_some() { 689 | message.push("", "null")?; 690 | } 691 | if self.visit_seq.is_some() { 692 | message.push("an", "array")?; 693 | } 694 | if self.visit_map.is_some() { 695 | message.push("a", "map")?; 696 | } 697 | message.flush() 698 | } 699 | 700 | fn visit_bool(self, v: bool) -> Result 701 | where 702 | E: serde::de::Error, 703 | { 704 | if let Some(visit_bool) = self.visit_bool { 705 | visit_bool(v).map_err(error::unerase) 706 | } else { 707 | DefaultVisitor::new(&self).visit_bool(v) 708 | } 709 | } 710 | 711 | fn visit_i8(self, v: i8) -> Result 712 | where 713 | E: serde::de::Error, 714 | { 715 | use crate::int::IntKind::*; 716 | self.dispatch_integer(v, [I8, I16, I32, I64, I128, U8, U16, U32, U64, U128]) 717 | } 718 | 719 | fn visit_i16(self, v: i16) -> Result 720 | where 721 | E: serde::de::Error, 722 | { 723 | use crate::int::IntKind::*; 724 | self.dispatch_integer(v, [I16, I32, I64, I128, I8, U8, U16, U32, U64, U128]) 725 | } 726 | 727 | fn visit_i32(self, v: i32) -> Result 728 | where 729 | E: serde::de::Error, 730 | { 731 | use crate::int::IntKind::*; 732 | self.dispatch_integer(v, [I32, I64, I128, I8, I16, U8, U16, U32, U64, U128]) 733 | } 734 | 735 | fn visit_i64(self, v: i64) -> Result 736 | where 737 | E: serde::de::Error, 738 | { 739 | use crate::int::IntKind::*; 740 | self.dispatch_integer(v, [I64, I128, I8, I16, I32, U8, U16, U32, U64, U128]) 741 | } 742 | 743 | fn visit_i128(self, v: i128) -> Result 744 | where 745 | E: serde::de::Error, 746 | { 747 | use crate::int::IntKind::*; 748 | self.dispatch_integer(v, [I128, I8, I16, I32, I64, U8, U16, U32, U64, U128]) 749 | } 750 | 751 | fn visit_u8(self, v: u8) -> Result 752 | where 753 | E: serde::de::Error, 754 | { 755 | use crate::int::IntKind::*; 756 | self.dispatch_integer(v, [U8, U16, U32, U64, U128, I8, I16, I32, I64, I128]) 757 | } 758 | 759 | fn visit_u16(self, v: u16) -> Result 760 | where 761 | E: serde::de::Error, 762 | { 763 | use crate::int::IntKind::*; 764 | self.dispatch_integer(v, [U16, U32, U64, U128, U8, I8, I16, I32, I64, I128]) 765 | } 766 | 767 | fn visit_u32(self, v: u32) -> Result 768 | where 769 | E: serde::de::Error, 770 | { 771 | use crate::int::IntKind::*; 772 | self.dispatch_integer(v, [U32, U64, U128, U8, U16, I8, I16, I32, I64, I128]) 773 | } 774 | 775 | fn visit_u64(self, v: u64) -> Result 776 | where 777 | E: serde::de::Error, 778 | { 779 | use crate::int::IntKind::*; 780 | self.dispatch_integer(v, [U64, U128, U8, U16, U32, I8, I16, I32, I64, I128]) 781 | } 782 | 783 | fn visit_u128(self, v: u128) -> Result 784 | where 785 | E: serde::de::Error, 786 | { 787 | use crate::int::IntKind::*; 788 | self.dispatch_integer(v, [U128, U8, U16, U32, U64, I8, I16, I32, I64, I128]) 789 | } 790 | 791 | fn visit_f32(self, v: f32) -> Result 792 | where 793 | E: serde::de::Error, 794 | { 795 | if let Some(visit_f32) = self.visit_f32 { 796 | visit_f32(v).map_err(error::unerase) 797 | } else { 798 | self.visit_f64(f64::from(v)) 799 | } 800 | } 801 | 802 | fn visit_f64(self, v: f64) -> Result 803 | where 804 | E: serde::de::Error, 805 | { 806 | if let Some(visit_f64) = self.visit_f64 { 807 | visit_f64(v).map_err(error::unerase) 808 | } else { 809 | DefaultVisitor::new(&self).visit_f64(v) 810 | } 811 | } 812 | 813 | fn visit_char(self, v: char) -> Result 814 | where 815 | E: serde::de::Error, 816 | { 817 | if let Some(visit_char) = self.visit_char { 818 | visit_char(v).map_err(error::unerase) 819 | } else if self.visit_str.is_some() { 820 | self.visit_str(v.encode_utf8(&mut [0u8; 4])) 821 | } else { 822 | Err(E::invalid_type(Unexpected::Char(v), &self)) 823 | } 824 | } 825 | 826 | fn visit_str(self, v: &str) -> Result 827 | where 828 | E: serde::de::Error, 829 | { 830 | if let Some(visit_str) = self.visit_str { 831 | visit_str(v).map_err(error::unerase) 832 | } else { 833 | DefaultVisitor::new(&self).visit_str(v) 834 | } 835 | } 836 | 837 | fn visit_borrowed_str(self, v: &'de str) -> Result 838 | where 839 | E: serde::de::Error, 840 | { 841 | if let Some(visit_borrowed_str) = self.visit_borrowed_str { 842 | visit_borrowed_str(v).map_err(error::unerase) 843 | } else { 844 | self.visit_str(v) 845 | } 846 | } 847 | 848 | fn visit_bytes(self, v: &[u8]) -> Result 849 | where 850 | E: serde::de::Error, 851 | { 852 | if let Some(visit_bytes) = self.visit_bytes { 853 | visit_bytes(v).map_err(error::unerase) 854 | } else { 855 | DefaultVisitor::new(&self).visit_bytes(v) 856 | } 857 | } 858 | 859 | fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result 860 | where 861 | E: serde::de::Error, 862 | { 863 | if let Some(visit_borrowed_bytes) = self.visit_borrowed_bytes { 864 | visit_borrowed_bytes(v).map_err(error::unerase) 865 | } else { 866 | self.visit_bytes(v) 867 | } 868 | } 869 | 870 | fn visit_byte_buf(self, v: Vec) -> Result 871 | where 872 | E: serde::de::Error, 873 | { 874 | if let Some(visit_byte_buf) = self.visit_byte_buf { 875 | visit_byte_buf(v).map_err(error::unerase) 876 | } else { 877 | self.visit_bytes(&v) 878 | } 879 | } 880 | 881 | fn visit_none(self) -> Result 882 | where 883 | E: serde::de::Error, 884 | { 885 | if let Some(visit_none) = self.visit_none { 886 | visit_none().map_err(error::unerase) 887 | } else { 888 | DefaultVisitor::new(&self).visit_none() 889 | } 890 | } 891 | 892 | fn visit_unit(self) -> Result 893 | where 894 | E: serde::de::Error, 895 | { 896 | if let Some(visit_unit) = self.visit_unit { 897 | visit_unit().map_err(error::unerase) 898 | } else { 899 | DefaultVisitor::new(&self).visit_unit() 900 | } 901 | } 902 | 903 | fn visit_seq(self, seq: A) -> Result 904 | where 905 | A: SeqAccess<'de>, 906 | { 907 | if let Some(visit_seq) = self.visit_seq { 908 | visit_seq(Seq::new(seq)).map_err(error::unerase) 909 | } else { 910 | DefaultVisitor::new(&self).visit_seq(seq) 911 | } 912 | } 913 | 914 | fn visit_map(self, map: A) -> Result 915 | where 916 | A: MapAccess<'de>, 917 | { 918 | if let Some(visit_map) = self.visit_map { 919 | visit_map(Map::new(map)).map_err(error::unerase) 920 | } else { 921 | DefaultVisitor::new(&self).visit_map(map) 922 | } 923 | } 924 | } 925 | 926 | struct DefaultVisitor<'a, E, T> { 927 | expected: &'a E, 928 | value: PhantomData, 929 | } 930 | 931 | impl<'a, E, T> DefaultVisitor<'a, E, T> { 932 | fn new(expected: &'a E) -> Self { 933 | DefaultVisitor { 934 | expected, 935 | value: PhantomData, 936 | } 937 | } 938 | } 939 | 940 | impl<'a, 'de, V, T> Visitor<'de> for DefaultVisitor<'a, V, T> 941 | where 942 | V: Expected, 943 | { 944 | type Value = T; 945 | 946 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 947 | self.expected.fmt(formatter) 948 | } 949 | } 950 | 951 | struct Expecting<'e, 'a> { 952 | formatter: &'e mut fmt::Formatter<'a>, 953 | count: usize, 954 | last: Option<&'e str>, 955 | } 956 | 957 | impl<'e, 'a> Expecting<'e, 'a> { 958 | fn new(formatter: &'e mut fmt::Formatter<'a>) -> Self { 959 | Expecting { 960 | formatter, 961 | count: 0, 962 | last: None, 963 | } 964 | } 965 | 966 | fn push(&mut self, article: &str, item: &'e str) -> fmt::Result { 967 | self.count += 1; 968 | if self.count == 1 { 969 | if !article.is_empty() { 970 | self.formatter.write_str(article)?; 971 | self.formatter.write_str(" ")?; 972 | } 973 | self.formatter.write_str(item)?; 974 | } else { 975 | if let Some(last) = self.last.take() { 976 | self.formatter.write_str(", ")?; 977 | self.formatter.write_str(last)?; 978 | } 979 | self.last = Some(item); 980 | } 981 | Ok(()) 982 | } 983 | 984 | fn flush(&mut self) -> fmt::Result { 985 | if self.count == 0 { 986 | self.formatter.write_str("unspecified") // ?? 987 | } else if let Some(last) = self.last.take() { 988 | self.formatter.write_str(" or ")?; 989 | self.formatter.write_str(last) 990 | } else { 991 | Ok(()) 992 | } 993 | } 994 | } 995 | --------------------------------------------------------------------------------