1use 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
12pub enum BinaryValueMode<'a> {
16 InlineBinary,
18 BulkDataUri(&'a dyn Fn(Tag) -> Option<String>),
24}
25
26pub fn to_json(dataset: &DataSet) -> DcmResult<String> {
28 let obj = dataset_to_json_object(dataset)?;
29 serde_json::to_string(&obj).map_err(|e| DcmError::Other(format!("JSON serialize error: {e}")))
30}
31
32pub fn to_json_pretty(dataset: &DataSet) -> DcmResult<String> {
34 let obj = dataset_to_json_object(dataset)?;
35 serde_json::to_string_pretty(&obj)
36 .map_err(|e| DcmError::Other(format!("JSON serialize error: {e}")))
37}
38
39pub fn to_json_with_binary_mode(dataset: &DataSet, mode: BinaryValueMode<'_>) -> DcmResult<String> {
41 let obj = dataset_to_json_object_with_binary_mode(dataset, &mode)?;
42 serde_json::to_string(&obj).map_err(|e| DcmError::Other(format!("JSON serialize error: {e}")))
43}
44
45fn dataset_to_json_object(
46 dataset: &DataSet,
47) -> DcmResult<serde_json::Map<String, serde_json::Value>> {
48 dataset_to_json_object_internal(dataset, None)
49}
50
51fn dataset_to_json_object_with_binary_mode(
52 dataset: &DataSet,
53 mode: &BinaryValueMode<'_>,
54) -> DcmResult<serde_json::Map<String, serde_json::Value>> {
55 dataset_to_json_object_internal(dataset, Some(mode))
56}
57
58fn dataset_to_json_object_internal(
59 dataset: &DataSet,
60 binary_mode: Option<&BinaryValueMode<'_>>,
61) -> DcmResult<serde_json::Map<String, serde_json::Value>> {
62 let mut map = serde_json::Map::new();
63 for (tag, elem) in dataset.iter() {
64 if tag.is_group_length() || tag.is_delimiter() {
66 continue;
67 }
68 let key = format!("{:04X}{:04X}", tag.group, tag.element);
69 let json_elem = element_to_json_internal(elem, binary_mode)?;
70 map.insert(key, json_elem);
71 }
72 Ok(map)
73}
74
75fn element_to_json_internal(
76 elem: &Element,
77 binary_mode: Option<&BinaryValueMode<'_>>,
78) -> DcmResult<serde_json::Value> {
79 let vr_str = elem.vr.code().to_string();
80
81 if let Some(json) = binary_value_to_json(elem, binary_mode, &vr_str)? {
82 return Ok(json);
83 }
84
85 let value_json: Option<serde_json::Value> = match &elem.value {
86 Value::Empty => None,
87
88 Value::Strings(v) => {
89 if v.is_empty() {
90 None
91 } else if elem.vr == Vr::PN {
92 let arr: Vec<serde_json::Value> = v
94 .iter()
95 .map(|s| {
96 let mut obj = serde_json::Map::new();
97 if !s.is_empty() {
98 obj.insert("Alphabetic".into(), serde_json::Value::String(s.clone()));
99 }
100 serde_json::Value::Object(obj)
101 })
102 .collect();
103 Some(serde_json::Value::Array(arr))
104 } else {
105 Some(serde_json::Value::Array(
106 v.iter()
107 .map(|s| serde_json::Value::String(s.clone()))
108 .collect(),
109 ))
110 }
111 }
112
113 Value::Uid(s) => Some(serde_json::Value::Array(vec![serde_json::Value::String(
114 s.clone(),
115 )])),
116
117 Value::PersonNames(names) => {
118 let arr: Vec<serde_json::Value> = names
119 .iter()
120 .map(|pn| {
121 let mut obj = serde_json::Map::new();
122 if !pn.alphabetic.is_empty() {
123 obj.insert(
124 "Alphabetic".into(),
125 serde_json::Value::String(pn.alphabetic.clone()),
126 );
127 }
128 if !pn.ideographic.is_empty() {
129 obj.insert(
130 "Ideographic".into(),
131 serde_json::Value::String(pn.ideographic.clone()),
132 );
133 }
134 if !pn.phonetic.is_empty() {
135 obj.insert(
136 "Phonetic".into(),
137 serde_json::Value::String(pn.phonetic.clone()),
138 );
139 }
140 serde_json::Value::Object(obj)
141 })
142 .collect();
143 if arr.is_empty() {
144 None
145 } else {
146 Some(serde_json::Value::Array(arr))
147 }
148 }
149
150 Value::Date(dates) => Some(serde_json::Value::Array(
151 dates
152 .iter()
153 .map(|d| serde_json::Value::String(d.to_string()))
154 .collect(),
155 )),
156 Value::Time(times) => Some(serde_json::Value::Array(
157 times
158 .iter()
159 .map(|t| serde_json::Value::String(t.to_string()))
160 .collect(),
161 )),
162 Value::DateTime(dts) => Some(serde_json::Value::Array(
163 dts.iter()
164 .map(|dt| serde_json::Value::String(dt.to_string()))
165 .collect(),
166 )),
167
168 Value::Ints(v) => Some(serde_json::Value::Array(
169 v.iter().map(|n| serde_json::json!(n)).collect(),
170 )),
171 Value::Decimals(v) => Some(serde_json::Value::Array(
172 v.iter()
173 .map(|n| {
174 if n.is_finite() {
175 serde_json::json!(n)
176 } else {
177 serde_json::Value::Null
178 }
179 })
180 .collect(),
181 )),
182
183 Value::U16(v) => Some(serde_json::Value::Array(
184 v.iter().map(|n| serde_json::json!(n)).collect(),
185 )),
186 Value::I16(v) => Some(serde_json::Value::Array(
187 v.iter().map(|n| serde_json::json!(n)).collect(),
188 )),
189 Value::U32(v) => Some(serde_json::Value::Array(
190 v.iter().map(|n| serde_json::json!(n)).collect(),
191 )),
192 Value::I32(v) => Some(serde_json::Value::Array(
193 v.iter().map(|n| serde_json::json!(n)).collect(),
194 )),
195 Value::U64(v) => Some(serde_json::Value::Array(
196 v.iter().map(|n| serde_json::json!(n)).collect(),
197 )),
198 Value::I64(v) => Some(serde_json::Value::Array(
199 v.iter().map(|n| serde_json::json!(n)).collect(),
200 )),
201 Value::F32(v) => Some(serde_json::Value::Array(
202 v.iter()
203 .map(|n| {
204 if n.is_finite() {
205 serde_json::json!(n)
206 } else {
207 serde_json::Value::Null
208 }
209 })
210 .collect(),
211 )),
212 Value::F64(v) => Some(serde_json::Value::Array(
213 v.iter()
214 .map(|n| {
215 if n.is_finite() {
216 serde_json::json!(n)
217 } else {
218 serde_json::Value::Null
219 }
220 })
221 .collect(),
222 )),
223
224 Value::Tags(tags) => Some(serde_json::Value::Array(
225 tags.iter()
226 .map(|t| serde_json::Value::String(format!("{:04X}{:04X}", t.group, t.element)))
227 .collect(),
228 )),
229
230 Value::Sequence(items) => {
231 let arr: Vec<serde_json::Value> = items
232 .iter()
233 .map(|item| {
234 dataset_to_json_object_internal(item, binary_mode)
235 .map(serde_json::Value::Object)
236 .unwrap_or(serde_json::Value::Null)
237 })
238 .collect();
239 Some(serde_json::Value::Array(arr))
240 }
241 Value::U8(bytes) => {
242 use base64::Engine;
243 let b64 = base64::engine::general_purpose::STANDARD.encode(bytes);
244 let mut obj = serde_json::Map::new();
245 obj.insert("vr".into(), serde_json::Value::String(vr_str.clone()));
246 obj.insert("InlineBinary".into(), serde_json::Value::String(b64));
247 return Ok(serde_json::Value::Object(obj));
248 }
249 Value::PixelData(pd) => {
250 let bytes = match pd {
251 PixelData::Native { bytes } => bytes.as_slice(),
252 PixelData::Encapsulated { fragments, .. } => {
253 fragments.first().map(|f| f.as_slice()).unwrap_or(&[])
254 }
255 };
256 use base64::Engine;
257 let b64 = base64::engine::general_purpose::STANDARD.encode(bytes);
258 let mut obj = serde_json::Map::new();
259 obj.insert("vr".into(), serde_json::Value::String(vr_str.clone()));
260 obj.insert("InlineBinary".into(), serde_json::Value::String(b64));
261 return Ok(serde_json::Value::Object(obj));
262 }
263 };
264
265 let mut obj = serde_json::Map::new();
266 obj.insert("vr".into(), serde_json::Value::String(vr_str));
267 if let Some(v) = value_json {
268 obj.insert("Value".into(), v);
269 }
270 Ok(serde_json::Value::Object(obj))
271}
272
273fn binary_value_to_json(
274 elem: &Element,
275 binary_mode: Option<&BinaryValueMode<'_>>,
276 vr_str: &str,
277) -> DcmResult<Option<serde_json::Value>> {
278 let Some(binary_mode) = binary_mode else {
279 return Ok(None);
280 };
281
282 if !is_bulk_data_eligible(elem) {
283 return Ok(None);
284 }
285
286 match binary_mode {
287 BinaryValueMode::BulkDataUri(resolve_uri) => {
288 if let Some(uri) = resolve_uri(elem.tag) {
289 return Ok(Some(json_bulk_data_uri(vr_str, uri)));
290 }
291
292 if matches!(elem.value, Value::PixelData(PixelData::Encapsulated { .. })) {
293 return Err(DcmError::Other(format!(
294 "encapsulated Pixel Data tag {} requires BulkDataURI in to_json_with_binary_mode",
295 elem.tag
296 )));
297 }
298
299 Ok(None)
300 }
301 BinaryValueMode::InlineBinary => {
302 if let Value::PixelData(PixelData::Encapsulated { .. }) = &elem.value {
303 return Err(DcmError::Other(format!(
304 "encapsulated Pixel Data tag {} requires BulkDataURI in to_json_with_binary_mode",
305 elem.tag
306 )));
307 }
308 Ok(None)
309 }
310 }
311}
312
313fn is_bulk_data_eligible(elem: &Element) -> bool {
314 matches!(elem.value, Value::PixelData(_))
315 || matches!(
316 elem.vr,
317 Vr::OB | Vr::OD | Vr::OF | Vr::OL | Vr::OV | Vr::OW | Vr::UN
318 )
319}
320
321fn json_bulk_data_uri(vr_str: &str, uri: String) -> serde_json::Value {
322 let mut obj = serde_json::Map::new();
323 obj.insert("vr".into(), serde_json::Value::String(vr_str.to_string()));
324 obj.insert("BulkDataURI".into(), serde_json::Value::String(uri));
325 serde_json::Value::Object(obj)
326}
327
328pub fn from_json(json: &str) -> DcmResult<DataSet> {
332 let map: serde_json::Map<String, serde_json::Value> = serde_json::from_str(json)
333 .map_err(|e| DcmError::Other(format!("JSON parse error: {e}")))?;
334 json_object_to_dataset(&map)
335}
336
337fn json_object_to_dataset(map: &serde_json::Map<String, serde_json::Value>) -> DcmResult<DataSet> {
338 let mut dataset = DataSet::new();
339 for (key, val) in map {
340 let tag = parse_json_tag(key)?;
341 let elem = json_value_to_element(tag, val)?;
342 dataset.insert(elem);
343 }
344 Ok(dataset)
345}
346
347fn parse_json_tag(key: &str) -> DcmResult<Tag> {
348 if key.len() != 8 {
349 return Err(DcmError::Other(format!("invalid JSON tag key: '{key}'")));
350 }
351 let group = u16::from_str_radix(&key[0..4], 16)
352 .map_err(|_| DcmError::Other(format!("invalid tag group: '{}'", &key[0..4])))?;
353 let element = u16::from_str_radix(&key[4..8], 16)
354 .map_err(|_| DcmError::Other(format!("invalid tag element: '{}'", &key[4..8])))?;
355 Ok(Tag::new(group, element))
356}
357
358fn json_scalar_token_string(tag: Tag, vr: Vr, value: &serde_json::Value) -> DcmResult<String> {
359 match value {
360 serde_json::Value::String(s) => Ok(s.clone()),
361 serde_json::Value::Number(n) => Ok(n.to_string()),
362 serde_json::Value::Null => Ok(String::new()),
363 _ => Err(DcmError::Other(format!(
364 "invalid JSON value for tag {tag} VR {}: expected number, string or null",
365 vr.code()
366 ))),
367 }
368}
369
370fn json_value_tokens(tag: Tag, vr: Vr, values_arr: &[serde_json::Value]) -> DcmResult<Vec<String>> {
371 values_arr
372 .iter()
373 .map(|value| json_scalar_token_string(tag, vr, value))
374 .collect()
375}
376
377fn tokens_are_all_empty(tokens: &[String]) -> bool {
378 tokens.iter().all(|token| token.is_empty())
379}
380
381fn reject_mixed_empty_tokens(tag: Tag, vr: Vr, tokens: &[String]) -> DcmResult<()> {
382 let has_empty = tokens.iter().any(|token| token.is_empty());
383 let has_non_empty = tokens.iter().any(|token| !token.is_empty());
384
385 if has_empty && has_non_empty {
386 return Err(DcmError::Other(format!(
387 "tag {tag} VR {} cannot represent mixed empty and non-empty JSON values",
388 vr.code()
389 )));
390 }
391
392 Ok(())
393}
394
395fn parse_numeric_tokens<T>(
396 tag: Tag,
397 vr: Vr,
398 values_arr: &[serde_json::Value],
399) -> DcmResult<Option<Vec<T>>>
400where
401 T: std::str::FromStr,
402 T::Err: std::fmt::Display,
403{
404 let tokens = json_value_tokens(tag, vr, values_arr)?;
405 if tokens_are_all_empty(&tokens) {
406 return Ok(None);
407 }
408
409 reject_mixed_empty_tokens(tag, vr, &tokens)?;
410
411 let values = tokens
412 .iter()
413 .map(|token| {
414 token.parse::<T>().map_err(|err| {
415 DcmError::Other(format!(
416 "invalid {} value '{}' for tag {tag}: {err}",
417 vr.code(),
418 token
419 ))
420 })
421 })
422 .collect::<DcmResult<Vec<T>>>()?;
423
424 Ok(Some(values))
425}
426
427fn json_value_to_element(tag: Tag, val: &serde_json::Value) -> DcmResult<Element> {
428 let obj = val
429 .as_object()
430 .ok_or_else(|| DcmError::Other(format!("expected JSON object for tag {tag}")))?;
431
432 let vr_str = obj
433 .get("vr")
434 .and_then(|v| v.as_str())
435 .ok_or_else(|| DcmError::Other(format!("missing 'vr' in JSON element for tag {tag}")))?;
436
437 let vr = Vr::from_bytes([vr_str.as_bytes()[0], vr_str.as_bytes()[1]])
438 .ok_or_else(|| DcmError::Other(format!("unknown VR '{vr_str}' in JSON for tag {tag}")))?;
439
440 if let Some(b64_val) = obj.get("InlineBinary") {
442 use base64::Engine;
443 let b64_str = b64_val
444 .as_str()
445 .ok_or_else(|| DcmError::Other("InlineBinary must be a string".into()))?;
446 let bytes = base64::engine::general_purpose::STANDARD
447 .decode(b64_str)
448 .map_err(|e| DcmError::Other(format!("base64 decode error: {e}")))?;
449 return Ok(Element::bytes(tag, vr, bytes));
450 }
451
452 if let Some(uri_val) = obj.get("BulkDataURI") {
453 let _uri = uri_val
454 .as_str()
455 .ok_or_else(|| DcmError::Other("BulkDataURI must be a string".into()))?;
456 return Err(DcmError::Other(format!(
457 "BulkDataURI deserialization is not supported for tag {tag}"
458 )));
459 }
460
461 let values_arr = match obj.get("Value") {
462 None => return Ok(Element::new(tag, vr, Value::Empty)),
463 Some(v) => v
464 .as_array()
465 .ok_or_else(|| DcmError::Other(format!("'Value' must be array for tag {tag}")))?,
466 };
467
468 let value = match vr {
469 Vr::SQ => {
470 let items: DcmResult<Vec<DataSet>> = values_arr
471 .iter()
472 .map(|item| {
473 let item_obj = item
474 .as_object()
475 .ok_or_else(|| DcmError::Other("SQ item must be a JSON object".into()))?;
476 json_object_to_dataset(item_obj)
477 })
478 .collect();
479 Value::Sequence(items?)
480 }
481
482 Vr::PN => {
483 let names: DcmResult<Vec<PersonName>> = values_arr
484 .iter()
485 .map(|pn_val| {
486 if pn_val.is_null() {
487 return Ok(PersonName::parse(""));
488 }
489 let pn_obj = pn_val
490 .as_object()
491 .ok_or_else(|| DcmError::Other("PN value must be a JSON object".into()))?;
492 let alphabetic = pn_obj
493 .get("Alphabetic")
494 .and_then(|v| v.as_str())
495 .unwrap_or("")
496 .to_string();
497 let ideographic = pn_obj
498 .get("Ideographic")
499 .and_then(|v| v.as_str())
500 .unwrap_or("")
501 .to_string();
502 let phonetic = pn_obj
503 .get("Phonetic")
504 .and_then(|v| v.as_str())
505 .unwrap_or("")
506 .to_string();
507 Ok(PersonName {
508 alphabetic,
509 ideographic,
510 phonetic,
511 })
512 })
513 .collect();
514 Value::PersonNames(names?)
515 }
516
517 Vr::DA => {
518 let tokens = json_value_tokens(tag, vr, values_arr)?;
519 if tokens_are_all_empty(&tokens) {
520 Value::Empty
521 } else {
522 reject_mixed_empty_tokens(tag, vr, &tokens)?;
523 let dates: DcmResult<Vec<DicomDate>> =
524 tokens.iter().map(|token| DicomDate::parse(token)).collect();
525 Value::Date(dates?)
526 }
527 }
528
529 Vr::TM => {
530 let tokens = json_value_tokens(tag, vr, values_arr)?;
531 if tokens_are_all_empty(&tokens) {
532 Value::Empty
533 } else {
534 reject_mixed_empty_tokens(tag, vr, &tokens)?;
535 let times: DcmResult<Vec<DicomTime>> =
536 tokens.iter().map(|token| DicomTime::parse(token)).collect();
537 Value::Time(times?)
538 }
539 }
540
541 Vr::DT => {
542 let tokens = json_value_tokens(tag, vr, values_arr)?;
543 if tokens_are_all_empty(&tokens) {
544 Value::Empty
545 } else {
546 reject_mixed_empty_tokens(tag, vr, &tokens)?;
547 let dts: DcmResult<Vec<DicomDateTime>> = tokens
548 .iter()
549 .map(|token| DicomDateTime::parse(token))
550 .collect();
551 Value::DateTime(dts?)
552 }
553 }
554
555 Vr::UI => {
556 let tokens = json_value_tokens(tag, vr, values_arr)?;
557 if tokens_are_all_empty(&tokens) {
558 Value::Empty
559 } else {
560 reject_mixed_empty_tokens(tag, vr, &tokens)?;
561 let uid = tokens.first().cloned().unwrap_or_default();
562 Value::Uid(uid)
563 }
564 }
565
566 Vr::IS => match parse_numeric_tokens::<i64>(tag, vr, values_arr)? {
567 Some(ints) => Value::Ints(ints),
568 None => Value::Empty,
569 },
570
571 Vr::DS => match parse_numeric_tokens::<f64>(tag, vr, values_arr)? {
572 Some(decimals) => Value::Decimals(decimals),
573 None => Value::Empty,
574 },
575
576 Vr::US | Vr::OW => match parse_numeric_tokens::<u16>(tag, vr, values_arr)? {
577 Some(vals) => Value::U16(vals),
578 None => Value::Empty,
579 },
580
581 Vr::SS => match parse_numeric_tokens::<i16>(tag, vr, values_arr)? {
582 Some(vals) => Value::I16(vals),
583 None => Value::Empty,
584 },
585
586 Vr::UL | Vr::OL => match parse_numeric_tokens::<u32>(tag, vr, values_arr)? {
587 Some(vals) => Value::U32(vals),
588 None => Value::Empty,
589 },
590
591 Vr::SL => match parse_numeric_tokens::<i32>(tag, vr, values_arr)? {
592 Some(vals) => Value::I32(vals),
593 None => Value::Empty,
594 },
595
596 Vr::UV | Vr::OV => match parse_numeric_tokens::<u64>(tag, vr, values_arr)? {
597 Some(vals) => Value::U64(vals),
598 None => Value::Empty,
599 },
600
601 Vr::SV => match parse_numeric_tokens::<i64>(tag, vr, values_arr)? {
602 Some(vals) => Value::I64(vals),
603 None => Value::Empty,
604 },
605
606 Vr::FL | Vr::OF => match parse_numeric_tokens::<f32>(tag, vr, values_arr)? {
607 Some(vals) => Value::F32(vals),
608 None => Value::Empty,
609 },
610
611 Vr::FD | Vr::OD => match parse_numeric_tokens::<f64>(tag, vr, values_arr)? {
612 Some(vals) => Value::F64(vals),
613 None => Value::Empty,
614 },
615
616 Vr::AT => {
617 let tags: DcmResult<Vec<Tag>> = values_arr
618 .iter()
619 .filter_map(|v| v.as_str())
620 .map(|s| {
621 if s.len() != 8 {
622 return Err(DcmError::Other(format!("invalid AT value: '{s}'")));
623 }
624 let g = u16::from_str_radix(&s[0..4], 16)
625 .map_err(|_| DcmError::Other(format!("bad AT group: {s}")))?;
626 let e = u16::from_str_radix(&s[4..8], 16)
627 .map_err(|_| DcmError::Other(format!("bad AT element: {s}")))?;
628 Ok(Tag::new(g, e))
629 })
630 .collect();
631 Value::Tags(tags?)
632 }
633
634 _ => {
636 let strings = json_value_tokens(tag, vr, values_arr)?;
637 Value::Strings(strings)
638 }
639 };
640
641 Ok(Element::new(tag, vr, value))
642}
643
644#[cfg(test)]
645mod tests {
646 use super::*;
647 use dicom_toolkit_dict::tags;
648
649 fn make_dataset() -> DataSet {
650 let mut ds = DataSet::new();
651 ds.set_string(tags::PATIENT_NAME, Vr::PN, "Doe^John");
652 ds.set_string(tags::PATIENT_ID, Vr::LO, "PAT-001");
653 ds.set_uid(tags::SOP_INSTANCE_UID, "1.2.3.4.5");
654 ds.set_u16(tags::SAMPLES_PER_PIXEL, 1);
655 ds.set_u16(tags::ROWS, 512);
656 ds.set_u16(tags::COLUMNS, 512);
657 ds
658 }
659
660 #[test]
661 fn serialize_basic_dataset() {
662 let ds = make_dataset();
663 let json = to_json(&ds).unwrap();
664 assert!(json.contains("00100010"), "should contain PatientName tag");
666 assert!(json.contains("00100020"), "should contain PatientID tag");
667 assert!(
668 json.contains("0020000D") || json.contains("00080018"),
669 "should contain UID tag"
670 );
671 }
672
673 #[test]
674 fn roundtrip_string_element() {
675 let mut ds = DataSet::new();
676 ds.set_string(tags::PATIENT_ID, Vr::LO, "PAT-123");
677
678 let json = to_json(&ds).unwrap();
679 let parsed = from_json(&json).unwrap();
680
681 assert_eq!(parsed.get_string(tags::PATIENT_ID), Some("PAT-123"));
682 }
683
684 #[test]
685 fn roundtrip_uid_element() {
686 let mut ds = DataSet::new();
687 ds.set_uid(tags::SOP_INSTANCE_UID, "1.2.840.10008.5.1.4.1.1.2");
688
689 let json = to_json(&ds).unwrap();
690 let parsed = from_json(&json).unwrap();
691
692 assert_eq!(
693 parsed.get_string(tags::SOP_INSTANCE_UID),
694 Some("1.2.840.10008.5.1.4.1.1.2")
695 );
696 }
697
698 #[test]
699 fn roundtrip_u16_element() {
700 let mut ds = DataSet::new();
701 ds.set_u16(tags::ROWS, 256);
702
703 let json = to_json(&ds).unwrap();
704 let parsed = from_json(&json).unwrap();
705
706 assert_eq!(parsed.get_u16(tags::ROWS), Some(256));
707 }
708
709 #[test]
710 fn roundtrip_sequence() {
711 let mut ds = DataSet::new();
712 let mut item = DataSet::new();
713 item.set_string(tags::PATIENT_ID, Vr::LO, "ITEM-1");
714 ds.set_sequence(tags::REFERENCED_SOP_SEQUENCE, vec![item]);
715
716 let json = to_json(&ds).unwrap();
717 let parsed = from_json(&json).unwrap();
718
719 let items = parsed.get_items(tags::REFERENCED_SOP_SEQUENCE).unwrap();
720 assert_eq!(items.len(), 1);
721 assert_eq!(items[0].get_string(tags::PATIENT_ID), Some("ITEM-1"));
722 }
723
724 #[test]
725 fn roundtrip_person_name() {
726 let mut ds = DataSet::new();
727 ds.set_string(tags::PATIENT_NAME, Vr::PN, "Smith^John^^Dr.");
728
729 let json = to_json(&ds).unwrap();
730 assert!(json.contains("Alphabetic"), "PN should use Alphabetic key");
731
732 let parsed = from_json(&json).unwrap();
733 assert!(parsed.contains(tags::PATIENT_NAME));
735 }
736
737 #[test]
738 fn invalid_json_returns_error() {
739 assert!(from_json("not json").is_err());
740 assert!(from_json("[]").is_err(), "array at root should fail");
741 }
742
743 #[test]
744 fn invalid_tag_key_returns_error() {
745 assert!(from_json(r#"{"00100": {"vr": "LO"}}"#).is_err());
747 assert!(from_json(r#"{"GGGGEEEE": {"vr": "LO"}}"#).is_err());
749 }
750
751 #[test]
752 fn pretty_print_is_valid_json() {
753 let ds = make_dataset();
754 let pretty = to_json_pretty(&ds).unwrap();
755 let reparsed: serde_json::Value = serde_json::from_str(&pretty).unwrap();
757 assert!(reparsed.is_object());
758 }
759
760 #[test]
761 fn bulk_data_uri_mode_uses_uri_for_binary_vrs() {
762 let binary_tag = Tag::new(0x5400, 0x1010);
763 let mut ds = DataSet::new();
764 ds.insert(Element::new(
765 binary_tag,
766 Vr::OB,
767 Value::U8(vec![1, 2, 3, 4]),
768 ));
769
770 let json = to_json_with_binary_mode(
771 &ds,
772 BinaryValueMode::BulkDataUri(&|tag| {
773 (tag == binary_tag).then_some("https://example.test/bulk/54001010".to_string())
774 }),
775 )
776 .unwrap();
777
778 assert!(json.contains("\"BulkDataURI\":\"https://example.test/bulk/54001010\""));
779 assert!(!json.contains("InlineBinary"));
780 }
781
782 #[test]
783 fn bulk_data_uri_mode_uses_uri_for_pixel_data() {
784 let mut ds = DataSet::new();
785 ds.insert(Element::new(
786 tags::PIXEL_DATA,
787 Vr::OB,
788 Value::PixelData(PixelData::Encapsulated {
789 offset_table: vec![0],
790 fragments: vec![vec![1, 2], vec![3, 4]],
791 }),
792 ));
793
794 let json = to_json_with_binary_mode(
795 &ds,
796 BinaryValueMode::BulkDataUri(&|tag| {
797 (tag == tags::PIXEL_DATA).then_some("https://example.test/bulk/pixel".to_string())
798 }),
799 )
800 .unwrap();
801
802 assert!(json.contains("\"BulkDataURI\":\"https://example.test/bulk/pixel\""));
803 assert!(!json.contains("InlineBinary"));
804 }
805
806 #[test]
807 fn bulk_data_uri_mode_rejects_encapsulated_pixel_data_without_uri() {
808 let mut ds = DataSet::new();
809 ds.insert(Element::new(
810 tags::PIXEL_DATA,
811 Vr::OB,
812 Value::PixelData(PixelData::Encapsulated {
813 offset_table: vec![0],
814 fragments: vec![vec![1, 2], vec![3, 4]],
815 }),
816 ));
817
818 let err =
819 to_json_with_binary_mode(&ds, BinaryValueMode::BulkDataUri(&|_| None)).unwrap_err();
820 assert!(err.to_string().contains("requires BulkDataURI"));
821 }
822
823 #[test]
824 fn from_json_accepts_dcmtk_style_scalar_tokens() {
825 let json = r#"{
826 "00100020": {"vr": "LO", "Value": [123]},
827 "00200013": {"vr": "IS", "Value": ["42"]},
828 "00180050": {"vr": "DS", "Value": ["2.5"]},
829 "00280010": {"vr": "US", "Value": ["256"]}
830 }"#;
831
832 let parsed = from_json(json).unwrap();
833
834 assert_eq!(parsed.get_string(tags::PATIENT_ID), Some("123"));
835 assert_eq!(
836 parsed.get(Tag::new(0x0020, 0x0013)).unwrap().value,
837 Value::Ints(vec![42])
838 );
839 match &parsed.get(Tag::new(0x0018, 0x0050)).unwrap().value {
840 Value::Decimals(values) => assert!((values[0] - 2.5).abs() < 1e-9),
841 other => panic!("unexpected value: {:?}", other),
842 }
843 assert_eq!(parsed.get_u16(tags::ROWS), Some(256));
844 }
845
846 #[test]
847 fn from_json_rejects_non_scalar_value_entries() {
848 let err = from_json(r#"{"00100020":{"vr":"LO","Value":[{}]}}"#).unwrap_err();
849 assert!(err.to_string().contains("expected number, string or null"));
850 }
851
852 #[test]
853 fn from_json_rejects_bulk_data_uri_without_loader() {
854 let err =
855 from_json(r#"{"7FE00010":{"vr":"OB","BulkDataURI":"https://example.test/pixel"}}"#)
856 .unwrap_err();
857 assert!(err.to_string().contains("BulkDataURI"));
858 }
859}