Hassle-free JSON encoding and decoding in Swift

Related tags

JSON JSONCodable
Overview

Logo

Platform Language CocoaPods Carthage Travis CI License

#JSONCodable Hassle-free JSON encoding and decoding in Swift

Installation

  • Simply add the following to your Cartfile and run carthage update:
github "matthewcheok/JSONCodable" ~> 3.0.1
  • or add the following to your Podfile and run pod install:
pod 'JSONCodable', '~> 3.0.1'
  • or clone as a git submodule,

  • or just copy files in the JSONCodable folder into your project.

TLDR

  • Uses Protocol Extensions
  • Error Handling
  • Supports let properties
  • Supports enum properties backed by compatible values

Change Log

  • Moved encoding and decoding methods to a helper class

JSONCodable is made of two separate protocols JSONEncodable and JSONDecodable.

  • JSONEncodable allows your structs and classes to generate NSDictionary or [String: AnyObject] equivalents for use with NSJSONSerialization.

  • JSONDecodable allows you to generate structs from NSDictionary coming in from a network request for example.

##Decoding JSON Take these two types for example:

 struct User {    
     let id: Int    
     let name: String   
     var email: String?   
     var company: Company?    
     var friends: [User] = []   
 }    
    
 struct Company {   
     let name: String   
     var address: String?   
 }    

You'd simply add conformance to JSONDecodable (or to JSONCodable):

extension User: JSONDecodable {
  init(object: JSONObject) throws {
      let decoder = JSONDecoder(object: object)        
      id = try decoder.decode("id")
      name = try decoder.decode("full_name")
      email = try decoder.decode("email")
      company = try decoder.decode("company")
      friends = try decoder.decode("friends")
  }
}

extension Company: JSONDecodable {
  init(object: JSONObject) throws {
      let decoder = JSONDecoder(object: object)
      name = try decoder.decode("name")
      address = try decoder.decode("address")
  }
}

Note on Class Extensions: After the update to Swift 2.2 adding an initializer in an extension for classes is no longer supported. The current suggested work around for this is to just add the initializer in the class definition. For structs extensions still work as that had previously in this case.

Then provide the implementations for init(object: JSONObject) throws where JSONObject is a typealias for [String:AnyObject]. As before, you can use this to configure the mapping between keys in the Dictionary to properties in your structs and classes.

let user = try User(object: JSON)
print("\(user)")

Result:

User(
  id: 24,
  name: "John Appleseed",
  email: Optional("[email protected]"),
  company: Optional(Company(
    name: "Apple",
    address: Optional("1 Infinite Loop, Cupertino, CA")
  )),
  friends: [
    User(
      id: 27,
      name: "Bob Jefferson",
      email: nil,
      company: nil,
      friends: []
    ),
    User(
      id: 29,
      name: "Jen Jackson",
      email: nil,
      company: nil,
      friends: []
    )
  ]
)

Decoding Nested Arrays and Dictionary

Decoding also supports retrieving values using . separators for dictionaries and [index] for arrays. See below example:

name = try decoder.decode("value[0].properties.name")

Encoding JSON

Simply add conformance to JSONEncodable (or to JSONCodable):

extension User: JSONEncodable {
    func toJSON() throws -> Any {
        return try JSONEncoder.create({ (encoder) -> Void in
            try encoder.encode(id, key: "id")
            try encoder.encode(name, key: "full_name")
            try encoder.encode(email, key: "email")
            try encoder.encode(company, key: "company")
            try encoder.encode(friends, key: "friends")
        })
    }
}

extension Company: JSONEncodable {}

The default implementation of func toJSON() inspects the properties of your type using reflection (see Company.) If you need a different mapping, you can provide your own implementation (see User.)

Instantiate your struct, then use the func toJSON() method to obtain a equivalent form suitable for use with NSJSONSerialization:

let dict = try user.toJSON()
print("dict: \(dict)")

Result:

[full_name: John Appleseed, id: 24, email: john@appleseed.com, company: {
    address = "1 Infinite Loop, Cupertino, CA";
    name = Apple;
}, friends: (
        {
        friends =         (
        );
        "full_name" = "Bob Jefferson";
        id = 27;
    },
        {
        friends =         (
        );
        "full_name" = "Jen Jackson";
        id = 29;
    }
)]

Working with JSON Strings

The convenience initializer init?(JSONString: String) is provided on JSONDecodable. You may also use func toJSONString() throws -> String to obtain a string equivalent of your types.

Transforming values

To transform values, create an instance of JSONTransformer:

let JSONTransformerStringToNSURL = JSONTransformer<String, NSURL>(
        decoding: {NSURL(string: $0)},
        encoding: {$0.absoluteString})

A JSONTransformer converts between 2 types, in this case, String and NSURL. It takes a closure for decoding and another for encoding, and in each case, you return an optional value of the corresponding type given an input type (you can return nil if a transformation is not possible).

Next, use the overloaded versions of func encode() and func decode() to supply the transformer:

struct User {
  ...
  var website: NSURL?
}

