marksands / bettercodable Goto Github PK
View Code? Open in Web Editor NEWBetter Codable through Property Wrappers
Home Page: http://marksands.github.io/2019/10/21/better-codable-through-property-wrappers.html
License: MIT License
Better Codable through Property Wrappers
Home Page: http://marksands.github.io/2019/10/21/better-codable-through-property-wrappers.html
License: MIT License
Tried:
@DATEvalue var cancellableUntil: Date?
This gives the error:
Property type 'Date?' does not match 'wrappedValue' type 'Date'
Hello,
please add non-codable property wrapper, as below, for properties that do not support codable protocol or do not want to decode / encode but can still be used and set separately.
/// Marks a property as non-codable and to be ignored
@propertyWrapper
struct Uncodable<Value> {
private var value: Value?
public init(wrappedValue: Value?) {
self.value = wrappedValue
}
public var wrappedValue: Value? {
get { value }
set { self.value = newValue }
}
}
extension Uncodable: Codable {
public func encode(to encoder: Encoder) throws {
// Skip encoding the wrapped value.
}
public init(from decoder: Decoder) throws {
// The wrapped value is simply initialised to nil when decoded.
self.value = nil
}
}
thanks, grat project!
I'm curious what the purpose of the LossyDecodableValue
private struct is in the LossyArray
wrapper. Since the generic T
type is constrained to be a Codable
type, couldn't the decoding in the wrapper be changed to:
let value = try container.decode(T.self)
I was playing around with this and it works as expected. Not sure I'm missing something and would appreciate some insight. Thanks!
I don't know if this is a Cocoapods problem or an SPM problem or a BetterCodable problem.
I removed the pod from my podfile, did a pod install, then added it via SPM. Now when I build I get the Framework not found BetterCodable
error and I have no idea why.
Anybody done this before?
Decoder
not containing the array's key => @DefaultEmptyArray
Decoder
contains the array's key, but one of the objects cannot be decoded => @LossyArray
But there is no possibility to have both behaviors.
struct User: Codable {
var name: String
var boughtArticles: [Article]
}
struct Article: Codable {
var price: Int
}
{
"name": "abc",
"bought_articles": [
{ "price": 15 },
{ "proice": 10 } // Miss-typed key
]
}
omitempty
{
"name": "abc"
}
In this case, neither possibilities work properly.
@LossyArray
would fail with the 2nd JSON@DefaultEmptyArray
would not fail, but it would be an empty array, and not contain the Article with price 10, that could've been correctly decoded.What are your thoughts on this? How should it be handled?
I can create a @DefaultEmptyLossyArray
, if there is no better option.
Hey Mark! I just tried to use BetterCodable on a package I'm working on and realized that adding the dependency this way .package(url: "https://github.com/marksands/BetterCodable.git", from: "0.0.0")
is picking 0.0.1 without the latest changes. This is because the release tag points to an earlier commit. I think we might need to do another release to have everyone use the latest changes.
Hi
I have a need to be able to handle both
I tried to compose those with something like:
@DefaultEmptyArray
@LossyArray
var items: [Item]
but I get the swift compiler error
Property type '[Model.Item]' does not match that of the 'wrappedValue' property of its wrapper type 'DefaultEmptyArray'
This seems because one of the two types arent able to wrap the other (both want to take the original T
).
To fix this I have done the following:
struct DefaultLossyArray<T: Codable>: DefaultCodableStrategy {
static var defaultValue: LossyArray<T> { .init(wrappedValue: []) }
}
typealias EmptyLossyArray<T> = DefaultCodable<DefaultLossyArray<T>> where T: Codable
with this addition I can hadle the above two cases well.
@EmptyLossyArray
@LossyArray
var items: [Item]
Do you feel something like this is worth adding to BetterCodable
?
Please let me know, I can send a PR w/tests etc.
Thanks!
How to deal with such scenario:
@DefaultFalse @LosslessValue var isAdmin: Bool
Ole Begemann came up with a nice use case for fixing a shortcoming of Codable when dealing with Dictionaries that have a String based Enum key. Currently, these unexpectedly encode/decode as arrays rather than dictionary representations
https://gist.github.com/ole/5c31ca9e2919c815029784ef3b8fdc0d
If this is too specific of a use case, it might not make sense to include it, but I believe it solves a real problem that I have faced with Codable. What do you think about including this or a similar implementation? If you think it's useful, this package feels like a good home for it.
With the current naming the DefaultCodingStrategy can't be used with enums and types conforming to RawRepresentable. RawValue is an associated type that is used by the RawRepresentable protocol. Therefore if the DefaultCodingStrategy is implemented by an enum the defaultValue refers to the RawValue of that enum type which does not work with the DefaultCodable property wrapper since the types of wrappedValue and defaultValue have to match.
Here a little example:
enum VehicleType: String {
case car
case motorcycle
case unknown
}
extension VehicleType: DefaultCodableStrategy {
static var defaultValue: String { // the compiler suggests this type
return Vehicle.unknown
}
}
struct Vehicle: Codable {
let name: String
@DefaultCodable<VehicleType>
let vehicleType: VehicleType // compiler error
}
What are the options and thoughts on adding separate Decodable / Encodable support?
With the current implementation, everything relies on T: Codable
.
So for example, the following code will not compile, because @DefaultEmptyArray
requires ResponseObject
to confirm to Encodable
as well.
struct NetworkResponse: Decodable {
@DefaultEmptyArray var someArray: [ResponseObject]
}
struct ResponseObject: Decodable {
var age: Int
}
But I only care that NetworkResponse
be Decodable
, I don't need Encodable
as well.
In order to make it compile. I need to conform/implement Encodable
on ResponseObject
.
It's trivial of course for the current ResponseObject
, but when the object is more complex and uses a custom decoder initializer, I'm left with 2 bad options:
Encodable
implementation, which will never be usedencode(into:)
method, but then I lose compile time guarantee (which is the whole reason I'm using BetterCodable
, so I don't have to write a bunch of useless guard
s)DefaultCodable<Strategy<T>>
would only require T
to confirm to what the parent requires. In this case NetworkResponse
only requires Decodable
, so ResponseObject
should only need Decodable
.
ResponseObject
could be used in 2 places, one requiring Decodable
, the other one requiring Encodable
)DefaultDecodableEmptyArray
etc. etc.let data = """
{"a": "1"}
""".data(using: .utf8)!
struct A: Decodable {
@DefaultFalse var a: Bool
}
let m = try JSONDecoder.init().decode(A.self, from: data)
print(m.a) // print is false
but actual true
is right answer
Is there any option for decoding entities that have custom date formats?
I have had this work once with the same project no issues. Then suddenly a pod install causes it to break. For whatever reason 0.1.0 works fine. Is this because the podspec is still saying s.version = '0.1.0'
?
Our dep target is set to 12.4.
[!] CocoaPods could not find compatible versions for pod "BetterCodable":
In snapshot (Podfile.lock):
BetterCodable (= 0.1.0)
In Podfile:
ViewModels/Tests (from `Frameworks/ViewModels/`) was resolved to 0.1.0, which depends on
BetterCodable (~> 0.2.0)
Specs satisfying the `BetterCodable (= 0.1.0), BetterCodable (~> 0.2.0)` dependency were found, but they required a higher minimum deployment target.
struct Foo: Decodable { enum CodingKeys: String, CodingKey { case contentIDs = "contentIds" || "Ids" || "IDs" // something like this? } let contentIDs: [UUID] }
I have an API which either sends {"bool":true} or no key at all. But I still like to have a non-optional Bool in my Codable class because I have to access it from Objective-C (which can't access optional Bools). Is that possible somehow? Thanks.
sometimes when error happened, we don't konw where is wrong, and debug difficultly
I've added a Bool
type auto-conversion function, so if people want to add it to their projects, I'll try pulling requests.
JSON | Swift |
---|---|
"false", "no", "0", "n", "f", 0, false | false |
"true", "yes", "1", "y", "t", 1, true | true |
import Foundation
@propertyWrapper
public struct BoolValue: Codable {
public var wrappedValue: Bool
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringifiedValue = try? container.decode(String.self) {
switch stringifiedValue.lowercased() {
case "false", "no", "0", "n", "f": wrappedValue = false
case "true", "yes", "1", "y", "t": wrappedValue = true
default:
let description = "Expected to decode Bool but found a '\(stringifiedValue)' instead."
throw DecodingError.dataCorruptedError(in: container, debugDescription: description)
}
} else if let integerifiedValue = try? container.decode(Int.self) {
switch integerifiedValue {
case 0: wrappedValue = false
case 1: wrappedValue = true
default:
let description = "Expected to decode Bool but found a '\(integerifiedValue)' instead."
throw DecodingError.dataCorruptedError(in: container, debugDescription: description)
}
} else {
wrappedValue = try container.decode(Bool.self)
}
}
public func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
}
Usage:
@BoolValue var isFollow: Bool
Thank you for your code ๐ใ
The following code also works on my project,
If you think it's useful, this package feels like a good home for it ๐คใ
protocol CodableEnumeration: RawRepresentable, Codable where RawValue: Codable {
static var defaultCase: Self { get }
}
extension CodableEnumeration {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let decoded = try container.decode(RawValue.self)
self = Self.init(rawValue: decoded) ?? Self.defaultCase
} catch {
self = Self.defaultCase
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}
Usage:
enum FreeType: Int, CodableEnumeration {
case normal = 1
case free = 2
static var defaultCase: Self { .normal }
}
let type: FreeType?
let json = """ [ {"name": "Taro", "age": 20}, {"name": "Hanako"} ] """.data(using: .utf8)! // Hanako has no "age"
I have a field updatedON
that comes as a nil value from the api, but gets updated in the local DB after an object is updated. Not sure how to handle this. Essentially I'd like to combine ISO8601Strategy
and DefaultCodableStrategy
`
struct DefToZero: DefaultCodableStrategy {
static var defaultValue: Double { 0.0 }
}
struct T : Hashable, Equatable, Codable {
@DefaultCodable var a : Double = 0.0
@DefaultCodable var b : Double = 0.0
}
struct C : Hashable, Equatable, Codable {
var t : T
}
`
When I run this, however, I get...
CodingKeys(stringValue: "a", intValue: nil) in Context(codingPath: [CodingKeys(stringValue: "t", intValue: nil), debugDescription: "No value associated with key CodingKeys(stringValue: \"a\", intValue: nil) (\"a\").", underlyingError: nil)
I've simplified things a bit, but this retains the jist. Should nested structs like this work? Am I right in expecting this to decode from a JSON that is missing the a member of the substruct t?
Hi @marksands, thanks for such a great library! I'm currently thinking of making XMLCoder more community-driven to encourage more contributions. I'm considering an umbrella organization (could name it https://github.com/SwiftCodable, but suggestions are welcome) that could host multiple libraries related to Codable
, including XMLCoder and possibly your library? I hope this would be more than the sum of all parts, since this would also help discoverability for potential users and cross-pollination across those libraries. Would you be interested in such collaboration?
We started getting this warning on Xcode 13 on watchOS extension. I am not sure at the moment what symbol it is using that's extension unsafe but any help would be appreciated
Currently when you have a class with a property marked as @LosslessValue
you cannot initialize the class "directly" via a custom init
method as the property marked as @LosslessValue
can only by initialized as Decodable
.
Hello,
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)!
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
I'm using a JSONEncoder
with dateEncodingStrategy
set to .iso8601
like this:
extension JSONEncoder {
public static var iso: JSONEncoder {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
return encoder
}
}
My Codable
type has a discoverDate
property which I specify as @DefaultCodable
:
@DefaultCodable<Date.DefaultToNow>
public var discoverDate: Date
The DefaultCodableStrategy
I use is defined like this (I'm using Date()
as the default value):
extension Date {
public enum DefaultToNow: DefaultCodableStrategy {
public static var defaultValue: Date { Date() }
}
}
But for some reason, when I encode the object using JSONEncoder.iso
, it ignores the dateEncodingStrategy
which I set to iso8601
but it renders like I didn't set it. For example, my JSON output looks something like this:
{ "discoverDate" : 7200 }
But I would expect it to look like this:
{ "discoverDate" : "2001-01-01T02:00:00Z" }
I believe this is a bug. Any ideas how I can work around this?
The last commit occurred on Sep 12, 2022
0.4.0 was released on Jul 10, 2021
ยท 8 commits to master since this release
Would you be open to making a new release? In a newer build system I'm using, I kind of need a proper tag and cannot just point at a SHA. While I could obviously fork the repo to (just) create a tag, I'd prefer the canonical repo.
Thanks!
Hi, I have a following sample structure of the object:
struct A: Codable {
.
.
.
@LossyOptional var objectB: B?
.
.
.
}
struct B: Codable {
.
.
.
@LossyOptional var details: String?
.
.
.
}
I am trying to decode object of type A and in case there is a missing details
key in object B, I always get the whole B as a nil
.
Shouldn't decoding make details
equal to nil and continue to decode the rest of the properties?
Or am I getting it wrong?
Hi,
I've found an issue when using @LosslessValue
from Int response.
We are currently upgrading our backend to a new API version and we need to handle both Int
and String
value for the id
field.
To handle it properly we decided to set the id
field with the @LosslessValue
Property Wrapper.
struct Response: Codable {
@LosslessValue var id: String
}
let json = #"{ "id": 1 }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
print(result) // Response(id: "true")
The result for this example should be Response(id: "1")
instead of Response(id: "true")
Note that everything works as excepted for int value > 1
This seems because of the decode priority in the types
array in LosslessValue.swift
let types: [(Decoder) -> LosslessStringCodable?] = [
decode(String.self),
decodeBoolFromNSNumber(),
decode(Bool.self),
decode(Int.self),
decode(Int8.self),
decode(Int16.self),
decode(Int64.self),
decode(UInt.self),
decode(UInt8.self),
decode(UInt16.self),
decode(UInt64.self),
decode(Double.self),
decode(Float.self)
]
As we can see the decodeBoolFromNSNumber()
takes priority over the decode(Int.self)
Is there a way to combine the property wrapper with optionality?
Say I have
struct S {
let timestamp: Date?
}
I want the timestamp
property to be decoded using a custom strategy, but
struct S {
@DateValue<TimestampStrategy> var timestamp: Date?
}
does not build because the timestamp
property is optional.
Is there a way to make it work?
I tried defining a date strategy on optional types but that does not work.
BetterCodable
is very cool library, but I noticed that field mapping doesn't seem to be supported now? I have to keep the model fields and json fields unified, which may not be a good fit for our project.
Do you have any plans to support field mapping? This would help us to completely ditch the custom Codable
implementation.
Using DefaultCodable
as an example, you can add an optional String type parameter to the init(wrappedValue:)
method.
Afterwards, the init(from:)
and encode(to:)
methods decide whether to use the singleValueContainer
or container(keyedBy:)
method depending on whether the field has a value or not.
Similar to LosslessValue
, just on working or arrays.
Example:
struct S {
@LoselessArray var groupIds: [String]
}
would decode
{
"groupIds": ["1","2","3"]
}
and also
{
"groupIds": [1,2,3]
}
let json = """ {"user_id": "abc", "name": "tattn"} """.data(using: .utf8)!
For Upper Camel Case:
let json = """
{"UserId": "abc", "Name": "tattn"}
""".data(using: .utf8)!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.