Support for a KeyCodingStrategy option in JSONEncoder and JSONDecoder

Hi all,
At work we have just added Codable support for a whole bunch of model
objects in our code base.
Many places we have added CodingKeys enumeration in order to convert the
camel cased property names to snake case for our JSON keys.
As an experiment I have tried adding a KeyCodingStrategy option to a copy
of the JSONEncoder and JSONDecoder implementations.
This is currently an enumeration with the following values
.original
.snakeCase
.custom((String) -> String)

I just extended CodingKey as follows:
extension CodingKey {
    func stringValue(with encodingStrategy:
StructuralEncoder.KeyEncodingStrategy) -> String {
        switch encodingStrategy {
        case .original:
            return stringValue
        case .snakeCase:
            let pattern = "([a-z0-9])([A-Z])"
            let regex = try! NSRegularExpression(pattern: pattern, options:
[])
            let range = NSRange(location: 0, length:
stringValue.characters.count)
            return regex.stringByReplacingMatches(in: stringValue, options:
[], range: range, withTemplate: "$1_$2").lowercased()
        case .custom(let t):
            return t(stringValue)
        }
    }
}

and then I replaced all references to key.stringValue with
key.stringValue(with: self.encoder.options.keyCodingStrategy)

This seems to work very nicely.

So my question is: Do anyone else see the benefit of such an addition to
the JSONEncoder and JSONDecoder?

The downside as I see it, is that the current CodingKeys are guaranteed to
be unique by the compiler, and it would be possible to create collisions by
using key name transforms.
Is this downside bigger than the gains?

One advantage is that one could argue that one CodingKey strategy may not
fit all serialization mechanisms. For instance one might wish to have upper
camel cased keys in Plists (just an example) and snake case in JSON. This
method could easily support this, while the current CodingKeys strategy
cannot...

Looking forward to hearing feedback.

Sincerely,
/morten

Hi Morten,

I’ve actually been working on this same idea already and will have something to propose soon.

- Tony

···

On Oct 19, 2017, at 2:03 AM, Morten Bek Ditlevsen via swift-evolution <swift-evolution@swift.org> wrote:

Hi all,
At work we have just added Codable support for a whole bunch of model objects in our code base.
Many places we have added CodingKeys enumeration in order to convert the camel cased property names to snake case for our JSON keys.
As an experiment I have tried adding a KeyCodingStrategy option to a copy of the JSONEncoder and JSONDecoder implementations.
This is currently an enumeration with the following values
.original
.snakeCase
.custom((String) -> String)

I just extended CodingKey as follows:
extension CodingKey {
    func stringValue(with encodingStrategy: StructuralEncoder.KeyEncodingStrategy) -> String {
        switch encodingStrategy {
        case .original:
            return stringValue
        case .snakeCase:
            let pattern = "([a-z0-9])([A-Z])"
            let regex = try! NSRegularExpression(pattern: pattern, options: [])
            let range = NSRange(location: 0, length: stringValue.characters.count)
            return regex.stringByReplacingMatches(in: stringValue, options: [], range: range, withTemplate: "$1_$2").lowercased()
        case .custom(let t):
            return t(stringValue)
        }
    }
}

and then I replaced all references to key.stringValue with key.stringValue(with: self.encoder.options.keyCodingStrategy)

This seems to work very nicely.

So my question is: Do anyone else see the benefit of such an addition to the JSONEncoder and JSONDecoder?

The downside as I see it, is that the current CodingKeys are guaranteed to be unique by the compiler, and it would be possible to create collisions by using key name transforms.
Is this downside bigger than the gains?

One advantage is that one could argue that one CodingKey strategy may not fit all serialization mechanisms. For instance one might wish to have upper camel cased keys in Plists (just an example) and snake case in JSON. This method could easily support this, while the current CodingKeys strategy cannot...

Looking forward to hearing feedback.

