Using JSON with custom types
At a glance
| Item | Summary |
|---|---|
| Purpose | Demonstrate array decoding, key renaming, nested source shapes, and dynamic JSON keys with Codable. |
| Deliverable | A multi-page Xcode playground, not an app with a runtime UI or shared object graph. |
| Architecture | Each page is an independent executable lesson with local JSON, local types, encoder/decoder calls, and printed output. |
Project structure
Using-JSON-with-Custom-Types.playground/Pages/
├── Read Data from Arrays.xcplaygroundpage/Contents.swift
├── Change Key Names.xcplaygroundpage/Contents.swift
├── Access Nested Data.xcplaygroundpage/Contents.swift
├── Merge Data from Different Depths.xcplaygroundpage/Contents.swift
└── Table of Contents.xcplaygroundpage/Contents.swift
Declarations with the same names on different pages are isolated by playground-page execution; they do not form one module-wide model layer.
Overall architecture
flowchart LR
JSON["external JSON shape"] --> Decoder["JSONDecoder"]
Decoder --> DTO["GroceryStoreService Decodable DTO"]
DTO --> Adapter["GroceryStore.init(from:)"]
Adapter --> Domain["flattened GroceryStore"]
Domain --> App["consumer / printed output"]
Reference code
Using-JSON-with-Custom-Types.playground/Pages/Access Nested Data.xcplaygroundpage/Contents.swift:143 — the domain initializer deliberately removes transport-only aisle/shelf nesting.
extension GroceryStore {
// ...
}Ownership and state
classDiagram
JSONDecoder ..> GroceryStoreService : creates values
GroceryStoreService *-- Aisle
Aisle *-- Shelf
Shelf *-- Product
GroceryStore *-- Product : flattened array
JSONEncoder ..> GroceryStore : reads values
GroceryStore ..> ProductKey : dynamic keys
Ownership evidence
Using-JSON-with-Custom-Types.playground/Pages/Merge Data from Different Depths.xcplaygroundpage/Contents.swift:87 — encoding creates nested containers from domain values without persistent shared state.
func encode(to encoder: Encoder) throws {
// ...
}All state is value-semantic and page-local. Decoders create DTO/domain values; adapter initializers copy the needed data. Encoders borrow values to write containers. There is no owner with a lifecycle beyond playground execution.
Class and protocol design
| Type technique | Responsibility |
|---|---|
GroceryProduct: Codable |
Use synthesized array element coding when names and shape align. |
Private CodingKeys enum |
Map wire keys such as product_name to idiomatic Swift properties. |
GroceryStoreService: Decodable |
Mirror a nested transport shape exactly. |
GroceryStore.init(from:) |
Convert transport values into a flatter domain shape. |
ProductKey: CodingKey struct |
Represent unbounded product names as dynamic JSON keys. |
Manual encode / init(from:) |
Merge key names and nested values into one product model. |
Protocol-oriented design here means conformance to Codable, Encodable, Decodable, and CodingKey; there is no repository or service interface.
Access control
| Boundary | Effect and rationale |
|---|---|
private CodingKeys |
Keeps wire-field names inside the type whose representation they define. |
Page types and values are implicit internal |
They are lesson-local implementation, not a public package API. |
public init(from:) on the internal GroceryStore extension |
Satisfies/illustrates decoding visibility, but the internal enclosing type caps external access. |
| Nested DTO types | Names transport-only aisle/shelf concepts without exposing them to the flattened domain model. |
Using-JSON-with-Custom-Types.playground/Pages/Change Key Names.xcplaygroundpage/Contents.swift:47 shows the private key map; the explicitly public decoder initializer is at Using-JSON-with-Custom-Types.playground/Pages/Merge Data from Different Depths.xcplaygroundpage/Contents.swift:124.
Logic ownership and placement
| Logic | Owner |
|---|---|
| Wire-name mapping | Nested CodingKeys |
| Transport-shape mirroring | GroceryStoreService and nested DTOs |
| Transport-to-domain flattening | GroceryStore.init(from:) |
| Dynamic-key generation | ProductKey |
| Manual nested encode/decode | GroceryStore protocol conformances |
| Lesson execution and output | Page-level statements |
Design patterns
| Pattern | Evidence | Purpose |
|---|---|---|
| Synthesized Codable | Using-JSON-with-Custom-Types.playground/Pages/Read Data from Arrays.xcplaygroundpage/Contents.swift:38 |
Handles aligned homogeneous array data with minimal code. |
| Coding-key mapping | Using-JSON-with-Custom-Types.playground/Pages/Change Key Names.xcplaygroundpage/Contents.swift:42 |
Separates idiomatic Swift names from wire names. |
| DTO / translation boundary | Using-JSON-with-Custom-Types.playground/Pages/Access Nested Data.xcplaygroundpage/Contents.swift:115 |
Prevents transport nesting from shaping the app model. |
| Custom dynamic key | Using-JSON-with-Custom-Types.playground/Pages/Merge Data from Different Depths.xcplaygroundpage/Contents.swift:74 |
Supports data-dependent object keys. |
Naming conventions
GroceryStoreServicesignals a source-shaped intermediary;GroceryStoreis the app-facing domain value.- Nested types (
Aisle,Shelf,ProductKey) keep names scoped to the representation that needs them. - Initializers use
init(from:)for translation and Codable decoding, with the parameter type disambiguating their roles.
Architecture takeaways
- Model transport JSON exactly in a DTO when its shape is not suitable for app code.
- Convert at one initializer boundary and keep the domain representation simple.
- Keep wire keys private to their coding type.
- Treat each playground page as an independent architecture example, not one combined application.
Source map
| Source | Role |
|---|---|
Using-JSON-with-Custom-Types.playground/Pages/Read Data from Arrays.xcplaygroundpage/Contents.swift:38 |
Synthesized array coding |
Using-JSON-with-Custom-Types.playground/Pages/Change Key Names.xcplaygroundpage/Contents.swift:42 |
Static key mapping |
Using-JSON-with-Custom-Types.playground/Pages/Access Nested Data.xcplaygroundpage/Contents.swift:115 |
Source-shaped DTO |
Using-JSON-with-Custom-Types.playground/Pages/Access Nested Data.xcplaygroundpage/Contents.swift:143 |
DTO-to-domain adapter |
Using-JSON-with-Custom-Types.playground/Pages/Merge Data from Different Depths.xcplaygroundpage/Contents.swift:73 |
Dynamic coding keys |