Skip to main content

dpp/document/v0/
json_conversion.rs

1use crate::document::fields::property_names;
2use crate::document::serialization_traits::{
3    DocumentJsonMethodsV0, DocumentPlatformValueMethodsV0,
4};
5use crate::document::DocumentV0;
6use crate::util::json_value::JsonValueExt;
7use crate::ProtocolError;
8use platform_value::{Identifier, Value};
9use platform_version::version::PlatformVersion;
10use serde::Deserialize;
11use serde_json::{json, Value as JsonValue};
12use std::convert::TryInto;
13
14impl DocumentJsonMethodsV0<'_> for DocumentV0 {
15    fn to_json_with_identifiers_using_bytes(
16        &self,
17        _platform_version: &PlatformVersion,
18    ) -> Result<JsonValue, ProtocolError> {
19        let mut value = json!({
20            property_names::ID: self.id,
21            property_names::OWNER_ID: self.owner_id,
22        });
23        let value_mut = value.as_object_mut().unwrap();
24        if let Some(created_at) = self.created_at {
25            value_mut.insert(
26                property_names::CREATED_AT.to_string(),
27                JsonValue::Number(created_at.into()),
28            );
29        }
30        if let Some(updated_at) = self.updated_at {
31            value_mut.insert(
32                property_names::UPDATED_AT.to_string(),
33                JsonValue::Number(updated_at.into()),
34            );
35        }
36        if let Some(created_at_block_height) = self.created_at_block_height {
37            value_mut.insert(
38                property_names::CREATED_AT_BLOCK_HEIGHT.to_string(),
39                JsonValue::Number(created_at_block_height.into()),
40            );
41        }
42
43        if let Some(updated_at_block_height) = self.updated_at_block_height {
44            value_mut.insert(
45                property_names::UPDATED_AT_BLOCK_HEIGHT.to_string(),
46                JsonValue::Number(updated_at_block_height.into()),
47            );
48        }
49
50        if let Some(created_at_core_block_height) = self.created_at_core_block_height {
51            value_mut.insert(
52                property_names::CREATED_AT_CORE_BLOCK_HEIGHT.to_string(),
53                JsonValue::Number(created_at_core_block_height.into()),
54            );
55        }
56
57        if let Some(updated_at_core_block_height) = self.updated_at_core_block_height {
58            value_mut.insert(
59                property_names::UPDATED_AT_CORE_BLOCK_HEIGHT.to_string(),
60                JsonValue::Number(updated_at_core_block_height.into()),
61            );
62        }
63        if let Some(transferred_at) = self.transferred_at {
64            value_mut.insert(
65                property_names::TRANSFERRED_AT.to_string(),
66                JsonValue::Number(transferred_at.into()),
67            );
68        }
69        if let Some(transferred_at_block_height) = self.transferred_at_block_height {
70            value_mut.insert(
71                property_names::TRANSFERRED_AT_BLOCK_HEIGHT.to_string(),
72                JsonValue::Number(transferred_at_block_height.into()),
73            );
74        }
75        if let Some(transferred_at_core_block_height) = self.transferred_at_core_block_height {
76            value_mut.insert(
77                property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT.to_string(),
78                JsonValue::Number(transferred_at_core_block_height.into()),
79            );
80        }
81        if let Some(creator_id) = self.creator_id {
82            value_mut.insert(property_names::CREATOR_ID.to_string(), json!(creator_id));
83        }
84        if let Some(revision) = self.revision {
85            value_mut.insert(
86                property_names::REVISION.to_string(),
87                JsonValue::Number(revision.into()),
88            );
89        }
90
91        self.properties
92            .iter()
93            .try_for_each(|(key, property_value)| {
94                let serde_value: JsonValue = property_value.try_to_validating_json()?;
95                value_mut.insert(key.to_string(), serde_value);
96                Ok::<(), ProtocolError>(())
97            })?;
98
99        Ok(value)
100    }
101
102    fn to_json(&self, _platform_version: &PlatformVersion) -> Result<JsonValue, ProtocolError> {
103        self.to_object()
104            .map(|v| v.try_into().map_err(ProtocolError::ValueError))?
105    }
106
107    fn from_json_value<S, E>(
108        mut document_value: JsonValue,
109        _platform_version: &PlatformVersion,
110    ) -> Result<Self, ProtocolError>
111    where
112        for<'de> S: Deserialize<'de> + TryInto<Identifier, Error = E>,
113        E: Into<ProtocolError>,
114    {
115        let mut document = Self {
116            ..Default::default()
117        };
118
119        if let Ok(value) = document_value.remove(property_names::ID) {
120            if !value.is_null() {
121                let data: S = serde_json::from_value(value)?;
122                document.id = data.try_into().map_err(Into::into)?;
123            }
124        }
125        if let Ok(value) = document_value.remove(property_names::OWNER_ID) {
126            if !value.is_null() {
127                let data: S = serde_json::from_value(value)?;
128                document.owner_id = data.try_into().map_err(Into::into)?;
129            }
130        }
131        if let Ok(value) = document_value.remove(property_names::REVISION) {
132            document.revision = serde_json::from_value(value)?
133        }
134        if let Ok(value) = document_value.remove(property_names::CREATED_AT) {
135            document.created_at = serde_json::from_value(value)?
136        }
137        if let Ok(value) = document_value.remove(property_names::UPDATED_AT) {
138            document.updated_at = serde_json::from_value(value)?
139        }
140        if let Ok(value) = document_value.remove(property_names::CREATED_AT_BLOCK_HEIGHT) {
141            document.created_at_block_height = serde_json::from_value(value)?;
142        }
143        if let Ok(value) = document_value.remove(property_names::UPDATED_AT_BLOCK_HEIGHT) {
144            document.updated_at_block_height = serde_json::from_value(value)?;
145        }
146        if let Ok(value) = document_value.remove(property_names::CREATED_AT_CORE_BLOCK_HEIGHT) {
147            document.created_at_core_block_height = serde_json::from_value(value)?;
148        }
149        if let Ok(value) = document_value.remove(property_names::UPDATED_AT_CORE_BLOCK_HEIGHT) {
150            document.updated_at_core_block_height = serde_json::from_value(value)?;
151        }
152        if let Ok(value) = document_value.remove(property_names::TRANSFERRED_AT) {
153            document.transferred_at = serde_json::from_value(value)?;
154        }
155        if let Ok(value) = document_value.remove(property_names::TRANSFERRED_AT_BLOCK_HEIGHT) {
156            document.transferred_at_block_height = serde_json::from_value(value)?;
157        }
158        if let Ok(value) = document_value.remove(property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT) {
159            document.transferred_at_core_block_height = serde_json::from_value(value)?;
160        }
161        if let Ok(value) = document_value.remove(property_names::CREATOR_ID) {
162            if !value.is_null() {
163                let data: S = serde_json::from_value(value)?;
164                document.creator_id = Some(data.try_into().map_err(Into::into)?);
165            }
166        }
167
168        let platform_value: Value = document_value.into();
169
170        document.properties = platform_value
171            .into_btree_string_map()
172            .map_err(ProtocolError::ValueError)?;
173        Ok(document)
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180    use crate::data_contract::accessors::v0::DataContractV0Getters;
181    use crate::data_contract::document_type::random_document::CreateRandomDocument;
182    use crate::document::serialization_traits::DocumentJsonMethodsV0;
183    use crate::tests::json_document::json_document_to_contract;
184    use platform_version::version::PlatformVersion;
185    use std::collections::BTreeMap;
186
187    fn make_document_v0_with_all_timestamps() -> DocumentV0 {
188        let mut properties = BTreeMap::new();
189        properties.insert("label".to_string(), Value::Text("test-label".to_string()));
190        DocumentV0 {
191            id: Identifier::new([1u8; 32]),
192            owner_id: Identifier::new([2u8; 32]),
193            properties,
194            revision: Some(3),
195            created_at: Some(1_700_000_000_000),
196            updated_at: Some(1_700_000_100_000),
197            transferred_at: Some(1_700_000_200_000),
198            created_at_block_height: Some(100),
199            updated_at_block_height: Some(200),
200            transferred_at_block_height: Some(300),
201            created_at_core_block_height: Some(50),
202            updated_at_core_block_height: Some(60),
203            transferred_at_core_block_height: Some(70),
204            creator_id: Some(Identifier::new([9u8; 32])),
205        }
206    }
207
208    fn make_minimal_document_v0() -> DocumentV0 {
209        DocumentV0 {
210            id: Identifier::new([0xAA; 32]),
211            owner_id: Identifier::new([0xBB; 32]),
212            properties: BTreeMap::new(),
213            revision: None,
214            created_at: None,
215            updated_at: None,
216            transferred_at: None,
217            created_at_block_height: None,
218            updated_at_block_height: None,
219            transferred_at_block_height: None,
220            created_at_core_block_height: None,
221            updated_at_core_block_height: None,
222            transferred_at_core_block_height: None,
223            creator_id: None,
224        }
225    }
226
227    // ================================================================
228    //  to_json produces a JsonValue containing all set fields
229    // ================================================================
230
231    #[test]
232    fn to_json_includes_id_and_owner_id() {
233        let platform_version = PlatformVersion::latest();
234        let doc = make_minimal_document_v0();
235        let json = doc
236            .to_json(platform_version)
237            .expect("to_json should succeed");
238        let obj = json.as_object().expect("should be an object");
239        assert!(
240            obj.contains_key(property_names::ID),
241            "JSON should contain $id"
242        );
243        assert!(
244            obj.contains_key(property_names::OWNER_ID),
245            "JSON should contain $ownerId"
246        );
247    }
248
249    #[test]
250    fn to_json_represents_none_timestamps_as_null() {
251        let platform_version = PlatformVersion::latest();
252        let doc = make_minimal_document_v0();
253        let json = doc
254            .to_json(platform_version)
255            .expect("to_json should succeed");
256        let obj = json.as_object().expect("should be an object");
257
258        // to_json serializes via serde, so None fields appear as null
259        if let Some(val) = obj.get(property_names::CREATED_AT) {
260            assert!(
261                val.is_null(),
262                "$createdAt should be null when None, got: {:?}",
263                val
264            );
265        }
266        if let Some(val) = obj.get(property_names::UPDATED_AT) {
267            assert!(
268                val.is_null(),
269                "$updatedAt should be null when None, got: {:?}",
270                val
271            );
272        }
273        if let Some(val) = obj.get(property_names::REVISION) {
274            assert!(
275                val.is_null(),
276                "$revision should be null when None, got: {:?}",
277                val
278            );
279        }
280    }
281
282    // ================================================================
283    //  to_json_with_identifiers_using_bytes includes all timestamps
284    // ================================================================
285
286    #[test]
287    fn to_json_with_identifiers_using_bytes_includes_all_timestamp_fields() {
288        let platform_version = PlatformVersion::latest();
289        let doc = make_document_v0_with_all_timestamps();
290        let json = doc
291            .to_json_with_identifiers_using_bytes(platform_version)
292            .expect("to_json_with_identifiers_using_bytes should succeed");
293        let obj = json.as_object().expect("should be an object");
294
295        assert!(obj.contains_key(property_names::ID));
296        assert!(obj.contains_key(property_names::OWNER_ID));
297        assert!(obj.contains_key(property_names::REVISION));
298        assert!(obj.contains_key(property_names::CREATED_AT));
299        assert!(obj.contains_key(property_names::UPDATED_AT));
300        assert!(obj.contains_key(property_names::TRANSFERRED_AT));
301        assert!(obj.contains_key(property_names::CREATED_AT_BLOCK_HEIGHT));
302        assert!(obj.contains_key(property_names::UPDATED_AT_BLOCK_HEIGHT));
303        assert!(obj.contains_key(property_names::TRANSFERRED_AT_BLOCK_HEIGHT));
304        assert!(obj.contains_key(property_names::CREATED_AT_CORE_BLOCK_HEIGHT));
305        assert!(obj.contains_key(property_names::UPDATED_AT_CORE_BLOCK_HEIGHT));
306        assert!(obj.contains_key(property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT));
307        assert!(obj.contains_key(property_names::CREATOR_ID));
308
309        // Verify numeric values
310        assert_eq!(obj[property_names::REVISION].as_u64(), Some(3));
311        assert_eq!(
312            obj[property_names::CREATED_AT].as_u64(),
313            Some(1_700_000_000_000)
314        );
315        assert_eq!(
316            obj[property_names::UPDATED_AT].as_u64(),
317            Some(1_700_000_100_000)
318        );
319        assert_eq!(
320            obj[property_names::TRANSFERRED_AT].as_u64(),
321            Some(1_700_000_200_000)
322        );
323        assert_eq!(
324            obj[property_names::CREATED_AT_BLOCK_HEIGHT].as_u64(),
325            Some(100)
326        );
327        assert_eq!(
328            obj[property_names::UPDATED_AT_BLOCK_HEIGHT].as_u64(),
329            Some(200)
330        );
331        assert_eq!(
332            obj[property_names::TRANSFERRED_AT_BLOCK_HEIGHT].as_u64(),
333            Some(300)
334        );
335        assert_eq!(
336            obj[property_names::CREATED_AT_CORE_BLOCK_HEIGHT].as_u64(),
337            Some(50)
338        );
339        assert_eq!(
340            obj[property_names::UPDATED_AT_CORE_BLOCK_HEIGHT].as_u64(),
341            Some(60)
342        );
343        assert_eq!(
344            obj[property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT].as_u64(),
345            Some(70)
346        );
347    }
348
349    #[test]
350    fn to_json_with_identifiers_using_bytes_includes_custom_properties() {
351        let platform_version = PlatformVersion::latest();
352        let doc = make_document_v0_with_all_timestamps();
353        let json = doc
354            .to_json_with_identifiers_using_bytes(platform_version)
355            .expect("should succeed");
356        let obj = json.as_object().expect("should be an object");
357        assert_eq!(
358            obj.get("label").and_then(|v| v.as_str()),
359            Some("test-label")
360        );
361    }
362
363    // ================================================================
364    //  from_json_value round-trip: to_json -> from_json_value
365    //  Uses String as the identifier deserialization type since
366    //  to_json produces base58 string identifiers.
367    // ================================================================
368
369    #[test]
370    fn json_round_trip_with_random_dashpay_profile() {
371        let platform_version = PlatformVersion::latest();
372        let contract = json_document_to_contract(
373            "../rs-drive/tests/supporting_files/contract/dashpay/dashpay-contract.json",
374            false,
375            platform_version,
376        )
377        .expect("expected to load dashpay contract");
378
379        let document_type = contract
380            .document_type_for_name("profile")
381            .expect("expected profile document type");
382
383        for seed in 0..5u64 {
384            let document = document_type
385                .random_document(Some(seed), platform_version)
386                .expect("expected random document");
387
388            let crate::document::Document::V0(doc_v0) = &document;
389
390            let json_val = doc_v0
391                .to_json(platform_version)
392                .expect("to_json should succeed");
393
394            let recovered = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
395                .expect("from_json_value should succeed");
396
397            assert_eq!(doc_v0.id, recovered.id, "id mismatch for seed {seed}");
398            assert_eq!(
399                doc_v0.owner_id, recovered.owner_id,
400                "owner_id mismatch for seed {seed}"
401            );
402            assert_eq!(
403                doc_v0.revision, recovered.revision,
404                "revision mismatch for seed {seed}"
405            );
406        }
407    }
408
409    // ================================================================
410    //  from_json_value extracts all system fields correctly
411    // ================================================================
412
413    #[test]
414    fn from_json_value_extracts_timestamps_and_revision() {
415        let platform_version = PlatformVersion::latest();
416        let id = Identifier::new([1u8; 32]);
417        let owner = Identifier::new([2u8; 32]);
418        let creator = Identifier::new([9u8; 32]);
419
420        let json_val = json!({
421            "$id": bs58::encode(id.to_buffer()).into_string(),
422            "$ownerId": bs58::encode(owner.to_buffer()).into_string(),
423            "$revision": 5,
424            "$createdAt": 1_000_000u64,
425            "$updatedAt": 2_000_000u64,
426            "$createdAtBlockHeight": 100u64,
427            "$updatedAtBlockHeight": 200u64,
428            "$createdAtCoreBlockHeight": 50u32,
429            "$updatedAtCoreBlockHeight": 60u32,
430            "$transferredAt": 3_000_000u64,
431            "$transferredAtBlockHeight": 300u64,
432            "$transferredAtCoreBlockHeight": 70u32,
433            "$creatorId": bs58::encode(creator.to_buffer()).into_string(),
434            "customProp": "hello"
435        });
436
437        let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
438            .expect("from_json_value should succeed");
439
440        assert_eq!(doc.id, id);
441        assert_eq!(doc.owner_id, owner);
442        assert_eq!(doc.revision, Some(5));
443        assert_eq!(doc.created_at, Some(1_000_000));
444        assert_eq!(doc.updated_at, Some(2_000_000));
445        assert_eq!(doc.created_at_block_height, Some(100));
446        assert_eq!(doc.updated_at_block_height, Some(200));
447        assert_eq!(doc.created_at_core_block_height, Some(50));
448        assert_eq!(doc.updated_at_core_block_height, Some(60));
449        assert_eq!(doc.transferred_at, Some(3_000_000));
450        assert_eq!(doc.transferred_at_block_height, Some(300));
451        assert_eq!(doc.transferred_at_core_block_height, Some(70));
452        assert_eq!(doc.creator_id, Some(creator));
453        // Custom property should be in properties map
454        assert_eq!(
455            doc.properties.get("customProp"),
456            Some(&Value::Text("hello".to_string()))
457        );
458    }
459
460    #[test]
461    fn from_json_value_handles_missing_optional_fields() {
462        let platform_version = PlatformVersion::latest();
463        let id = Identifier::new([3u8; 32]);
464        let owner = Identifier::new([4u8; 32]);
465        let json_val = json!({
466            "$id": bs58::encode(id.to_buffer()).into_string(),
467            "$ownerId": bs58::encode(owner.to_buffer()).into_string(),
468        });
469
470        let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
471            .expect("from_json_value should succeed with minimal fields");
472
473        assert_eq!(doc.id, id);
474        assert_eq!(doc.owner_id, owner);
475        assert_eq!(doc.revision, None);
476        assert_eq!(doc.created_at, None);
477        assert_eq!(doc.updated_at, None);
478        assert_eq!(doc.transferred_at, None);
479        assert_eq!(doc.created_at_block_height, None);
480        assert_eq!(doc.updated_at_block_height, None);
481        assert_eq!(doc.transferred_at_block_height, None);
482        assert_eq!(doc.created_at_core_block_height, None);
483        assert_eq!(doc.updated_at_core_block_height, None);
484        assert_eq!(doc.transferred_at_core_block_height, None);
485        assert_eq!(doc.creator_id, None);
486    }
487
488    // ================================================================
489    //  to_json_with_identifiers_using_bytes: minimal document has only
490    //  $id and $ownerId keys (no optional fields rendered).
491    // ================================================================
492
493    #[test]
494    fn to_json_with_identifiers_using_bytes_minimal_document_has_only_id_and_owner() {
495        let platform_version = PlatformVersion::latest();
496        let doc = make_minimal_document_v0();
497        let json = doc
498            .to_json_with_identifiers_using_bytes(platform_version)
499            .expect("to_json_with_identifiers_using_bytes should succeed");
500        let obj = json.as_object().expect("object");
501        assert!(obj.contains_key(property_names::ID));
502        assert!(obj.contains_key(property_names::OWNER_ID));
503        // None-valued optional fields are NOT emitted by this serializer.
504        assert!(!obj.contains_key(property_names::CREATED_AT));
505        assert!(!obj.contains_key(property_names::UPDATED_AT));
506        assert!(!obj.contains_key(property_names::TRANSFERRED_AT));
507        assert!(!obj.contains_key(property_names::CREATED_AT_BLOCK_HEIGHT));
508        assert!(!obj.contains_key(property_names::UPDATED_AT_BLOCK_HEIGHT));
509        assert!(!obj.contains_key(property_names::TRANSFERRED_AT_BLOCK_HEIGHT));
510        assert!(!obj.contains_key(property_names::CREATED_AT_CORE_BLOCK_HEIGHT));
511        assert!(!obj.contains_key(property_names::UPDATED_AT_CORE_BLOCK_HEIGHT));
512        assert!(!obj.contains_key(property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT));
513        assert!(!obj.contains_key(property_names::CREATOR_ID));
514        assert!(!obj.contains_key(property_names::REVISION));
515    }
516
517    // ================================================================
518    //  to_json_with_identifiers_using_bytes: id/owner emitted as
519    //  base58 strings (via serde_json derive on Identifier).
520    // ================================================================
521
522    #[test]
523    fn to_json_with_identifiers_using_bytes_emits_base58_identifiers() {
524        let platform_version = PlatformVersion::latest();
525        let doc = make_minimal_document_v0();
526        let json = doc
527            .to_json_with_identifiers_using_bytes(platform_version)
528            .expect("should succeed");
529        let obj = json.as_object().expect("object");
530        // $id and $ownerId are serialized as base58 strings by Identifier's
531        // Serialize impl, which is what the underlying json! macro uses.
532        let id_val = obj.get(property_names::ID).expect("id present");
533        assert!(id_val.is_string(), "expected base58 string for $id");
534        let owner_val = obj.get(property_names::OWNER_ID).expect("owner present");
535        assert!(owner_val.is_string(), "expected base58 string for $ownerId");
536    }
537
538    // ================================================================
539    //  from_json_value handles null creator_id by leaving it None.
540    // ================================================================
541
542    #[test]
543    fn from_json_value_with_null_creator_id_stays_none() {
544        let platform_version = PlatformVersion::latest();
545        let json_val = json!({
546            "$id": bs58::encode([1u8; 32]).into_string(),
547            "$ownerId": bs58::encode([2u8; 32]).into_string(),
548            "$creatorId": JsonValue::Null,
549        });
550        let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
551            .expect("from_json_value should succeed with null creator_id");
552        assert_eq!(doc.creator_id, None);
553    }
554
555    // ================================================================
556    //  from_json_value handles null id/owner by leaving them defaulted.
557    // ================================================================
558
559    #[test]
560    fn from_json_value_with_null_id_leaves_default() {
561        let platform_version = PlatformVersion::latest();
562        let json_val = json!({
563            "$id": JsonValue::Null,
564            "$ownerId": bs58::encode([2u8; 32]).into_string(),
565        });
566        let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
567            .expect("from_json_value should succeed with null $id");
568        // Default Identifier is all-zeros.
569        assert_eq!(doc.id, Identifier::new([0u8; 32]));
570    }
571
572    // ================================================================
573    //  to_json_with_identifiers_using_bytes: multiple user-defined
574    //  properties are all included.
575    // ================================================================
576
577    #[test]
578    fn to_json_with_identifiers_using_bytes_with_multiple_properties() {
579        let platform_version = PlatformVersion::latest();
580        let mut props = BTreeMap::new();
581        props.insert("a".to_string(), Value::U64(1));
582        props.insert("b".to_string(), Value::Text("two".to_string()));
583        props.insert("c".to_string(), Value::Bool(true));
584        let doc = DocumentV0 {
585            id: Identifier::new([1u8; 32]),
586            owner_id: Identifier::new([2u8; 32]),
587            properties: props,
588            revision: None,
589            created_at: None,
590            updated_at: None,
591            transferred_at: None,
592            created_at_block_height: None,
593            updated_at_block_height: None,
594            transferred_at_block_height: None,
595            created_at_core_block_height: None,
596            updated_at_core_block_height: None,
597            transferred_at_core_block_height: None,
598            creator_id: None,
599        };
600        let json = doc
601            .to_json_with_identifiers_using_bytes(platform_version)
602            .expect("should succeed");
603        let obj = json.as_object().expect("object");
604        assert_eq!(obj.get("a").and_then(|v| v.as_u64()), Some(1));
605        assert_eq!(obj.get("b").and_then(|v| v.as_str()), Some("two"));
606        assert_eq!(obj.get("c").and_then(|v| v.as_bool()), Some(true));
607    }
608
609    // ================================================================
610    //  from_json_value: an empty object produces a fully-defaulted doc
611    // ================================================================
612
613    #[test]
614    fn from_json_value_empty_object_returns_default_document() {
615        let platform_version = PlatformVersion::latest();
616        let doc = DocumentV0::from_json_value::<String, _>(json!({}), platform_version)
617            .expect("from_json_value should succeed with empty object");
618        assert_eq!(doc.id, Identifier::new([0u8; 32]));
619        assert_eq!(doc.owner_id, Identifier::new([0u8; 32]));
620        assert_eq!(doc.revision, None);
621        assert!(doc.properties.is_empty());
622    }
623
624    // ================================================================
625    //  from_json_value with creator_id
626    // ================================================================
627
628    #[test]
629    fn from_json_value_parses_creator_id() {
630        let platform_version = PlatformVersion::latest();
631        let creator = Identifier::new([0xCC; 32]);
632        let json_val = json!({
633            "$id": bs58::encode([1u8; 32]).into_string(),
634            "$ownerId": bs58::encode([2u8; 32]).into_string(),
635            "$creatorId": bs58::encode(creator.to_buffer()).into_string(),
636        });
637
638        let doc = DocumentV0::from_json_value::<String, _>(json_val, platform_version)
639            .expect("from_json_value with creator_id should succeed");
640
641        assert_eq!(doc.creator_id, Some(creator));
642    }
643}