Skip to main content

dicom_toolkit_data/
json.rs

1//! DICOM JSON serialization and deserialization (PS3.18 Annex F).
2//!
3//! Ports DCMTK's `dcjson.h` / `dcjsonrd.h`. The DICOM JSON model encodes each
4//! element as `{ "GGGGEEEE": { "vr": "XX", "Value": [...] } }`.
5
6use crate::dataset::DataSet;
7use crate::element::Element;
8use crate::value::{DicomDate, DicomDateTime, DicomTime, PersonName, PixelData, Value};
9use dicom_toolkit_core::error::{DcmError, DcmResult};
10use dicom_toolkit_dict::{Tag, Vr};
11
12// ── Serialization ─────────────────────────────────────────────────────────────
13
14/// Serialize a `DataSet` to a DICOM JSON string (PS3.18 §F.2).
15pub fn to_json(dataset: &DataSet) -> DcmResult<String> {
16    let obj = dataset_to_json_object(dataset)?;
17    serde_json::to_string(&obj).map_err(|e| DcmError::Other(format!("JSON serialize error: {e}")))
18}
19
20/// Serialize a `DataSet` to a pretty-printed DICOM JSON string.
21pub fn to_json_pretty(dataset: &DataSet) -> DcmResult<String> {
22    let obj = dataset_to_json_object(dataset)?;
23    serde_json::to_string_pretty(&obj)
24        .map_err(|e| DcmError::Other(format!("JSON serialize error: {e}")))
25}
26
27fn dataset_to_json_object(
28    dataset: &DataSet,
29) -> DcmResult<serde_json::Map<String, serde_json::Value>> {
30    let mut map = serde_json::Map::new();
31    for (tag, elem) in dataset.iter() {
32        // Skip group-length tags and sequence delimiter tags
33        if tag.is_group_length() || tag.is_delimiter() {
34            continue;
35        }
36        let key = format!("{:04X}{:04X}", tag.group, tag.element);
37        let json_elem = element_to_json(elem)?;
38        map.insert(key, json_elem);
39    }
40    Ok(map)
41}
42
43fn element_to_json(elem: &Element) -> DcmResult<serde_json::Value> {
44    let vr_str = elem.vr.code().to_string();
45
46    let value_json: Option<serde_json::Value> = match &elem.value {
47        Value::Empty => None,
48
49        Value::Strings(v) => {
50            if v.is_empty() {
51                None
52            } else if elem.vr == Vr::PN {
53                // PN strings stored as raw "Last^First^Middle^Prefix^Suffix" — convert to JSON PN format
54                let arr: Vec<serde_json::Value> = v
55                    .iter()
56                    .map(|s| {
57                        let mut obj = serde_json::Map::new();
58                        if !s.is_empty() {
59                            obj.insert("Alphabetic".into(), serde_json::Value::String(s.clone()));
60                        }
61                        serde_json::Value::Object(obj)
62                    })
63                    .collect();
64                Some(serde_json::Value::Array(arr))
65            } else {
66                Some(serde_json::Value::Array(
67                    v.iter()
68                        .map(|s| serde_json::Value::String(s.clone()))
69                        .collect(),
70                ))
71            }
72        }
73
74        Value::Uid(s) => Some(serde_json::Value::Array(vec![serde_json::Value::String(
75            s.clone(),
76        )])),
77
78        Value::PersonNames(names) => {
79            let arr: Vec<serde_json::Value> = names
80                .iter()
81                .map(|pn| {
82                    let mut obj = serde_json::Map::new();
83                    if !pn.alphabetic.is_empty() {
84                        obj.insert(
85                            "Alphabetic".into(),
86                            serde_json::Value::String(pn.alphabetic.clone()),
87                        );
88                    }
89                    if !pn.ideographic.is_empty() {
90                        obj.insert(
91                            "Ideographic".into(),
92                            serde_json::Value::String(pn.ideographic.clone()),
93                        );
94                    }
95                    if !pn.phonetic.is_empty() {
96                        obj.insert(
97                            "Phonetic".into(),
98                            serde_json::Value::String(pn.phonetic.clone()),
99                        );
100                    }
101                    serde_json::Value::Object(obj)
102                })
103                .collect();
104            if arr.is_empty() {
105                None
106            } else {
107                Some(serde_json::Value::Array(arr))
108            }
109        }
110
111        Value::Date(dates) => Some(serde_json::Value::Array(
112            dates
113                .iter()
114                .map(|d| serde_json::Value::String(d.to_string()))
115                .collect(),
116        )),
117        Value::Time(times) => Some(serde_json::Value::Array(
118            times
119                .iter()
120                .map(|t| serde_json::Value::String(t.to_string()))
121                .collect(),
122        )),
123        Value::DateTime(dts) => Some(serde_json::Value::Array(
124            dts.iter()
125                .map(|dt| serde_json::Value::String(dt.to_string()))
126                .collect(),
127        )),
128
129        Value::Ints(v) => Some(serde_json::Value::Array(
130            v.iter().map(|n| serde_json::json!(n)).collect(),
131        )),
132        Value::Decimals(v) => Some(serde_json::Value::Array(
133            v.iter()
134                .map(|n| {
135                    if n.is_finite() {
136                        serde_json::json!(n)
137                    } else {
138                        serde_json::Value::Null
139                    }
140                })
141                .collect(),
142        )),
143
144        Value::U16(v) => Some(serde_json::Value::Array(
145            v.iter().map(|n| serde_json::json!(n)).collect(),
146        )),
147        Value::I16(v) => Some(serde_json::Value::Array(
148            v.iter().map(|n| serde_json::json!(n)).collect(),
149        )),
150        Value::U32(v) => Some(serde_json::Value::Array(
151            v.iter().map(|n| serde_json::json!(n)).collect(),
152        )),
153        Value::I32(v) => Some(serde_json::Value::Array(
154            v.iter().map(|n| serde_json::json!(n)).collect(),
155        )),
156        Value::U64(v) => Some(serde_json::Value::Array(
157            v.iter().map(|n| serde_json::json!(n)).collect(),
158        )),
159        Value::I64(v) => Some(serde_json::Value::Array(
160            v.iter().map(|n| serde_json::json!(n)).collect(),
161        )),
162        Value::F32(v) => Some(serde_json::Value::Array(
163            v.iter()
164                .map(|n| {
165                    if n.is_finite() {
166                        serde_json::json!(n)
167                    } else {
168                        serde_json::Value::Null
169                    }
170                })
171                .collect(),
172        )),
173        Value::F64(v) => Some(serde_json::Value::Array(
174            v.iter()
175                .map(|n| {
176                    if n.is_finite() {
177                        serde_json::json!(n)
178                    } else {
179                        serde_json::Value::Null
180                    }
181                })
182                .collect(),
183        )),
184
185        Value::Tags(tags) => Some(serde_json::Value::Array(
186            tags.iter()
187                .map(|t| serde_json::Value::String(format!("{:04X}{:04X}", t.group, t.element)))
188                .collect(),
189        )),
190
191        Value::Sequence(items) => {
192            let arr: Vec<serde_json::Value> = items
193                .iter()
194                .map(|item| {
195                    dataset_to_json_object(item)
196                        .map(serde_json::Value::Object)
197                        .unwrap_or(serde_json::Value::Null)
198                })
199                .collect();
200            Some(serde_json::Value::Array(arr))
201        }
202
203        // Bulk binary data: encode as base64 InlineBinary
204        Value::U8(bytes) => {
205            use base64::Engine;
206            let b64 = base64::engine::general_purpose::STANDARD.encode(bytes);
207            let mut obj = serde_json::Map::new();
208            obj.insert("vr".into(), serde_json::Value::String(vr_str.clone()));
209            obj.insert("InlineBinary".into(), serde_json::Value::String(b64));
210            return Ok(serde_json::Value::Object(obj));
211        }
212
213        Value::PixelData(pd) => {
214            let bytes = match pd {
215                PixelData::Native { bytes } => bytes.as_slice(),
216                PixelData::Encapsulated { fragments, .. } => {
217                    // Return first fragment for inline; full support would use BulkDataURI
218                    fragments.first().map(|f| f.as_slice()).unwrap_or(&[])
219                }
220            };
221            use base64::Engine;
222            let b64 = base64::engine::general_purpose::STANDARD.encode(bytes);
223            let mut obj = serde_json::Map::new();
224            obj.insert("vr".into(), serde_json::Value::String(vr_str.clone()));
225            obj.insert("InlineBinary".into(), serde_json::Value::String(b64));
226            return Ok(serde_json::Value::Object(obj));
227        }
228    };
229
230    let mut obj = serde_json::Map::new();
231    obj.insert("vr".into(), serde_json::Value::String(vr_str));
232    if let Some(v) = value_json {
233        obj.insert("Value".into(), v);
234    }
235    Ok(serde_json::Value::Object(obj))
236}
237
238// ── Deserialization ───────────────────────────────────────────────────────────
239
240/// Deserialize a DICOM JSON string into a `DataSet`.
241pub fn from_json(json: &str) -> DcmResult<DataSet> {
242    let map: serde_json::Map<String, serde_json::Value> = serde_json::from_str(json)
243        .map_err(|e| DcmError::Other(format!("JSON parse error: {e}")))?;
244    json_object_to_dataset(&map)
245}
246
247fn json_object_to_dataset(map: &serde_json::Map<String, serde_json::Value>) -> DcmResult<DataSet> {
248    let mut dataset = DataSet::new();
249    for (key, val) in map {
250        let tag = parse_json_tag(key)?;
251        let elem = json_value_to_element(tag, val)?;
252        dataset.insert(elem);
253    }
254    Ok(dataset)
255}
256
257fn parse_json_tag(key: &str) -> DcmResult<Tag> {
258    if key.len() != 8 {
259        return Err(DcmError::Other(format!("invalid JSON tag key: '{key}'")));
260    }
261    let group = u16::from_str_radix(&key[0..4], 16)
262        .map_err(|_| DcmError::Other(format!("invalid tag group: '{}'", &key[0..4])))?;
263    let element = u16::from_str_radix(&key[4..8], 16)
264        .map_err(|_| DcmError::Other(format!("invalid tag element: '{}'", &key[4..8])))?;
265    Ok(Tag::new(group, element))
266}
267
268fn json_value_to_element(tag: Tag, val: &serde_json::Value) -> DcmResult<Element> {
269    let obj = val
270        .as_object()
271        .ok_or_else(|| DcmError::Other(format!("expected JSON object for tag {tag}")))?;
272
273    let vr_str = obj
274        .get("vr")
275        .and_then(|v| v.as_str())
276        .ok_or_else(|| DcmError::Other(format!("missing 'vr' in JSON element for tag {tag}")))?;
277
278    let vr = Vr::from_bytes([vr_str.as_bytes()[0], vr_str.as_bytes()[1]])
279        .ok_or_else(|| DcmError::Other(format!("unknown VR '{vr_str}' in JSON for tag {tag}")))?;
280
281    // Check for InlineBinary first
282    if let Some(b64_val) = obj.get("InlineBinary") {
283        use base64::Engine;
284        let b64_str = b64_val
285            .as_str()
286            .ok_or_else(|| DcmError::Other("InlineBinary must be a string".into()))?;
287        let bytes = base64::engine::general_purpose::STANDARD
288            .decode(b64_str)
289            .map_err(|e| DcmError::Other(format!("base64 decode error: {e}")))?;
290        return Ok(Element::bytes(tag, vr, bytes));
291    }
292
293    let values_arr = match obj.get("Value") {
294        None => return Ok(Element::new(tag, vr, Value::Empty)),
295        Some(v) => v
296            .as_array()
297            .ok_or_else(|| DcmError::Other(format!("'Value' must be array for tag {tag}")))?,
298    };
299
300    let value = match vr {
301        Vr::SQ => {
302            let items: DcmResult<Vec<DataSet>> = values_arr
303                .iter()
304                .map(|item| {
305                    let item_obj = item
306                        .as_object()
307                        .ok_or_else(|| DcmError::Other("SQ item must be a JSON object".into()))?;
308                    json_object_to_dataset(item_obj)
309                })
310                .collect();
311            Value::Sequence(items?)
312        }
313
314        Vr::PN => {
315            let names: DcmResult<Vec<PersonName>> = values_arr
316                .iter()
317                .map(|pn_val| {
318                    if pn_val.is_null() {
319                        return Ok(PersonName::parse(""));
320                    }
321                    let pn_obj = pn_val
322                        .as_object()
323                        .ok_or_else(|| DcmError::Other("PN value must be a JSON object".into()))?;
324                    let alphabetic = pn_obj
325                        .get("Alphabetic")
326                        .and_then(|v| v.as_str())
327                        .unwrap_or("")
328                        .to_string();
329                    let ideographic = pn_obj
330                        .get("Ideographic")
331                        .and_then(|v| v.as_str())
332                        .unwrap_or("")
333                        .to_string();
334                    let phonetic = pn_obj
335                        .get("Phonetic")
336                        .and_then(|v| v.as_str())
337                        .unwrap_or("")
338                        .to_string();
339                    Ok(PersonName {
340                        alphabetic,
341                        ideographic,
342                        phonetic,
343                    })
344                })
345                .collect();
346            Value::PersonNames(names?)
347        }
348
349        Vr::DA => {
350            let dates: DcmResult<Vec<DicomDate>> = values_arr
351                .iter()
352                .filter_map(|v| v.as_str())
353                .map(DicomDate::parse)
354                .collect();
355            Value::Date(dates?)
356        }
357
358        Vr::TM => {
359            let times: DcmResult<Vec<DicomTime>> = values_arr
360                .iter()
361                .filter_map(|v| v.as_str())
362                .map(DicomTime::parse)
363                .collect();
364            Value::Time(times?)
365        }
366
367        Vr::DT => {
368            let dts: DcmResult<Vec<DicomDateTime>> = values_arr
369                .iter()
370                .filter_map(|v| v.as_str())
371                .map(DicomDateTime::parse)
372                .collect();
373            Value::DateTime(dts?)
374        }
375
376        Vr::UI => {
377            let uid = values_arr
378                .first()
379                .and_then(|v| v.as_str())
380                .unwrap_or("")
381                .to_string();
382            Value::Uid(uid)
383        }
384
385        Vr::IS => {
386            let ints: Vec<i64> = values_arr.iter().filter_map(|v| v.as_i64()).collect();
387            Value::Ints(ints)
388        }
389
390        Vr::DS => {
391            let decimals: Vec<f64> = values_arr.iter().filter_map(|v| v.as_f64()).collect();
392            Value::Decimals(decimals)
393        }
394
395        Vr::US | Vr::OW => {
396            let vals: Vec<u16> = values_arr
397                .iter()
398                .filter_map(|v| v.as_u64().map(|n| n as u16))
399                .collect();
400            Value::U16(vals)
401        }
402
403        Vr::SS => {
404            let vals: Vec<i16> = values_arr
405                .iter()
406                .filter_map(|v| v.as_i64().map(|n| n as i16))
407                .collect();
408            Value::I16(vals)
409        }
410
411        Vr::UL | Vr::OL => {
412            let vals: Vec<u32> = values_arr
413                .iter()
414                .filter_map(|v| v.as_u64().map(|n| n as u32))
415                .collect();
416            Value::U32(vals)
417        }
418
419        Vr::SL => {
420            let vals: Vec<i32> = values_arr
421                .iter()
422                .filter_map(|v| v.as_i64().map(|n| n as i32))
423                .collect();
424            Value::I32(vals)
425        }
426
427        Vr::UV | Vr::OV => {
428            let vals: Vec<u64> = values_arr.iter().filter_map(|v| v.as_u64()).collect();
429            Value::U64(vals)
430        }
431
432        Vr::SV => {
433            let vals: Vec<i64> = values_arr.iter().filter_map(|v| v.as_i64()).collect();
434            Value::I64(vals)
435        }
436
437        Vr::FL | Vr::OF => {
438            let vals: Vec<f32> = values_arr
439                .iter()
440                .filter_map(|v| v.as_f64().map(|n| n as f32))
441                .collect();
442            Value::F32(vals)
443        }
444
445        Vr::FD | Vr::OD => {
446            let vals: Vec<f64> = values_arr.iter().filter_map(|v| v.as_f64()).collect();
447            Value::F64(vals)
448        }
449
450        Vr::AT => {
451            let tags: DcmResult<Vec<Tag>> = values_arr
452                .iter()
453                .filter_map(|v| v.as_str())
454                .map(|s| {
455                    if s.len() != 8 {
456                        return Err(DcmError::Other(format!("invalid AT value: '{s}'")));
457                    }
458                    let g = u16::from_str_radix(&s[0..4], 16)
459                        .map_err(|_| DcmError::Other(format!("bad AT group: {s}")))?;
460                    let e = u16::from_str_radix(&s[4..8], 16)
461                        .map_err(|_| DcmError::Other(format!("bad AT element: {s}")))?;
462                    Ok(Tag::new(g, e))
463                })
464                .collect();
465            Value::Tags(tags?)
466        }
467
468        // Default: string VRs
469        _ => {
470            let strings: Vec<String> = values_arr
471                .iter()
472                .filter_map(|v| v.as_str().map(str::to_string))
473                .collect();
474            Value::Strings(strings)
475        }
476    };
477
478    Ok(Element::new(tag, vr, value))
479}
480
481#[cfg(test)]
482mod tests {
483    use super::*;
484    use dicom_toolkit_dict::tags;
485
486    fn make_dataset() -> DataSet {
487        let mut ds = DataSet::new();
488        ds.set_string(tags::PATIENT_NAME, Vr::PN, "Doe^John");
489        ds.set_string(tags::PATIENT_ID, Vr::LO, "PAT-001");
490        ds.set_uid(tags::SOP_INSTANCE_UID, "1.2.3.4.5");
491        ds.set_u16(tags::SAMPLES_PER_PIXEL, 1);
492        ds.set_u16(tags::ROWS, 512);
493        ds.set_u16(tags::COLUMNS, 512);
494        ds
495    }
496
497    #[test]
498    fn serialize_basic_dataset() {
499        let ds = make_dataset();
500        let json = to_json(&ds).unwrap();
501        // Should contain our tags as 8-hex-char keys
502        assert!(json.contains("00100010"), "should contain PatientName tag");
503        assert!(json.contains("00100020"), "should contain PatientID tag");
504        assert!(
505            json.contains("0020000D") || json.contains("00080018"),
506            "should contain UID tag"
507        );
508    }
509
510    #[test]
511    fn roundtrip_string_element() {
512        let mut ds = DataSet::new();
513        ds.set_string(tags::PATIENT_ID, Vr::LO, "PAT-123");
514
515        let json = to_json(&ds).unwrap();
516        let parsed = from_json(&json).unwrap();
517
518        assert_eq!(parsed.get_string(tags::PATIENT_ID), Some("PAT-123"));
519    }
520
521    #[test]
522    fn roundtrip_uid_element() {
523        let mut ds = DataSet::new();
524        ds.set_uid(tags::SOP_INSTANCE_UID, "1.2.840.10008.5.1.4.1.1.2");
525
526        let json = to_json(&ds).unwrap();
527        let parsed = from_json(&json).unwrap();
528
529        assert_eq!(
530            parsed.get_string(tags::SOP_INSTANCE_UID),
531            Some("1.2.840.10008.5.1.4.1.1.2")
532        );
533    }
534
535    #[test]
536    fn roundtrip_u16_element() {
537        let mut ds = DataSet::new();
538        ds.set_u16(tags::ROWS, 256);
539
540        let json = to_json(&ds).unwrap();
541        let parsed = from_json(&json).unwrap();
542
543        assert_eq!(parsed.get_u16(tags::ROWS), Some(256));
544    }
545
546    #[test]
547    fn roundtrip_sequence() {
548        let mut ds = DataSet::new();
549        let mut item = DataSet::new();
550        item.set_string(tags::PATIENT_ID, Vr::LO, "ITEM-1");
551        ds.set_sequence(tags::REFERENCED_SOP_SEQUENCE, vec![item]);
552
553        let json = to_json(&ds).unwrap();
554        let parsed = from_json(&json).unwrap();
555
556        let items = parsed.get_items(tags::REFERENCED_SOP_SEQUENCE).unwrap();
557        assert_eq!(items.len(), 1);
558        assert_eq!(items[0].get_string(tags::PATIENT_ID), Some("ITEM-1"));
559    }
560
561    #[test]
562    fn roundtrip_person_name() {
563        let mut ds = DataSet::new();
564        ds.set_string(tags::PATIENT_NAME, Vr::PN, "Smith^John^^Dr.");
565
566        let json = to_json(&ds).unwrap();
567        assert!(json.contains("Alphabetic"), "PN should use Alphabetic key");
568
569        let parsed = from_json(&json).unwrap();
570        // PN is stored as PersonName; we get it via the string getter which formats it back
571        assert!(parsed.contains(tags::PATIENT_NAME));
572    }
573
574    #[test]
575    fn invalid_json_returns_error() {
576        assert!(from_json("not json").is_err());
577        assert!(from_json("[]").is_err(), "array at root should fail");
578    }
579
580    #[test]
581    fn invalid_tag_key_returns_error() {
582        // tag key too short
583        assert!(from_json(r#"{"00100": {"vr": "LO"}}"#).is_err());
584        // non-hex chars
585        assert!(from_json(r#"{"GGGGEEEE": {"vr": "LO"}}"#).is_err());
586    }
587
588    #[test]
589    fn pretty_print_is_valid_json() {
590        let ds = make_dataset();
591        let pretty = to_json_pretty(&ds).unwrap();
592        // Should be parseable
593        let reparsed: serde_json::Value = serde_json::from_str(&pretty).unwrap();
594        assert!(reparsed.is_object());
595    }
596}