diff --git a/Cargo.lock b/Cargo.lock
index bf01f4ca4..0c1035e0f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -296,9 +296,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
 [[package]]
 name = "jiter"
-version = "0.8.2"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8243cf2d026264056bfacf305e54f5bee8866fd46b4c1873adcaebf614a0d306"
+checksum = "c024ccb0ed468a474efa325edea34d4198fb601d290c4d1bc24fe31ed11902fc"
 dependencies = [
  "ahash",
  "bitvec",
@@ -312,9 +312,9 @@ dependencies = [
 
 [[package]]
 name = "lexical-parse-float"
-version = "0.8.5"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f"
+checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2"
 dependencies = [
  "lexical-parse-integer",
  "lexical-util",
@@ -323,9 +323,9 @@ dependencies = [
 
 [[package]]
 name = "lexical-parse-integer"
-version = "0.8.6"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9"
+checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e"
 dependencies = [
  "lexical-util",
  "static_assertions",
@@ -333,9 +333,9 @@ dependencies = [
 
 [[package]]
 name = "lexical-util"
-version = "0.8.5"
+version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc"
+checksum = "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3"
 dependencies = [
  "static_assertions",
 ]
@@ -449,9 +449,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3"
-version = "0.23.5"
+version = "0.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872"
+checksum = "7f1c6c3591120564d64db2261bec5f910ae454f01def849b9c22835a84695e86"
 dependencies = [
  "cfg-if",
  "indoc",
@@ -468,9 +468,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-build-config"
-version = "0.23.5"
+version = "0.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb"
+checksum = "e9b6c2b34cf71427ea37c7001aefbaeb85886a074795e35f161f5aecc7620a7a"
 dependencies = [
  "once_cell",
  "python3-dll-a",
@@ -479,9 +479,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-ffi"
-version = "0.23.5"
+version = "0.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d"
+checksum = "5507651906a46432cdda02cd02dd0319f6064f1374c9147c45b978621d2c3a9c"
 dependencies = [
  "libc",
  "pyo3-build-config",
@@ -489,9 +489,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-macros"
-version = "0.23.5"
+version = "0.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da"
+checksum = "b0d394b5b4fd8d97d48336bb0dd2aebabad39f1d294edd6bcd2cccf2eefe6f42"
 dependencies = [
  "proc-macro2",
  "pyo3-macros-backend",
@@ -501,9 +501,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-macros-backend"
-version = "0.23.5"
+version = "0.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028"
+checksum = "fd72da09cfa943b1080f621f024d2ef7e2773df7badd51aa30a2be1f8caa7c8e"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -690,9 +690,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
 
 [[package]]
 name = "target-lexicon"
-version = "0.12.14"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
+checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
 
 [[package]]
 name = "tinystr"
diff --git a/Cargo.toml b/Cargo.toml
index 13af5c552..16dc843c4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,7 +29,7 @@ rust-version = "1.75"
 [dependencies]
 # TODO it would be very nice to remove the "py-clone" feature as it can panic,
 # but needs a bit of work to make sure it's not used in the codebase
-pyo3 = { version = "0.23.5", features = ["generate-import-lib", "num-bigint", "py-clone"] }
+pyo3 = { version = "0.24", features = ["generate-import-lib", "num-bigint", "py-clone"] }
 regex = "1.11.1"
 strum = { version = "0.26.3", features = ["derive"] }
 strum_macros = "0.26.4"
@@ -45,7 +45,7 @@ idna = "1.0.3"
 base64 = "0.22.1"
 num-bigint = "0.4.6"
 uuid = "1.15.1"
-jiter = { version = "0.8.2", features = ["python"] }
+jiter = { version = "0.9.0", features = ["python"] }
 hex = "0.4.3"
 
 [lib]
@@ -73,12 +73,12 @@ debug = true
 strip = false
 
 [dev-dependencies]
-pyo3 = { version = "0.23.5", features = ["auto-initialize"] }
+pyo3 = { version = "0.24", features = ["auto-initialize"] }
 
 [build-dependencies]
 version_check = "0.9.5"
 # used where logic has to be version/distribution specific, e.g. pypy
-pyo3-build-config = { version = "0.23.5" }
+pyo3-build-config = { version = "0.24" }
 
 [lints.clippy]
 dbg_macro = "warn"
diff --git a/src/input/input_json.rs b/src/input/input_json.rs
index 139c71a25..6479357cc 100644
--- a/src/input/input_json.rs
+++ b/src/input/input_json.rs
@@ -1,9 +1,8 @@
 use std::borrow::Cow;
 
-use jiter::{JsonArray, JsonObject, JsonValue, LazyIndexMap};
+use jiter::{JsonArray, JsonObject, JsonValue};
 use pyo3::prelude::*;
 use pyo3::types::{PyDict, PyList, PyString};
-use smallvec::SmallVec;
 use speedate::MicrosecondsPrecisionOverflowBehavior;
 use strum::EnumMessage;
 
@@ -62,7 +61,9 @@ impl<'py, 'data> Input<'py> for JsonValue<'data> {
         match self {
             JsonValue::Object(object) => {
                 let dict = PyDict::new(py);
-                for (k, v) in LazyIndexMap::iter(object) {
+                for (k, v) in object.as_slice() {
+                    // TODO: jiter doesn't deduplicate keys, so we should probably do that here to
+                    // avoid potential wasted work creating Python objects.
                     dict.set_item(k, v).unwrap();
                 }
                 Some(dict)
@@ -253,7 +254,14 @@ impl<'py, 'data> Input<'py> for JsonValue<'data> {
             JsonValue::Str(s) => Ok(string_to_vec(s).into()),
             JsonValue::Object(object) => {
                 // return keys iterator to match python's behavior
-                let keys: JsonArray = JsonArray::new(object.keys().map(|k| JsonValue::Str(k.clone())).collect());
+                // FIXME jiter doesn't deduplicate keys, should probably do that here before iteration.
+                let keys: JsonArray = JsonArray::new(
+                    object
+                        .as_slice()
+                        .iter()
+                        .map(|(k, _)| JsonValue::Str(k.clone()))
+                        .collect(),
+                );
                 Ok(GenericIterator::from(keys).into_static())
             }
             _ => Err(ValError::new(ErrorTypeDefaults::IterableType, self)),
@@ -543,11 +551,11 @@ impl<'data> ValidatedDict<'_> for &'_ JsonObject<'data> {
         &'a self,
         consumer: impl ConsumeIterator<ValResult<(Self::Key<'a>, Self::Item<'a>)>, Output = R>,
     ) -> ValResult<R> {
-        Ok(consumer.consume_iterator(LazyIndexMap::iter(self).map(|(k, v)| Ok((k.as_ref(), v)))))
+        Ok(consumer.consume_iterator(self.as_slice().iter().map(|(k, v)| Ok((k.as_ref(), v)))))
     }
 
     fn last_key(&self) -> Option<Self::Key<'_>> {
-        self.keys().last().map(AsRef::as_ref)
+        self.last().map(|(k, _)| k.as_ref())
     }
 }
 
@@ -555,7 +563,7 @@ impl<'a, 'py, 'data> ValidatedList<'py> for &'a JsonArray<'data> {
     type Item = &'a JsonValue<'data>;
 
     fn len(&self) -> Option<usize> {
-        Some(SmallVec::len(self))
+        Some(Vec::len(self))
     }
     fn iterate<R>(self, consumer: impl ConsumeIterator<PyResult<Self::Item>, Output = R>) -> ValResult<R> {
         Ok(consumer.consume_iterator(self.iter().map(Ok)))
@@ -569,7 +577,7 @@ impl<'a, 'data> ValidatedTuple<'_> for &'a JsonArray<'data> {
     type Item = &'a JsonValue<'data>;
 
     fn len(&self) -> Option<usize> {
-        Some(SmallVec::len(self))
+        Some(Vec::len(self))
     }
     fn iterate<R>(self, consumer: impl ConsumeIterator<PyResult<Self::Item>, Output = R>) -> ValResult<R> {
         Ok(consumer.consume_iterator(self.iter().map(Ok)))
@@ -637,12 +645,12 @@ impl<'data> KeywordArgs<'_> for JsonObject<'data> {
         Self: 'a;
 
     fn len(&self) -> usize {
-        LazyIndexMap::len(self)
+        Vec::len(self)
     }
     fn get_item<'k>(&self, key: &'k LookupKey) -> ValResult<Option<(&'k LookupPath, Self::Item<'_>)>> {
         key.json_get(self)
     }
     fn iter(&self) -> impl Iterator<Item = ValResult<(Self::Key<'_>, Self::Item<'_>)>> {
-        LazyIndexMap::iter(self).map(|(k, v)| Ok((k.as_ref(), v)))
+        self.as_slice().iter().map(|(k, v)| Ok((k.as_ref(), v)))
     }
 }
diff --git a/src/lookup_key.rs b/src/lookup_key.rs
index 9b06db8fa..db8a4c869 100644
--- a/src/lookup_key.rs
+++ b/src/lookup_key.rs
@@ -262,20 +262,33 @@ impl LookupKey {
         &'s self,
         dict: &'a JsonObject<'data>,
     ) -> ValResult<Option<(&'s LookupPath, &'a JsonValue<'data>)>> {
+        // FIXME: use of find_map in here probably leads to quadratic complexity
         match self {
-            Self::Simple(path) => match dict.get(path.first_key()) {
+            Self::Simple(path) => match dict
+                .iter()
+                .rev()
+                .find_map(|(k, v)| (k == path.first_key()).then_some(v))
+            {
                 Some(value) => {
                     debug_assert!(path.rest.is_empty());
                     Ok(Some((path, value)))
                 }
                 None => Ok(None),
             },
-            Self::Choice { path1, path2 } => match dict.get(path1.first_key()) {
+            Self::Choice { path1, path2 } => match dict
+                .iter()
+                .rev()
+                .find_map(|(k, v)| (k == path1.first_key()).then_some(v))
+            {
                 Some(value) => {
                     debug_assert!(path1.rest.is_empty());
                     Ok(Some((path1, value)))
                 }
-                None => match dict.get(path2.first_key()) {
+                None => match dict
+                    .iter()
+                    .rev()
+                    .find_map(|(k, v)| (k == path2.first_key()).then_some(v))
+                {
                     Some(value) => {
                         debug_assert!(path2.rest.is_empty());
                         Ok(Some((path2, value)))
@@ -287,7 +300,11 @@ impl LookupKey {
                 for path in path_choices {
                     // first step is different from the rest as we already know dict is JsonObject
                     // because of above checks, we know that path should have at least one element, hence unwrap
-                    let v: &JsonValue = match dict.get(path.first_item.key.as_str()) {
+                    let v: &JsonValue = match dict
+                        .iter()
+                        .rev()
+                        .find_map(|(k, v)| (k == path.first_key()).then_some(v))
+                    {
                         Some(v) => v,
                         None => continue,
                     };
@@ -527,7 +544,7 @@ impl PathItem {
 
     pub fn json_obj_get<'a, 'data>(&self, json_obj: &'a JsonObject<'data>) -> Option<&'a JsonValue<'data>> {
         match self {
-            Self::S(PathItemString { key, .. }) => json_obj.get(key.as_str()),
+            Self::S(PathItemString { key, .. }) => json_obj.iter().rev().find_map(|(k, v)| (k == key).then_some(v)),
             _ => None,
         }
     }
diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs
index 69c8279ea..0f2cbefc7 100644
--- a/src/validators/dataclass.rs
+++ b/src/validators/dataclass.rs
@@ -668,7 +668,7 @@ impl DataclassValidator {
                 dc.call_method0(post_init)
             } else {
                 let args = post_init_kwargs.downcast::<PyTuple>()?;
-                dc.call_method1(post_init, args)
+                dc.call_method1(post_init, args.clone()) // FIXME should not need clone here
             };
             r.map_err(|e| convert_err(py, e, input))?;
         }