Audit snapshots and API serialization
Use sf_toolkit.serialize_snapshot for private audit/storage documents, not
for Salesforce write payloads. It recursively retains declared readonly fields
(including IDs), nested records/lists, and captured unknown response fields.
Normal serialize_object remains the write-oriented formatter: it excludes
readonly/unknown fields by default, including nested typed list records.
import json
from sf_toolkit import serialize_snapshot
document = serialize_snapshot(deployment_result)
private_audit_json = json.dumps(document)
known_fields_only = serialize_snapshot(deployment_result, include_unknown=False)
Snapshots reuse scalar field formatting. Dates/times become ISO strings,
multipicklists become semicolon-delimited strings, and null stays JSON null.
Nested references and list records use snapshot formatting recursively rather
than write formatting, so readonly IDs and future metadata fields are not lost.
Declared fields win collisions with unknown audit fields. Shared references
are serialized at each location; cycles and depth beyond max_depth (default
100) raise a fixed ValueError. Non-finite numbers, non-string mapping keys,
opaque objects and binary fields require explicit handling.
This formatting applies to hydrated models, whether constructed directly or returned by model-based API helpers. A parsed JSON dictionary/list (including a raw Metadata response) stays a JSON tree: snapshots do not inject schema fields or conflate absent keys with null. Snapshot formatting is not a substitute for keeping the raw response when exact parsed shapes matter.
The serializer does not inspect arbitrary object internals or read blob files. It is not a redaction layer: declared/captured data can contain secrets or PII. Keep snapshots private, and never pass them as API mutation payloads or unrestricted action output. Unknown fields captured by non-strict models are audit data, not permission to write new Salesforce fields. Strict models continue rejecting undeclared constructor fields.
Metadata models retain future provider fields in additional_fields and expose
them to explicit snapshots without adding them to write serialization. The known
deployment statuses include Queued, FinalizingDeploy and
FinalizingDeployFailed. In the default hydrated models, done and success
accept JSON booleans or null, not truthy strings/numbers. Flow coverage results/warnings are retained
in RunTestsResult. These types do not replace application-specific deployment
policy or a credential-safe error boundary.
ListField(ChildModel) automatically hydrates dictionary members into declared
model instances, including non-SObject models. Nested field validation runs during
deserialization, not only on a later write. Undeclared data inside each model is
retained without guessing its type. typed_items=False explicitly leaves a
field’s list members untyped; RawField is a schema-less field alternative.
For a complete raw Metadata response, use typed=False. API-write formatting
still applies the declared member schema and excludes readonly/unknown fields;
snapshot formatting retains readonly/unknown fields recursively.
Populated binary declarations on either a regular model or an SObject require explicit handling, even when SObject’s API-oriented field map hides the blob. Snapshots reject them without opening/reading content. Attribute reassignment of already-captured unknown fields updates their audit store; arbitrary new implementation/private attributes are not automatically reflected into snapshots.
Multipicklist values
MultiPicklistField preserves null and empty string distinctly, and its
formatter uses the current MultiPicklistValue.values selections rather than
the str subclass’s original bytes. Selection validation also runs during
formatting, catching invalid in-place edits.
record.Tags.values[:] = ["First", "Second"]
# Descriptor assignment is needed to mark in-place edits dirty for an
# only_changes=True write; this also applies to other mutable field values.
record.Tags = record.Tags
Use the field/object serializers rather than JSON-encoding a bare
MultiPicklistValue: JSON encoders treat str subclasses as their original
string value and do not call their overridden __str__ method.