init(object: JSONObject) throws {
    ...
    website = try JSONDictionary.decode("website", transformer: JSONTransformerStringToNSURL)
}

func toJSON() throws -> AnyObject {
    return try JSONEncoder.create({ (encoder) -> Void in
        ...
        try result.encode(website, key: "website", transformer: JSONTransformerStringToNSURL)
    })
}

The following transformers are provided by default:

  • JSONTransformers.StringToNSURL: String <-> NSURL
  • JSONTransformers.StringToNSDate: String <-> NSDate ISO format

Feel free to suggest more!

Extending JSONCodable (thanks to @raylillywhite)

This allows for JSONDecoder extensions that allow the type system to better aid in decoding. For example, you could do:

extension JSONDecoder {
    public func decode(key: String) throws -> NSURL {
        return try decode(key, transformer: JSONTransformers.StringToNSURL)
    }
}

then you only need to do:

try url = decoder.decode("url")

instead of

try url = decoder.decode("url", JSONTransformers.StringToNSURL)

Example code

Refer to the included playground in the workspace for more details.

License

JSONCodable is under the MIT license.

Comments
  • Support System Struct Serialiaztion (CGRext, CGPoint, etc)

    Support System Struct Serialiaztion (CGRext, CGPoint, etc)

    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.

    opened by 100mango 8
  • Decode JSONString to Structure does not work.

    Decode JSONString to Structure does not work.

    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?

    opened by Christian1313 6
  • Better readability fortransformers

    Better readability fortransformers

    Changes default transformers to static function to make use of Swift type inference

    date = try decoder.decode("date", transformer: JSONTransformers.StringToNSDate)
    

    becomes

    date = try decoder.decode("date", transformer: .stringToDate())
    

    This improves readability while extending this pattern is easy. Just create an extension on JSONTransformer for every custom transformer.

    opened by lightsprint09 5
  • ambiguous decoder for type UInt64

    ambiguous decoder for type UInt64

    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,

    opened by vitorhugomagalhaes 5
  • `JSONDecodable` with static `parse` method?

    `JSONDecodable` with static `parse` method?

    Hi, Right now the definition for JSONDecodable requires an implementation of an initializer. This has a couple of drawbacks IMO:

    • Enums with associated values can't conform to JSONDecodable (see #29)
    • For classes, 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?

    opened by bejar37 4
  • Swift 2.2 compilation problem.

    Swift 2.2 compilation problem.

    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.

    https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID224

    Great library by the way !

    opened by tomergafner 4
  • Cross platform target + Travis CI

    Cross platform target + Travis CI

    • Removes all platform specific targets
    • Creates a single target which supports iOS, macOS, tvOS, watchOS
    • Adds Travis CI configuration
      • Travis currently runs at https://travis-ci.org/lightsprint09/JSONCodable
      • You have to create your own Travis CI instance when this is merged

    Read this and this for more information on multiplatform schemes

    opened by lightsprint09 3
  • Decode array of array fail

    Decode array of array fail

    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)")
            }
        }
    
    }
    
    opened by 100mango 3
  • Make `get` public in JSONDecoder

    Make `get` public in JSONDecoder

    This allows for JSONDecoder extensions that allow the type system to better aid in decoding. For example, you could do:

    extension JSONDecoder {
        public func decode(key: String) throws -> NSURL {
            return decode(key, JSONTransformers.StringToNSURL)
        }
    }
    

    then you only need to do:

    try url = decoder.decode("url")
    

    instead of

    try url = decoder.decode("url", JSONTransformers.StringToNSURL)
    

    And with the proper extensions setup, you never need to specify how to decode something at the call site unless there are multiple ways to decode a given type. As an example of a transformer that isn't built-in that we'd like this functionality for, we have a custom ID type that is convertible to/from a string, but don't want to have to manually specify the transformer each time we're decoding an ID.

    opened by raylillywhite 3
  • init?(JSONDictionary: JSONObject) should throw

    init?(JSONDictionary: JSONObject) should throw

    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!

    opened by srdanrasic 3
  • How to decode a root-level JSON array?

    How to decode a root-level JSON array?

    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?

    opened by tonyarnold 3
  • Swift 5 support.

    Swift 5 support.

    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!

    opened by HungNMMobile 0
  • remove JSONCodable protocol

    remove JSONCodable protocol

    I've bumped into a problem while using xcode9. Error I was getting was:

    'JSONDecoder' is ambiguous for type lookup in this context

    The only way to fix this is to put class in proper namespace by add prefix JSONCodable like JSONCodable.JSONDecoder, but there is a problem that compiler want to use protocol called the same, not lib name

    the only idea I've figure out is to remove this protocol

    opened by emilwojtaszek 2
  • JSONDecoder is ambiguous for type lookup

    JSONDecoder is ambiguous for type lookup

    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.

    opened by pronebird 2
  • Unable to call toJSON on multi-dimensional arrays

    Unable to call toJSON on multi-dimensional arrays

    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.

    opened by fontduroy 0
Releases(3.0.1)
Owner
Matthew Cheok
Matthew Cheok
A protocol to serialize Swift structs and classes for encoding and decoding.

Serpent (previously known as Serializable) is a framework made for creating model objects or structs that can be easily serialized and deserialized fr

Nodes - iOS 287 Nov 11, 2022
Implement dynamic JSON decoding within the constraints of Swift's sound type system by working on top of Swift's Codable implementations.

DynamicCodableKit DynamicCodableKit helps you to implement dynamic JSON decoding within the constraints of Swift's sound type system by working on top

SwiftyLab 15 Oct 16, 2022
Himotoki (紐解き) is a type-safe JSON decoding library written purely in Swift.

Himotoki Himotoki (紐解き) is a type-safe JSON decoding library written purely in Swift. This library is highly inspired by the popular Swift JSON parsin

IKEDA Sho 799 Dec 6, 2022
Nikolai Saganenko 1 Jan 9, 2022
Swift parser for JSON Feed — a new format similar to RSS and Atom but in JSON.

JSONFeed Swift parser for JSON Feed — a new format similar to RSS and Atom but in JSON. For more information about this new feed format visit: https:/

Toto Tvalavadze 31 Nov 22, 2021
JSEN (JSON Swift Enum Notation) is a lightweight enum representation of a JSON, written in Swift.

JSEN /ˈdʒeɪsən/ JAY-sən JSEN (JSON Swift Enum Notation) is a lightweight enum representation of a JSON, written in Swift. A JSON, as defined in the EC

Roger Oba 8 Nov 22, 2022
Swift-json - High-performance json parsing in swift

json 0.1.4 swift-json is a pure-Swift JSON parsing library designed for high-per

kelvin 43 Dec 15, 2022
JSON-Practice - JSON Practice With Swift

JSON Practice Vista creada con: Programmatic + AutoLayout Breve explicación de l

Vanesa Giselle Korbenfeld 0 Oct 29, 2021
Ss-json - High-performance json parsing in swift

json 0.1.1 swift-json is a pure-Swift JSON parsing library designed for high-per

kelvin 43 Dec 15, 2022
JSONNeverDie - Auto reflection tool from JSON to Model, user friendly JSON encoder / decoder, aims to never die

JSONNeverDie is an auto reflection tool from JSON to Model, a user friendly JSON encoder / decoder, aims to never die. Also JSONNeverDie is a very important part of Pitaya.

John Lui 454 Oct 30, 2022
Reflection based (Dictionary, CKRecord, NSManagedObject, Realm, JSON and XML) object mapping with extensions for Alamofire and Moya with RxSwift or ReactiveSwift

EVReflection General information At this moment the master branch is tested with Swift 4.2 and 5.0 beta If you want to continue using EVReflection in

Edwin Vermeer 964 Dec 14, 2022
A fast, convenient and nonintrusive conversion framework between JSON and model. Your model class doesn't need to extend any base class. You don't need to modify any model file.

MJExtension A fast, convenient and nonintrusive conversion framework between JSON and model. 转换速度快、使用简单方便的字典转模型框架 ?? ✍??Release Notes: more details Co

M了个J 8.5k Jan 3, 2023
Elevate is a JSON parsing framework that leverages Swift to make parsing simple, reliable and composable

Elevate is a JSON parsing framework that leverages Swift to make parsing simple, reliable and composable. Elevate should no longer be used for

Nike Inc. 611 Oct 23, 2022
HandyJSON is a framework written in Swift which to make converting model objects to and from JSON easy on iOS.

HandyJSON To deal with crash on iOS 14 beta4 please try version 5.0.3-beta HandyJSON is a framework written in Swift which to make converting model ob

Alibaba 4.1k Dec 29, 2022
ObjectMapper is a framework written in Swift that makes it easy for you to convert your model objects to and from JSON.

ObjectMapper is a framework written in Swift that makes it easy for you to convert your model objects (classes and structs) to and from J

Tristan Himmelman 9k Jan 2, 2023
YamlSwift - Load YAML and JSON documents using Swift

YamlSwift parses a string of YAML document(s) (or a JSON document) and returns a Yaml enum value representing that string.

Behrang Norouzinia 384 Nov 11, 2022
Swift/Obj-C HTTP framework with a focus on REST and JSON

Now Archived and Forked PMHTTP will not be maintained in this repository going forward. Please use, create issues on, and make PRs to the fork of PHMT

Postmates Inc. 509 Sep 4, 2022
Jay - Pure-Swift JSON parser & formatter. Fully streamable input and output. Linux & OS X ready.

Pure-Swift JSON parser & formatter. Fully streamable input and output. Linux & OS X ready. Replacement for NSJSONSerialization.

Danielle 132 Dec 5, 2021
Codable code is a Swift Package that allows you to convert JSON Strings into Swift structs

Codable code is a Swift Package that allows you to convert JSON Strings into Swift structs.

Julio Cesar Guzman Villanueva 2 Oct 6, 2022