Representing arbitrary data (e.g JSON) as a custom and opaque Codable type
Represent arbitrary data (e.g JSON) using a custom and opaque Codable type consisting of a hierarchy of primitive data values and nested…
Representing arbitrary data (e.g JSON) as a custom and opaque Codable type
Represent arbitrary data (e.g JSON) using a custom and opaque Codable type consisting of a hierarchy of primitive data values and nested Codables.

Arbitrary data/JSON represented as a custom/opaque Codable type.
Disclaimer: The idea and example code is heavily inspired by Rob Napier’s answer to this StackOverflow post (thank you!).
The OpaqueValue type
Building upon Swift’s concept of Encodable/Decodable and Encoder/Decoder, we can define a type that can represent arbitrary (Encodable/Decodable) data. Basically making the data opaque to our app.
The secret lies in manually implementing Encodable.encode(to: _) and Decodable.init(from: _), and also defining a struct conforming to the CodingKey protocol. This allows us to not be explicit about property names.
/// An opaque Codable type that can represent arbitrary (Codable) data. E.g some arbitrary JSON.
/// It consists of one or more primitive types and/or one or more nested opaque values.
///
/// This code is heavily inspired by [Rob Napier](https://stackoverflow.com/users/97337/rob-napier)'s answer to this [StackOverflow post](https://stackoverflow.com/questions/65901928/swift-jsonencoder-encoding-class-containing-a-nested-raw-json-object-literal).
enum OpaqueValue: Equatable {
struct PropertyKey: CodingKey, Hashable {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = String(intValue)
}
}
case object([PropertyKey: OpaqueValue])
case array([OpaqueValue])
case string(String)
case number(Double)
case boolean(Bool)
case null
}
// MARK: OpaqueValue+Encodable
extension OpaqueValue: Encodable {
func encode(to encoder: any Encoder) throws {
switch self {
case .object(let values):
var container = encoder.container(keyedBy: PropertyKey.self)
for (key, value) in values {
try container.encode(value, forKey: key)
}
case .array(let values):
var container = encoder.unkeyedContainer()
for value in values {
try container.encode(value)
}
case .string(let value):
var container = encoder.singleValueContainer()
try container.encode(value)
case .number(let value):
var container = encoder.singleValueContainer()
try container.encode(value)
case .boolean(let value):
var container = encoder.singleValueContainer()
try container.encode(value)
case .null:
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
}
// MARK: OpaqueValue+Decodable
extension OpaqueValue: Decodable {
init(from decoder: any Decoder) throws {
if let container = try? decoder.container(keyedBy: PropertyKey.self) {
var values: [PropertyKey: OpaqueValue] = [:]
for key in container.allKeys {
values[key] = try container.decode(OpaqueValue.self, forKey: key)
}
self = .object(values)
} else if var container = try? decoder.unkeyedContainer() {
var values: [OpaqueValue] = []
while !container.isAtEnd {
values.append(try container.decode(OpaqueValue.self))
}
self = .array(values)
} else {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(String.self) {
self = .string(value)
} else if let value = try? container.decode(Double.self) {
self = .number(value)
} else if let value = try? container.decode(Bool.self) {
self = .boolean(value)
} else {
guard container.decodeNil() else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Data unrecognizable")
}
self = .null
}
}
}
}
Representing arbitrary JSON as OpaqueValue
With our OpaqueValue type we can easily decode/encode arbitrary JSON.
let someArbitraryJSONData = Data("""
{
"message": {
"text": "Hello World"
},
"texts": ["Hello", "World"],
"note": "Hello World",
"age": 1337,
"tooOld": true,
"child": null
}
""".utf8)
var opaqueValue = try JSONDecoder().decode(OpaqueValue.self, from: someArbitraryJSONData)
let someOtherArbitraryJSONData = Data("""
{
"user": {
"name": "Thomas",
"id": 1337,
"isAdmin": false,
"lastUpdated": null
},
"mix": ["Hello World", 1337, false, null],
}
""".utf8)
opaqueValue = try JSONDecoder().decode(OpaqueValue.self, from: someOtherArbitraryJSONData)
// ...
struct SomeDataModel: Encodable {
let id: Int
let text: String
let opaqueData: OpaqueValue
}
let someDataModel = SomeDataModel(id: 1337, text: "Hello World", opaqueData: opaqueValue)
let someDataModelAsJSON = try JSONEncoder().encode(someDataModel)
Final words
Neat, right!? From time to time I find my self in a situation where I need to pass some arbitrary data from one back-end to another. And though I might need to move the data around in my app a bit before passing it along to the next back-end, my app doesn’t really need to know the details about the content of the data. And sometimes the data should be completely opaque to my app.
Then OpaqueValue is perfect!
Happy coding! 😄
메타데이터
- post_id
- dfaa07b22cd3
- slug
- representing-arbitrary-data-e-g-json-as-a-custom-and-opaque-codable-type-dfaa07b22cd3
- url
- https://medium.com/@thomsmed/representing-arbitrary-data-e-g-json-as-a-custom-and-opaque-codable-type-dfaa07b22cd3
- canonical_url
- https://medium.com/@thomsmed/representing-arbitrary-data-e-g-json-as-a-custom-and-opaque-codable-type-dfaa07b22cd3
- author_url
- https://medium.com/@thomsmed
- status
- ok
- fetched_at
- 2026-07-23 09:39:55