polars_core/series/ops/
json_map.rs1#[cfg(feature = "dtype-map")]
5use polars_arrow::bitmap::{Bitmap, BitmapBuilder};
6
7use crate::chunked_array::cast::CastOptions;
8#[cfg(feature = "dtype-map")]
9use crate::chunked_array::logical::try_apply_map_entries;
10use crate::prelude::*;
11
12impl DataType {
13 pub fn ensure_json_map_keys(&self) -> PolarsResult<()> {
15 if !self.contains_map() {
16 return Ok(());
17 }
18 self.try_visit_with(|_dtype| {
19 #[cfg(feature = "dtype-map")]
20 if let DataType::Map(key, _) = _dtype {
21 polars_ensure!(
22 is_json_map_key(key),
23 ComputeError:
24 "JSON Map keys must be String, Categorical or Enum, got `{key}`.\n\nCast keys to String or use `Expr.map.entries` to write a list of structs."
25 );
26 }
27 Ok(())
28 })
29 }
30
31 pub fn json_decode_dtype(&self) -> DataType {
37 use DataType as D;
38 match self {
39 #[cfg(feature = "dtype-map")]
40 D::Map(_, value) => D::Map(Box::new(D::String), Box::new(value.json_decode_dtype()))
41 .map_storage_dtype()
42 .unwrap(),
43 D::List(inner) => D::List(Box::new(inner.json_decode_dtype())),
44 #[cfg(feature = "dtype-array")]
45 D::Array(inner, _) if inner.contains_map() => {
46 D::List(Box::new(inner.json_decode_dtype()))
47 },
48 #[cfg(feature = "dtype-array")]
49 D::Array(inner, width) => D::Array(Box::new(inner.json_decode_dtype()), *width),
50 #[cfg(feature = "dtype-struct")]
51 D::Struct(fields) => D::Struct(
52 fields
53 .iter()
54 .map(|f| Field::new(f.name.clone(), f.dtype.json_decode_dtype()))
55 .collect(),
56 ),
57 #[cfg(feature = "dtype-extension")]
58 D::Extension(_, storage) if storage.contains_map() => storage.json_decode_dtype(),
59 #[cfg(feature = "dtype-extension")]
60 D::Extension(ext, storage) => {
61 D::Extension(ext.clone(), Box::new(storage.json_decode_dtype()))
62 },
63 #[cfg(feature = "dtype-categorical")]
64 D::Enum(..) | D::Categorical(..) => D::String,
65 dt => dt.clone(),
66 }
67 }
68}
69
70#[cfg(feature = "dtype-map")]
71fn is_json_map_key(dtype: &DataType) -> bool {
72 match dtype {
73 DataType::String => true,
74 #[cfg(feature = "dtype-categorical")]
75 DataType::Categorical(..) | DataType::Enum(..) => true,
76 _ => false,
77 }
78}
79
80impl Series {
81 pub fn from_json_decoded(self, target: &DataType, ignore_errors: bool) -> PolarsResult<Series> {
87 #[cfg(feature = "dtype-map")]
88 if target.contains_map() {
89 target.ensure_json_map_keys()?;
90 return from_json_decoded_rec(&self, target, ignore_errors);
91 }
92 cast_leaf(self, target, ignore_errors)
93 }
94}
95
96fn cast_leaf(s: Series, target: &DataType, ignore_errors: bool) -> PolarsResult<Series> {
97 if s.dtype() == target {
98 return Ok(s);
99 }
100 let options = if ignore_errors {
101 CastOptions::NonStrict
102 } else {
103 CastOptions::Strict
104 };
105 s.cast_with_options(target, options)
106}
107
108#[cfg(feature = "dtype-map")]
109fn from_json_decoded_rec(
110 series: &Series,
111 target: &DataType,
112 ignore_errors: bool,
113) -> PolarsResult<Series> {
114 match target {
115 DataType::Map(key_dtype, value_dtype) => {
116 let mut ok_keys: Option<Bitmap> = None;
117 let storage = try_apply_map_entries(series.list()?, |key, value| {
118 let decoded = key.cast_with_options(key_dtype, CastOptions::NonStrict)?;
120 if let Some(ok) = decoded.rechunk_validity().filter(|v| v.unset_bits() > 0) {
121 if !ignore_errors {
122 let idx = ok.iter().position(|ok| !ok).unwrap();
123 let key = key.str()?.get(idx).unwrap();
124 polars_bail!(
125 ComputeError:
126 "JSON object key \"{key}\" is not a valid Map key of type `{key_dtype}`"
127 );
128 }
129 ok_keys = Some(ok);
130 }
131 let value = from_json_decoded_rec(value, value_dtype, ignore_errors)?;
132 Ok((decoded, value))
133 })?
134 .into_series();
135
136 let storage = match ok_keys {
137 Some(ok) => {
138 null_rows_where(&storage, |start, len| ok.null_count_range(start, len) > 0)
139 },
140 None => storage,
141 };
142 Ok(MapChunked::try_from_storage(target.clone(), storage)?.into_series())
143 },
144 DataType::List(inner) if inner.contains_map() => {
145 let ca = series.list()?;
146 let values = from_json_decoded_rec(&ca.get_inner(), inner, ignore_errors)?;
147 Ok(ca.with_inner_values(&values).into_series())
148 },
149 #[cfg(feature = "dtype-array")]
150 DataType::Array(inner, width) if inner.contains_map() => {
151 let ca = series.list()?;
152 let values = from_json_decoded_rec(&ca.get_inner(), inner, ignore_errors)?;
153 let mut list = ca.with_inner_values(&values).into_series();
154 if ignore_errors {
155 list = null_rows_where(&list, |_, len| len != *width);
156 }
157 list.cast_with_options(target, CastOptions::Strict)
158 },
159 DataType::Struct(fields) if target.contains_map() => {
160 let ca = series.struct_()?;
161 let out = ca.try_apply_fields(|field| {
162 let f = fields
163 .iter()
164 .find(|f| f.name() == field.name())
165 .expect("the decode dtype keeps the target's struct fields");
166 from_json_decoded_rec(field, f.dtype(), ignore_errors)
167 })?;
168 Ok(out.into_series())
169 },
170 #[cfg(feature = "dtype-extension")]
171 DataType::Extension(typ, storage) if storage.contains_map() => {
172 Ok(from_json_decoded_rec(series, storage, ignore_errors)?.into_extension(typ.clone()))
173 },
174 _ => cast_leaf(series.clone(), target, ignore_errors),
175 }
176}
177
178#[cfg(feature = "dtype-map")]
181fn null_rows_where(list: &Series, mut bad: impl FnMut(usize, usize) -> bool) -> Series {
182 let ca = list.list().unwrap();
183 let mut validity = BitmapBuilder::with_capacity(ca.len());
184 let mut changed = false;
185 let mut base = 0;
186 for arr in ca.downcast_iter() {
187 let offsets = arr.offsets();
188 let first = *offsets.first() as usize;
189 for row in 0..arr.len() {
190 let (start, end) = offsets.start_end(row);
191 let valid = arr.is_valid(row);
192 let keep = valid && !bad(base + start - first, end - start);
193 changed |= valid != keep;
194 validity.push(keep);
195 }
196 base += offsets.range() as usize;
197 }
198
199 if changed {
200 list.with_validity(Some(validity.freeze()))
201 } else {
202 list.clone()
203 }
204}