Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# CHANGELOG

## v2.3.4

### Date: 12-Jun-2026

### Bug Fixes

- Added `toJSON()` to `EntryModel`, `AssetModel`, and `ContentTypeModel` to make entries with resolved references serializable via `JSONSerialization`.

## v2.3.3

### Date: 18-May-2026
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
version = "2.0">
version = "2.2">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<AutocreatedTestPlanReference>
</AutocreatedTestPlanReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
Expand Down Expand Up @@ -45,6 +56,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0F4A75D2241BAC4300E3A024"
BuildableName = "ContentstackSwift iOS Tests.xctest"
BlueprintName = "ContentstackSwift iOS Tests"
ReferencedContainer = "container:ContentstackSwift.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
7 changes: 7 additions & 0 deletions Sources/AssetModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ public final class AssetModel: AssetDecodable {
fields = try containerFields.decode(Dictionary<String, Any>.self)
}

/// Returns a JSON-serializable `[String: Any]` dictionary suitable for use with
/// `JSONSerialization`. Recursively converts any nested SDK model objects to plain
/// dictionaries. See `EntryModel.toJSON()` for context on why this is needed.
public func toJSON() -> [String: Any] {
return EntryModel.normalizeForJSON(fields ?? [:]) as? [String: Any] ?? [:]
}

public enum QueryableCodingKey: String, CodingKey {
case uid, title
case fileName = "filename"
Expand Down
22 changes: 22 additions & 0 deletions Sources/ContentTypeModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ public final class ContentTypeModel: SystemFields, Decodable {
}
}

/// Returns a JSON-serializable `[String: Any]` dictionary suitable for use with
/// `JSONSerialization`. See `EntryModel.toJSON()` for context on why this is needed.
public func toJSON() -> [String: Any] {
var json: [String: Any] = [
"uid": uid,
"title": title,
// schema entries originate from the decoder and are JSON-native, but normalize
// defensively in case any nested model object is present.
"schema": EntryModel.normalizeForJSON(schema)
]
if let description = description { json["description"] = description }
if let createdAt = createdAt { json["created_at"] = formatDate(createdAt) }
if let updatedAt = updatedAt { json["updated_at"] = formatDate(updatedAt) }
return json
}

private func formatDate(_ date: Date) -> String {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter.string(from: date)
}

public enum FieldKeys: String, CodingKey {
case title, uid, description
case createdAt = "created_at"
Expand Down
29 changes: 29 additions & 0 deletions Sources/EntryModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,33 @@ public class EntryModel: EntryDecodable, ContentTypeIncludable {
let containerFields = try decoder.container(keyedBy: JSONCodingKeys.self)
fields = try containerFields.decode(Dictionary<String, Any>.self)
}

/// Returns a JSON-serializable `[String: Any]` dictionary suitable for use with
/// `JSONSerialization`. When `include_all` or reference includes are used, the SDK
/// stores nested `EntryModel`/`AssetModel`/`ContentTypeModel` objects inside the
/// `fields` dictionary. Those Swift objects are not accepted by `JSONSerialization`,
/// so this method recursively converts them to plain dictionaries before returning.
public func toJSON() -> [String: Any] {
return EntryModel.normalizeForJSON(fields ?? [:]) as? [String: Any] ?? [:]
}

static func normalizeForJSON(_ value: Any) -> Any {
switch value {
case let entry as EntryModel:
return entry.toJSON()
case let asset as AssetModel:
return asset.toJSON()
case let contentType as ContentTypeModel:
return contentType.toJSON()
case let array as [Any]:
return array.map { normalizeForJSON($0) }
case let dict as [String: Any]:
return dict.mapValues { normalizeForJSON($0) }
default:
// Reached for JSON-native scalars (String/NSNumber/Bool/NSNull). The SDK decoder
// only ever stores these or the three model types above inside `fields`. If a new
// decodable model type is ever added to Decodable.swift, add a case for it here.
return value
}
}
}
Loading