Coping tools for gaslighting JSON.
Sometimes it's a nu
8000
mber.
Sometimes it's a string.
Sometimes it's null.
Sometimes it's a whole object pretending to be a number.
And the API doc? It swears it's always an Int
.
You can't change the backend. But you can protect your decoder — and your sanity.
HasLazyServer provides Codable types that accept the chaos, convert it safely, and log when things go sideways (in DEBUG).
These types help you decode values that shift between string, int, or double. They normalize the result to a usable format, and log inconsistencies.
struct Item: Codable {
let price: MaybeNumber
let title: MaybeString
}
let jsonList = [
#"{"price": 100.0, "title": "Book"}"#,
#"{"price": "100.0", "title": 3001}"#,
]
jsonList.forEach {
guard let item:Item = $0.data?.makeObj() else {return}
print("💰 Price as Double:", item.price.asDouble)
print("💰 Price as Int:", item.price.asInt)
print("🪪 Title:", item.title.asString)
}
💰 Price as Double: Optional(100.0)
💰 Price as Int: Optional(100)
🪪 Title: Optional("Book")
⚠️ [MaybeNumber] 'price' got String instead of Int/Double
⚠️ [MaybeString] 'title' got Int instead of String
💰 Price as Double: Optional(100.0)
💰 Price as Int: Optional(100)
🪪 Title: Optional("3001")
Sometimes you get one, sometimes many. SometimeArray remembers which it was — but gives you .asArray when you just want to move on.
struct User: Codable {
let name: String
let email: String?
let age: MaybeNumber?
}
struct UserList: Codable {
let users: SometimeArray<User>
}
let jsonList = [
#"{"name": "john", "email": "john@example.com", "age": "30"}"#,
#"[{"name": "john1", "email": "john1@example.com", "age": 30},{"name": "john2", "email": "john2@example.com", "age": "30"}]"#
]
jsonList.forEach {
guard let userList:UserList = $0.data?.makeObj() else { return }
userList.users.asArray.forEach {
print($0.name)
print($0.email)
print($0.age)
}
}
⚠️ [MaybeNumber] 'users.age' got String instead of Int/Double
john
Optional("john@example.com")
Optional(30)
⚠️ [MaybeNumber] 'users.Index 1.age' got String instead of Int/Double
john1
Optional("john1@example.com")
Optional(30)
john2
Optional("john2@example.com")
Optional(30)
In DEBUG builds, HasLazyServer prints logs for type mismatches. So you know when and where your trust was betrayed.
⚠️ [MaybeNumber] 'price' got String instead of Int/Double
⚠️ [MaybeString] 'title' got Int instead of String
These types work great standalone — but also integrate cleanly with FullyRESTful, a declarative Swift API library for REST and WebSocket.
import FullyRESTful
struct Book : Codable {
let name:MaybeString
let author:MaybeString
let price:MaybeNumber
let code:MaybeString?
}
struct Search : APIITEM {
struct Request : Codable {
let searchText:String
}
struct Response : Codable {
let items:SometimeArray<Book>
let totalCount:MaybeNumber
}
let requestModel = Request.self
let responseModel = Response.self
var method:HTTPMethod = .GET
var customHeader: [String : String]?
let server: ServerInfo = .init(domain: "https://myServer.com:8080", defaultHeader: ["Content-Type": "application/json; utf-8"])
let path = "/library/books/search"
}
Task {
let bookList = try? await Search().request(param: .init(searchText: "API Docs"))?.model?.items.asArray
bookList?.forEach {
print($0.name.asString)
print($0.author.asString)
}
}
When something is supposed to never be null, but reality disagrees — this type lets you keep going, while leaving a paper trail in your logs.
let price:InsistsNonNull<MaybeNumber>
Your app deserves to live. You deserve to sleep.
Let HasLazyServer handle the weird stuff. And protect your mental health — it's the only real production system you run.
MIT License