Sincerely,
/morten

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Yes, in general, I think Codable is a poor solution for json decoding just like I never used NSCoding to convert JSON to and from objects. It feels clumsy.

I found it a much better solution to add a category to NSObject that had

-(NSData*)toJSONRepresentationWithMappings:(NSDictionary*)d
+()fromJSONRepresentation:(NSData*) mappings:(NSDictionary*)d

where mappings might be { @"firstName": @"first_name", etc.... }

and was simple to write a general solution using introspection and KVC.

Codable is a limited one trick pony that would be trivial to write as a trait or extension if Swift provided the more profound thing - introspection and reflection. A whole world of opportunities would open up with that and we could stop wasting time on Codable and KeyPath - neither of which is that useful when working with string data from the wild.

Please stop messing about with these lil special cases and provide a general introspection and reflection capability. Until Swift has that, I might as well use C++.

···

On Oct 19, 2017, at 2:03 AM, Morten Bek Ditlevsen via swift-evolution <swift-evolution@swift.org> wrote:

Hi all,
At work we have just added Codable support for a whole bunch of model objects in our code base.
Many places we have added CodingKeys enumeration in order to convert the camel cased property names to snake case for our JSON keys.
As an experiment I have tried adding a KeyCodingStrategy option to a copy of the JSONEncoder and JSONDecoder implementations.
This is currently an enumeration with the following values
.original
.snakeCase
.custom((String) -> String)

I just extended CodingKey as follows:
extension CodingKey {
    func stringValue(with encodingStrategy: StructuralEncoder.KeyEncodingStrategy) -> String {
        switch encodingStrategy {
        case .original:
            return stringValue
        case .snakeCase:
            let pattern = "([a-z0-9])([A-Z])"
            let regex = try! NSRegularExpression(pattern: pattern, options: [])
            let range = NSRange(location: 0, length: stringValue.characters.count)
            return regex.stringByReplacingMatches(in: stringValue, options: [], range: range, withTemplate: "$1_$2").lowercased()
        case .custom(let t):
            return t(stringValue)
        }
    }
}

and then I replaced all references to key.stringValue with key.stringValue(with: self.encoder.options.keyCodingStrategy)

This seems to work very nicely.

So my question is: Do anyone else see the benefit of such an addition to the JSONEncoder and JSONDecoder?

The downside as I see it, is that the current CodingKeys are guaranteed to be unique by the compiler, and it would be possible to create collisions by using key name transforms.
Is this downside bigger than the gains?

One advantage is that one could argue that one CodingKey strategy may not fit all serialization mechanisms. For instance one might wish to have upper camel cased keys in Plists (just an example) and snake case in JSON. This method could easily support this, while the current CodingKeys strategy cannot...

Looking forward to hearing feedback.

Sincerely,
/morten

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Yes, in general, I think Codable is a poor solution for json decoding just like I never used NSCoding to convert JSON to and from objects. It feels clumsy.

I found it a much better solution to add a category to NSObject that had

-(NSData*)toJSONRepresentationWithMappings:(NSDictionary*)d
+()fromJSONRepresentation:(NSData*) mappings:(NSDictionary*)d

where mappings might be { @"firstName": @"first_name", etc.... }

and was simple to write a general solution using introspection and KVC.

Codable is a limited one trick pony that would be trivial to write as a trait or extension if Swift provided the more profound thing - introspection and reflection. A whole world of opportunities would open up with that and we could stop wasting time on Codable and KeyPath - neither of which is that useful when working with string data from the wild.

···

On Oct 19, 2017, at 8:09 AM, Tony Parker via swift-evolution <swift-evolution@swift.org> wrote:

Hi Morten,

I’ve actually been working on this same idea already and will have something to propose soon.

- Tony

On Oct 19, 2017, at 2:03 AM, Morten Bek Ditlevsen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi all,
At work we have just added Codable support for a whole bunch of model objects in our code base.
Many places we have added CodingKeys enumeration in order to convert the camel cased property names to snake case for our JSON keys.
As an experiment I have tried adding a KeyCodingStrategy option to a copy of the JSONEncoder and JSONDecoder implementations.
This is currently an enumeration with the following values
.original
.snakeCase
.custom((String) -> String)

