matthewcheok / jsoncodable Goto Github PK
View Code? Open in Web Editor NEWHassle-free JSON encoding and decoding in Swift
License: MIT License
Hassle-free JSON encoding and decoding in Swift
License: MIT License
Is there any way to do this with the current release? I see that the JSONDecodable protocol declares a convenience init method for dictionaries, but what about for APIs that return an array as their root?
Hi @matthewcheok, @Nadohs.
I really love this framework. I have used it on several projects. I have a small issue where when building the project with Carthage xcodebuild
will throw some warnings:
carthage update
--- xcodebuild: WARNING: Unable to open project file '/Users/user/Development/Project/Carthage/Checkouts/JSONCodable/JSONCodable.playground' in workspace '/Users/user/Development/Project/Carthage/Checkouts/JSONCodable/JSONCodable.xcworkspace'.
--- xcodebuild: WARNING: Unable to open project file '/Users/user/Development/Project/Carthage/Checkouts/JSONCodable/JSONCodable.playground' in workspace '/Users/user/Development/Project/Carthage/Checkouts/JSONCodable/JSONCodable.xcworkspace'.
Just thought I'd let you know for future releases.
Thanks again!
The .JSONEncode() method currently encodes to a dictionary. It would be nice if you added a couple of helper functions that use NSJSONSerialization to convert the dictionary to a JSON string (and the other way around).
If you do so, I think that method should be called JSONEncode() and the original method should be called something like .toDictionary()
I have a String
enum in my struct, but encoding to JSON is failing with JSONEncodableError.ChildIncompatibleTypeError
.
It is possible to reproduce the same issue in the current test suite by commenting out toJSON()
implementation in the Fruit
struct.
The problem lies in the default implementation of JSONEncoder.create
in the switch (value)
because an enum does not conform to JSONEncodable.
Tried to fix it in several ways, but unfortunately all failed at the same spot - extension of protocol 'RawRepresentable' cannot have an inheritance clause
...
struct Foo {
let bar: Array<Int> = []
}
extension Foo: JSONEncodable {
func toJSON() throws -> AnyObject {
return try JSONEncoder.create({ (encoder) -> Void in
try encoder.encode(bar, key: "bar")
})
}
}
This should result in the following
{
"bar": []
}
and not
{
}
Corresponding to JSONEncodable L196
Given the following data structure, how would I extract values nested inside multiple keys?
{
"links": {
"some_link": "http://blah.com/",
"other_link": "http://derp.com/"
}
}
With other frameworks like Argo, I can pull out values from child keys using syntax like ["links", "some_link"]
. I couldn't find anything similar on the decode()
method.
The server returns an array of records, i.e [record1, record2].. I don't quite understand how to encode/decode an array of records. Could you give me a pointer?
I'm trying to use JSONCodable with swift classes (instead of structs) and cannot get it working.
class User {
var id: Int = 0
var name: String = ""
var email: String? = nil
var friends: [User] = []
var website: NSURL?
}
extension User: JSONCodable {
init?(JSONDictionary: [String : AnyObject]) {
do {
id = try JSONDictionary.decode("id")
name = try JSONDictionary.decode("full_name")
email = try JSONDictionary.decode("email")
friends = try JSONDictionary.decode("friends")
website = try JSONDictionary.decode("website", transformer: JSONTransformers.StringToNSURL)
}
catch {
print(error)
return nil
}
}
}
Swift complain "Designated initializer cannot be declared in an extension of User; did you mean this to be a convenience initializer?". If I make it convenience initializer, it would then complain about required. If I use required, it then complains more ...
Any help is greatly appreciated. (Sorry if this is a stupid question, I'm not familiar with Swift compiler...)
The Objective-C libraries I have used for JSON, such as JSONModel, support reflection when decoding. This is good because JSON requests are often simple, while the responses are complex. For example, a REST API to search for a user may accept a single user ID in the request but return a complex user model in the response.
Reflection (mirroring) is supported in JSONCodable for encoding but not decoding. The user ID can be encoded "for free" but the decoding of the user model requires many lines of code.
For example, you can do:
extension User: JSONEncodable {}
and the library will handle serialization automatically. However, the reverse doesn't work; you have to deserialize the JSON manually. For example:
extension User: JSONDecodable {
init?(JSONDictionary: JSONObject) {
let decoder = JSONDecoder(object: JSONDictionary)
do {
email = try decoder.decode("email")
name = try decoder.decode("name")
...
}
...
}
}
Is there a technical issue that prevents JSONCodable from auto-deserializing JSON using JSONDecodable? I wonder if it is particularly challenging for some reason. If not, I would like to help remove this limitation.
My workaround:
For example: CGAffineTransform
extension JSONEncoder {
func encode(value: CGAffineTransform, key: String) {
object[key] = NSValue(CGAffineTransform: value)
}
}
extension JSONDecoder {
func decode(key: String) throws -> CGAffineTransform {
guard let value = get(key) else {
throw JSONDecodableError.MissingTypeError(key: key)
}
guard let compatible = value as? NSValue else {
throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: NSValue.self)
}
guard let type = String.fromCString(compatible.objCType) where type.containsString("CGAffineTransform") else {
throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: CGAffineTransform.self)
}
return compatible.CGAffineTransformValue()
}
}
But the object
is internal and get()
is private, So I have to change the source code.Would you consider add these pattern in the source code? I can make a pull request. But I think maybe there is a more elegant solution.
The tag of each version should use proper Semantic Versioning, or it won't work with tools like Carthage, which expect valid version tags.
For example: the latest version is tagged 3.0
. This is not valid SemVer. It should be 3.0.0
.
Not sure if it's worth rewriting the current tags, but it's definitely something to keep in mind for the future.
Thanks
Hi,
It's probably easiest for me to explain my use case:
The application that I work on uses an API which sends down information that looks vaguely like this:
{
...
"car": {
"make": "Honda",
"model": "Accord"
}
}
The application itself holds separate caches with rich models referring to "Honda Accord". During parsing, I'd like to deserialize this model into a SimpleCar
model, and then perform a lookup in my cache for the richer model, throwing an error if the model is not found.
My feeling is that this should be done during parse time, so I want to be able to add this contextual information to the JSONDecoder
. In the current design JSONDecoder
is not extensible. What are your thoughts on:
JSONDecoder
a protocolBaseJSONDecoder
type implementing JSONDecoder
associatedtype
to JSONDecodable
protocol JSONDecodable {
associatedtype Decoder: JSONDecoder
...
}
I'm sketching this out and I don't see a particular reason why we couldn't do this. Another possibility is making JSONDecoder
open to subclassing, which I don't think it is (and object
is internal).
Hi , characters.count method is deprecated since xcode 9.1 please fix them and use just "count" .
When i try to update my project to swift 5.0. I cant build with carthage with error:
SWIFT_VERSION '3.0' is unsupported, supported versions are: 4.0, 4.2, 5.0. (in target 'JSONCodable iOS' from project 'JSONCodable')
Please update the library for support swift 5. Thank you very much!
If I had a enum (liberally stolen from the Apple docs) like this:
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
}
Then it can't be made to inherit from JSONDecodable according to the compiler, although it actually gives you a slightly weird error about failable init methods.
It seems that after pull request #53 JSONCodable got functionality for encoding/decoding nested types (9782c0f). However the problem we are having is that our API JSON responses have some keys with .
in them. This makes it impossible to encode (and probably parse) the JSONs using the library.
For example this JSON is not parseable correctly with the lib:
{
"one.key" = "value"
}
Is there a way to fallback to a more low-level approach avoiding the key.components(separatedBy: ".")
part in each of func encode(_ value: key:)
overload?
It seems like a simple bool conversion is not possible, or am I doing something wrong?
struct Book {
var like: Bool?
}
extension Book: JSONDecodable {
init(object: JSONObject) throws {
let decoder = JSONDecoder(object: object)
like = try decoder.decode("like")
}
}
where JSON is simply:
{"like": "false"}
With strings it returns me an optional with a string, with bool it returns nil
I have a structure called OTKSession
that conforms to JSONDecodable
, however using in it context of Array
within generics does not work and I get the following error:
Client.swift:128:19: In argument type '(Result<[OTKSession]>) -> Void', '[OTKSession]' does not conform to expected type 'JSONDecodable'
Example code:
func rest<ResponseType: JSONDecodable>(_ completionHandler: @escaping (Result<ResponseType>) -> Void) {
let value = ... // comes asynchronously
let jsonString = String(bytes: value.0, encoding: .utf8)!
let responseObject = try! ResponseType(JSONString: jsonString)
completionHandler(.ok(responseObject))
}
Do I have to handle arrays separately or it is possible to incorporate them within single function? It seems like there is no way to constraint generics to Array of JSONDecodable objects.
At the moment I've added overloaded method that wraps ResponseType in array. Two methods coexist just fine but it seems like a code duplication to me which probably can be solved in more elegant way.
func rest<ResponseType: JSONDecodable>(_ completionHandler: @escaping (Result<[ResponseType]>) -> Void) {
let value = ... // comes asynchronously
let jsonString = String(bytes: value.0, encoding: .utf8)!
let responseObject = try! [ResponseType](JSONString: jsonString)
completionHandler(.ok(responseObject))
}
Latest version on CocoaPods is 2.0.
Side note (in case this may help others):
Xcode crashes when adding a init(object: JSONObject) throws
to an extension. When using the 2.0 version of JSONCodable which has the failable initializer in the protocol.
In XCode 7.3 (swift 2.2), adding an initializer to the example class in the playground class to "Decode" breaks JSONDecodable. The same error happens when using the latest DEVELOPMENT-SNAPSHOT-2016-03-01-a version through command line from GitHub.
class Company {
let name: String
var address: String?
init(n: String, a: String?) {
name = n
address = a
}
}
extension Company: JSONDecodable {
init(object : JSONObject) throws {
let decoder = JSONDecoder(object: object)
name = try decoder.decode("name")
address = try decoder.decode("address")
}
}
with two (incompatible) errors at the init(object:) in the extension:
A. Designated initializer cannot be declared in an extension of 'Company'; did you mean this to be a convenience initializer?
As such:
extension Company: JSONDecodable {
convenience init(object : JSONObject) throws {
let decoder = JSONDecoder(object: object)
name = try decoder.decode("name")
address = try decoder.decode("address")
}
}
B. Initializer requirement 'init(object:)' can only be satisfied by a required
initializer in the definition of non-final class 'Company'
It'd be great if this library would be available through CocoaPods! I see there's a podspec
file already, so it would be simple, right?
Note: For now, anyone who wants to use it you can use the following in your Podfile
:
pod 'JSONCodable', :git => 'https://github.com/matthewcheok/JSONCodable.git'
Hi,
Right now the definition for JSONDecodable
requires an implementation of an initializer. This has a couple of drawbacks IMO:
JSONDecodable
(see #29)JSONDecodable
has to be conformed to in the main class declaration, not in an extension. This means that classes defined in ObjC can't conform to JSONDecodable
in a Swift extension, for example.My question is: what are opinions on a breaking change to moving to a definition of JSONDecodable
that looks like this:
protocol JSONDecodable {
static func parse(decoder: JSONDecoder) throws -> Self
}
I know that this would be a breaking change, but are there other reasons why this is a bad idea?
Hi Cocoapods repo version is still 2.1, would you like to update the version to Cocoapods?
In JSONDecodable Line 197 an empty array gets return. Why is there no MissingTypeError thrown. This pattern can be found in more places.
It does not make sense to return an empty array if the key is unavailable.
I think the readme does not go in depth enough into some of the features of JSONCodable..
Such as: for Decoding:
init(object: JSONObject) throws {
....
let decoder = JSONDecoder(object: object)
likes = try decoder.decode("properties[0].likes")
....
}
Basically explaining that this feature exists, decoding for JSON using "." and "[index]" accessors to navigate dictionaries and arrays.
There is a travis config but no Travis setup. Travis makes contributing easier by running tests during a pull request.
The protocol extension for JSONDecodable
with the failable initializer init?(JSONString: String)
produce the following error message:
... Cannot find an initializer for type 'UserState' that accepts an argument list of type '(JSONString: String)' ...
To reproduce this I added the following code at the end of the Playground
do {
let jsonString = try user.JSONString()
if let user_the_same = User(JSONString: jsonString) {
print("Same User: \n\(user_the_same)")
}
} catch {
print("Error: \(error)")
}
To fix this I could add the complete failable initializer init?(JSONString: String)
to the User
structure declaration, but this is not how it should work.
Do you have any Idea?
Perhaps I'm missing something, but it doesn't seem like multi-dimensional arrays are supported with the JSONEncodable protocol. For example, this test fails:
class TwoDimensionalArrayClass: JSONEncodable {
var array2D: [[String]]?
}
class JSONCodableTests: XCTestCase {
func testTwoDimensionalArray() {
let twoDim = TwoDimensionalArrayClass()
twoDim.array2D = [["One", "Two", "Three"], ["Four", "Five", "Six"], ["Seven", "Eight", "Nine"]]
do {
try twoDim.toJSON()
}
catch {
print(error)
XCTFail()
}
}
}
Any help or suggestion would be greatly appreciated.
[
{
"ID": 568,
"av": 125435865,
"ad": "2016-06-07",
"ar": 0,
"at": 0,
"ah": 0,
"aj": "te"
}
]
struct Messages {
let id: [[String:AnyObject]]?
}
Hi there,
could we have a new release with the latest fixes/additions please? :)
thx!
Preface: I've looked at a whole bunch of different JSON libraries for a talk I gave last week on JSON parsing in Swift. The talk was partly about different libraries, and partly about the JsonGen code generator I've build. Although I didn't mention JSONCodable in the talk, it is in my opinion one of the best JSON libraries I've looked at.
One of the very few features I miss in JSONCodable is verbose error messages. In JsonGen I try to include as much information as possible about errors in the JSON. So it's easier to debug problems in JSON coming from the server.
These two features in particular:
From my post on error messages, these are the types of error messages JsonGen generates:
2 errors in Blog struct
- subTitle: Value is not of expected type String?: `42`
▿ posts: 2 errors in array
▿ [1] 1 error in Post struct
- title: Field missing
▿ [2] 3 errors in Post struct
- title: Value is not of expected type String: `1`
- body: Value is not of expected type String: `42`
▿ author: 2 errors in Author struct
- name: Field missing
- email: Field missing
Feel free to close this issue if this isn't something you're interested in for JSONCodable. It's just something I wanted to bring to your attention.
This fails to compile:
let val: JSONEncodable = SomeTypeThatImplementsJSONEncodable()
public func toJSON() throws -> AnyObject {
return try JSONEncoder.create { encoder in
try encoder.encode(val, key: "SomeKey")
}
}
The error given is "Cannot invoke 'encode' with an argument list of type '(JSONEncodable, key: String)'." This seems to be because the following method on JSONEncoder is marked private:
private func encode(value: JSONEncodable, key: String) throws
Is there some reason this shouldn't be marked public?
Hi,
Is there a limitation to not include these 2 options? Also, the iOS and OSX one are defaulting to "latests SDK release, which is a problem sometimes"
Hi Matthew,
excuse me if I am wrong (I am fairly new to Swift), but it seems to me that this line is a problem in JSONString(line 19):
let json = try toJSON() as! AnyObject
I understand that you use it later for NSJSONSerialization, but toJSON returns Any which cannot be casted toAnyObject.
Is it a bug of the Codable library or do you have any workaround in mind?
Thanks,
Anatoli
For example:
struct model {
let areas: [[Float]]
}
extension model: JSONDecodable {
init(object: JSONObject) {
do {
let decoder = JSONDecoder(object: object)
areas = try decoder.decode("areas")
// Compiler error: Ambiguous reference to member 'decode'
}catch{
fatalError("\(error)")
}
}
}
Hi Matthew,
It would be great to have support for enums as well in JSONTransformer.
Great work!
Most of the times the default implementation of toJSON() is great, so you don't need to implement that function. Anyway if you want to change the name of just one property, you need to implement toJSON() for all the properties. It would be nice to add a method that it will ask for mapping some properties name, otherwise will use the default behavior.
example:
My object has a oneProperty
property. And the server wants a one_property
property. I'd like a method like:
func mapPropertyName(name: String) -> String? {
if name == "oneProperty" {
return "one_property"
} else {
return nil // use default implementation
}
}
Using XCode 7.2, I'm getting Command failed due to signal: Segmentation fault: 11
error when extension part of following code is uncommented.
struct Status {
let version: Int
let host: String
}
extension Status: JSONDecodable {
func toJSON() throws -> AnyObject {
return try JSONEncoder.create({ (encoder) -> Void in
try encoder.encode(version, key: "version")
try encoder.encode(host, key: "host")
})
}
}
Based on StackOverflow comments, this fault is due to some yet to be clearly identified NSString related function signature issues and only workaround is to use another framework that doesn't have the code.
I have this code for os x simple app, but it does not work:
import Cocoa
struct GeoFolderData {
var nomeCartella:String
var nomeCommittente:String?
var dataCreazioneCartella:NSDate
var dataModificaCartella:NSDate
var isActive:Bool
}
extension GeoFolderData: JSONDecodable {
init(object: JSONObject) throws {
let decoder = JSONDecoder(object: object)
nomeCartella = try decoder.decode("Nome_Cartella")
nomeCommittente = try decoder.decode("Nome_Committente")
dataCreazioneCartella = try decoder.decode(key: "dataCreazioneCartella")
dataModificaCartella = try decoder.decode(key: "dataModificaCartella")
isActive = try decoder.decode("Is_Active")
}
}
extension GeoFolderData: JSONEncodable {
func toJSON() throws -> Any {
return try JSONEncoder.create({ (encoder) -> Void in
try encoder.encode(nomeCartella, key: "Nome_Cartella")
try encoder.encode(nomeCommittente, key: "Nome_Committente")
try encoder.encode(dataCreazioneCartella, key: "Data_Creazione_Cartella")
try encoder.encode(dataModificaCartella, key: "Data_Modifica_Cartella")
try encoder.encode(isActive, key: "Is_Active")
})
}
}
extension JSONDecoder {
public func decode(key: String) throws -> NSDate {
return try decode(String, transformer: JSONTransformer<StringToNSDate, StringToNSDate>)
}
}
Is there anyone who can help me?
Thank you.
JSONDecoder
is now a part of Foundation on iOS 11 and there is no way to use JSONDecoder
anymore because there are now two of them. I have a code that extends both JSONDecoder
and JSONEncoder
and both throw an error on Xcode 9 beta:
'JSONDecoder' is ambiguous for type lookup in this context
It seems like JSONCodable
will have to prefix its classes.
Hi @matthewcheok,
Kudos for the library! I have just one question. Why is the initializer init?(JSONDictionary: JSONObject)
a failable initializer rather than a throwing one? Sometime one might want the handle the error from the outside...
Thanks!
Hi,
First of all, thanks for sharing this amazing library.
I'm using some structs with UInt64 and that caused a compilation error indicating references to ambiguous decode functions.
My workaround was to extend JSONDecoder but this does not work too. Compilation works fine but it throws error while decoding.
private struct StorePromotionItem {
let banner: String
let icon: String
var saleOptionId: UInt64?
var productOfferId: UInt64?
}
extension StorePromotionItem: JSONDecodable {
init(object: JSONObject) throws {
let decoder = JSONDecoder(object: object)
banner = try decoder.decode("banner")
icon = try decoder.decode("icon")
saleOptionId = try decoder.decode("sale_option_id")
productOfferId = try decoder.decode("product_offer_id")
}
}
extension JSONDecoder {
public func decode(key: String) throws -> UInt64 {
let JSONTransformerStringToUInt64 = JSONTransformer<String, UInt64>(
decoding: {
UInt64($0)
},
encoding: {
String($0)
})
return try decode(key, transformer: JSONTransformerStringToUInt64)
}
}
Thanks,
Line 57 in the file JSONDecodable will not compile anymore since it is not valid to have a non-failable initializer requirement be satisfied by a failable one.
Great library by the way !
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.