I just extended CodingKey as follows:
extension CodingKey {
    func stringValue(with encodingStrategy: StructuralEncoder.KeyEncodingStrategy) -> String {
        switch encodingStrategy {
        case .original:
            return stringValue
        case .snakeCase:
            let pattern = "([a-z0-9])([A-Z])"
            let regex = try! NSRegularExpression(pattern: pattern, options: [])
            let range = NSRange(location: 0, length: stringValue.characters.count)
            return regex.stringByReplacingMatches(in: stringValue, options: [], range: range, withTemplate: "$1_$2").lowercased()
        case .custom(let t):
            return t(stringValue)
        }
    }
}

and then I replaced all references to key.stringValue with key.stringValue(with: self.encoder.options.keyCodingStrategy)

This seems to work very nicely.

So my question is: Do anyone else see the benefit of such an addition to the JSONEncoder and JSONDecoder?

The downside as I see it, is that the current CodingKeys are guaranteed to be unique by the compiler, and it would be possible to create collisions by using key name transforms.
Is this downside bigger than the gains?

One advantage is that one could argue that one CodingKey strategy may not fit all serialization mechanisms. For instance one might wish to have upper camel cased keys in Plists (just an example) and snake case in JSON. This method could easily support this, while the current CodingKeys strategy cannot...

Looking forward to hearing feedback.

Sincerely,
/morten

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi Tony,
Sounds really excellent. Looking forward to it.
Please let me know if there's anything regarding proposal or implementation
that I can help with.
/morten

···

On Thu, Oct 19, 2017 at 5:09 PM Tony Parker <anthony.parker@apple.com> wrote:

Hi Morten,

I’ve actually been working on this same idea already and will have
something to propose soon.

- Tony

On Oct 19, 2017, at 2:03 AM, Morten Bek Ditlevsen via swift-evolution < > swift-evolution@swift.org> wrote:

Hi all,
At work we have just added Codable support for a whole bunch of model
objects in our code base.
Many places we have added CodingKeys enumeration in order to convert the
camel cased property names to snake case for our JSON keys.
As an experiment I have tried adding a KeyCodingStrategy option to a copy
of the JSONEncoder and JSONDecoder implementations.
This is currently an enumeration with the following values
.original
.snakeCase
.custom((String) -> String)

I just extended CodingKey as follows:
extension CodingKey {
    func stringValue(with encodingStrategy:
StructuralEncoder.KeyEncodingStrategy) -> String {
        switch encodingStrategy {
        case .original:
            return stringValue
        case .snakeCase:
            let pattern = "([a-z0-9])([A-Z])"
            let regex = try! NSRegularExpression(pattern: pattern,
options: [])
            let range = NSRange(location: 0, length:
stringValue.characters.count)
            return regex.stringByReplacingMatches(in: stringValue,
options: [], range: range, withTemplate: "$1_$2").lowercased()
        case .custom(let t):
            return t(stringValue)
        }
    }
}

and then I replaced all references to key.stringValue with
key.stringValue(with: self.encoder.options.keyCodingStrategy)

This seems to work very nicely.

So my question is: Do anyone else see the benefit of such an addition to
the JSONEncoder and JSONDecoder?

The downside as I see it, is that the current CodingKeys are guaranteed to
be unique by the compiler, and it would be possible to create collisions by
using key name transforms.
Is this downside bigger than the gains?

One advantage is that one could argue that one CodingKey strategy may not
fit all serialization mechanisms. For instance one might wish to have upper
camel cased keys in Plists (just an example) and snake case in JSON. This
method could easily support this, while the current CodingKeys strategy
cannot...

Looking forward to hearing feedback.

Sincerely,
/morten

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution