Marshaling the typeless wild west of [String: Any]

Overview

License Carthage Platforms Build Status codebeat badge

Marshal

In Swift, we all deal with JSON, plists, and various forms of [String: Any]. Marshal believes you don't need a Ph.D. in monads or magic mirrors to deal with these in an expressive and type safe way. Marshal will help you write declarative, performant, error handled code using the power of Protocol Oriented Programming™.

Usage

Extracting values from [String: Any] using Marshal is as easy as

let name: String = try json.value(for: "name")
let url: URL = try json.value(for: "user.website") // extract from nested objects!

Converting to Models

Unmarshaling is the process of taking an intermediary data format (the marshaled object) and tranforming it into a local representation. Think of marshaling as serialization and unmarshaling as deserialization, or coding and decoding, respectively.

Often we want to take a marshaled object (like [String: Any]) and unmarshal it into one of our local models—for example we may want to take some JSON and initialize one of our local models with it:

struct User: Unmarshaling {
    var id: Int
    var name: String
    var email: String

    init(object: MarshaledObject) throws {
        id = try object.value(for: "id")
        name = try object.value(for: "name")
        email = try object.value(for: "email")
    }
}

Now, just by virtue of supplying a simple initializer you can pull your models directly out of [String: Any]!

let users: [User] = try json.value(for: "users")

That was easy! Thanks, Protocol Oriented Programming™!

Error Handling

Are you the shoot-from-the-hip type that doesn't care about errors? Use try? to give yourself an optional value. Otherwise, join us law-abiding folks and wrap your code in a do-catch to get all the juicy details when things go wrong.

Add Your Own Values

Out of the box, Marshal supports extracting native Swift types like String, Int, etc., as well as URL, anything conforming to Unmarshaling, and arrays of all the aforementioned types. It does not support extraction of more complex types such as Date due to the wide variety of date formats, etc.

However, Marshal doesn't just leave you up the creek without a paddle! Adding your own Marshal value type is as easy as extending your type with ValueType.

extension Date : ValueType {
    public static func value(from object: Any) throws -> Date {
        guard let dateString = object as? String else {
            throw MarshalError.typeMismatch(expected: String.self, actual: type(of: object))
        }
        // assuming you have a Date.fromISO8601String implemented...
        guard let date = Date.fromISO8601String(dateString) else {
            throw MarshalError.typeMismatch(expected: "ISO8601 date string", actual: dateString)
        }
        return date
    }
}

By simply implementing value(from:), Marshal allows you to immediately do this:

let birthDate: Date = json.value(for: "user.dob")

Protocol Oriented Programming™ strikes again!

Back to the Marshaled Object

We've looked at going from our [String: Any] into our local models, but what about the other way around?

extension User: Marshaling {
    func marshaled() -> [String: Any] {
        return {
            "id": id,
            "name" : name,
            "email": email
        }
    }
}

Now, you might be thinking "but couldn't I use reflection to do this for me automagically?" You could. And if you're into that, there are some other great frameworks for you to use. But Marshal believes mirrors can lead down the road to a world of hurt. Marshal lives in a world where What You See Is What You Get™, and you can easily adapt to APIs that snake case, camel case, or whatever case your backend developers are into. Marshal code is explicit and declarative. But don't just take Marshal's word for it—read the good word towards the end here on the official Swift blog.

Performance

Of course, Marshal wouldn't be the Marshal if it didn't have some of the fastest guns in the West. You should always take benchmarks with a grain of salt, but chew on these benchmarks for a bit anyway.

Contributors

Marshal began as a blog series on JSON parsing by Jason Larsen, but quickly evolved into a community project. A special thanks to the many people who have contributed at one point or another to varying degrees with ideas and code. A few of them, in alphabetical order:

  • Bart Whiteley
  • Brian Mullen
  • Derrick Hathaway
  • Dave DeLong
  • Jason Larsen
  • Mark Schultz
  • Nate Bird
  • Tim Shadel
Comments
  • convert core to protocol.

    convert core to protocol.

    This allows any object, that conforms to the protocol, the ability to extract values like a dictionary. The only thing that is required is for the object to implement the subscript method: subscript(key: KeyType)

    opened by brianmullen 42
  • Optionals in arrays

    Optionals in arrays

    Addresses https://github.com/utahiosmac/Marshal/issues/68 & https://github.com/utahiosmac/Marshal/issues/71

    The use case is when you have an array of objects but not all the objects successfully initialize. Instead of throwing out all the objects, only throw out the ones that didn't succeed.

    opened by jgrandelli 27
  • How to marshal array of arrays?

    How to marshal array of arrays?

    I'm trying to marshal a property like:

    var arrayOfArrayOfThings: [[Thing]]? 
    // ... later on ...
    arrayOfArray = try object.value(for: "arrayOfArrayOfThings") 
    

    Where Thing is a Unmarshaling. But I get a No 'value' candidates produce the expected contextual result type '[[Thing]]?'. I tried defining extensions to MarshableObject.value that return [[A]] but got stuck. It seems like for Array<Array<A>> Swift doesn't recognize the inner Array<A> type as a ValueType?

    bug 
    opened by jlyonsmith 14
  • Core Data and NSManagedObject

    Core Data and NSManagedObject

    Hi, I really like your approach with Marshal and I wanted to implement it in my project. However, I'm currently using core Data and my objects are NSManagedObjects.

    I'm trying to find a way to coexist Marshal and Core Data, but I haven't found any working solution yet. Did you already tried something similar?

    Thanks a lot

    question 
    opened by Tibb 8
  • Cocoapods (Actually pushing to Trunk)

    Cocoapods (Actually pushing to Trunk)

    Hi,

    I see a lot of issues around Cocoapods right now, and it stems from people not actually being able to access the podspec, without specifying the git url. In order to fix this then @psychoticidiot needs to run pod trunk push Marshal.podspec.

    This will release version 1.2.3 to cocoapods.org, and can then be found with a simple `pod 'Marshal', '~> 1.2'.

    Right now you have to do: pod 'Marshal', :git => 'https://github.com/utahiosmac/Marshal.git'.

    I hope this clarifies things. If you want, you can add me as a maintainer of the pod, and I'll make sure it gets updated on Trunk.

    opened by bjarkehs 6
  • Handling JSON where top level structure is an array not a dictionary

    Handling JSON where top level structure is an array not a dictionary

    Hello,

    Very interested in using Marshal however I'm not sure how best to handle JSON where the top level structure is an array rather than a dictionary.

    We have some JSON that looks like this:

     [
      {
      "id": "111",
      "firstname":"John",
      "surname": "Doe"
      }, {
      "id": "222",
      "firstname":"Jane",
      "surname": "Doe"
    }
    ]
    

    We would like to be able to map this directly into say an array of Users where User conforms to Unmarshaling i.e.

    struct User: Unmarshaling {
        var id: Int
        var firstname: String
        var surname: String
    
        init(object: MarshaledObject) throws {
            id = try object.value(for: "id")
            firstname = try object.value(for: "firstname")
            surname = try object.value(for: "surname")
        }
    }
    

    However the library seems to always need a key in order to create the unmarshaled object. The only way I can see to do this now is to have something like:

    var jsonObjects: [JSONObject] = try! JSONParser.JSONArrayWithData(data)
        for obj in jsonObjects {
             let user = User()
             user.id = try obj.value("id")
             user.firstname = try obj.value("firstname")
            user.surname = try obj.value("surname")
        }
    
    

    This does not seem as elegant as being able to create the array of users directly i.e.

    let users: [User] = try json.value("users")

    What is the best way of handling this kind of JSON format using Marshal?

    Thanks

    opened by tomasebrennan 5
  • Decode value with leading-dot key

    Decode value with leading-dot key

    How to get value for key ".tag" from this JSON:

    {
        ".tag": "test"
        "name": "something"
    }
    

    Marshal splits the key by ., so object.value(for: ".tag") returns nil because tag key is not found.

    help wanted 
    opened by dispatchqueue 5
  • Expected type Date for key...

    Expected type Date for key...

    Sometimes I get into a situation where Date parsing doesn't seem to work.

    This:

    self.createdAt = try json.value(for: "created_at")
    

    Throws this:

    Type mismatch. Expected type Date for key: created_at. Got '__NSCFString'
    

    I can work around this by transforming to a string first, then to Date:

    let s: String = try json.value(for: "created_at")
    self.createdAt = try Date.value(for: s)
    

    My Date support looks like so:

    extension Date {
        @nonobjc static let ISO8601SecondFormatter:DateFormatter = {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ";
            formatter.timeZone = TimeZone(abbreviation:"GMT")
            return formatter
        }()
        
        static func fromISO8601String(_ dateString:String) -> Date? {
            if let date = ISO8601SecondFormatter.date(from: dateString) {
                return date
            }
            return .none
        }
    }
    
    extension Date : ValueType {
        public static func value(_ object: Any) throws -> Date {
            guard let dateString = object as? String else {
                throw MarshalError.typeMismatch(expected: String.self, actual: type(of: object))
            }
            guard let date = Date.fromISO8601String(dateString) else {
                throw MarshalError.typeMismatch(expected: "ISO8601 date string", actual: dateString)
            }
            return date
        }
    }
    
    opened by pepasflo 5
  • Only return `nil` for the same key

    Only return `nil` for the same key

    This is needed in order to prevent errors further down the tree from being swallowed into the nil.

    Using stringValue for comparison, since KeyType doesn't conform to Equatable.

    opened by brentleyjones 5
  • Abiguous use of operator/function with enum

    Abiguous use of operator/function with enum

    Hi!

    I experienced a small problem when using enums as ValueTypes. I have an enum in a project defined as the following:

    enum Week: Int {
        case all = 0, even, odd
    }
    

    I implemented the ValueType extension as the following:

    extension Week: ValueType {
        public static func value(from object: Any) throws -> Week {
            guard let raw = object as? RawValue else {
                throw MarshalError.typeMismatch(expected: RawValue.self, actual: type(of: object))
            }
            guard let week = Week(rawValue: raw) else {
                throw Error.outOfBounds(raw)
            }
            return week
        }
    }
    

    When using this in a type that has a Week property, I get the compiler error 'ambiguous use of X' where X is either <| or value(for:).

    I fixed it by using this code in my type that is using Week, but the solution is not that satisfiying. Do you have any other ideas?

    let rawWeek: Int = try object <| "week"
    guard let week = Week(rawValue: rawWeek) else {
        throw Week.Error.outOfBounds(rawWeek)
    }
    self.week = week
    

    Thanks in advance!

    opened by BenchR267 4
  • Bug fix operator expansion

    Bug fix operator expansion

    Hey @jarsen -- this PR may fix #54. It also fixes a scary bug where I thought the function had a local variable any, but it resolved as the function as type Any, which then fails to cast into anything useful. Oops.

    I added some tests so this shouldn't regress.

    opened by KingOfBrian 4
  • Expected type MarshaledObject for key: node. Got 'Optional<Any>'

    Expected type MarshaledObject for key: node. Got 'Optional'

    I love Marshal and I'm a bit worried that maybe it's been abandoned, but hopefully you can help me with this:

    I've lately started running into this error: Expected type MarshaledObject for key: node. Got 'Optional<Any>'. I can cast the object to [String:Any], but not to [MarshaledObject].

    The failure is happening in Unmarshaling.swift:

    extension Unmarshaling {
        
        public static func value(from object: Any) throws -> ConvertibleType {
            guard let convertedObject = object as? MarshaledObject else {
                throw MarshalError.typeMismatch(expected: MarshaledObject.self, actual: type(of: object))
            }
            guard let value = try self.init(object: convertedObject) as? ConvertibleType else {
                throw MarshalError.typeMismatch(expected: ConvertibleType.self, actual: type(of: object))
            }
            return value
        }
        
    }
    

    If I print the type of the object in the debugger, it's:

    (lldb) po type(of: any)
    Swift.Optional<Any>
    

    In the debugger it looks like this:

    Screen Shot 2021-06-23 at 18 12 07

    Swift 5 in Xcode 13b, but also happens in 12.5.1

    Thanks!

    opened by JetForMe 1
  • Looking for example of how to marshal an object to json

    Looking for example of how to marshal an object to json

    Seems like the only examples I can find on Marshal are converting JSON into an object and not the other way. I'm assuming it's easy but perhaps I'm missing something.

    Can you post a test or example of how to go from a marshaled object to a string of json?

    opened by woodmicha 0
  • Marshaling Date?

    Marshaling Date?

    So, it's clear to me how to unmarshal a Date, by extending it with the ValueType protocol. But it's not at all clear how to marshal it. I tried various forms of this:

    extension
    Date : ValueType, Marshaling
    {
    	public
    	static
    	func
    	value(from inObj: Any)
    		throws
    		-> Date
    	{
    		guard
    			let timeInterval = inObj as? Double
    		else
    		{
    			throw MarshalError.typeMismatch(expected: Double.self, actual: type(of: inObj))
    		}
    		
    		let date = Date(timeIntervalSinceReferenceDate: timeInterval)
    		return date
    	}
    	
    	public
    	func
    	marshaled()
    		-> Double
    	{
    		return self.timeIntervalSinceReferenceDate
    	}
    }
    

    But Xcode doesn't like it. In this case, it says Type 'Date' does not conform to protocol 'Marshaling'. Not sure what I'm doing wrong.

    opened by JetForMe 0
  • Simplify Marshal using new Swift features

    Simplify Marshal using new Swift features

    Hi,

    I was wondering whether it would be possible to simplify Marshal implementation using new Swift features like conditional protocol conformance. This would also simplify creating 3rd party extensions above Marshal.

    This would e.g. allow [ValueType] to conform ValueType so lot of code could be then reused/deleted.

    I'm open to implementing it but don't wanna waste time on something that wouldn't have a chance to be merged.

    Thanks

    opened by olejnjak 1
  • SIGABRT when accessing dot separated keypath on non-object fileds

    SIGABRT when accessing dot separated keypath on non-object fileds

    This construction

    extension NSDictionary: MarshaledObject {
        public func any(for key: KeyType) throws -> Any {
            guard let value: Any = self.value(forKeyPath: key.stringValue) else {
                throw MarshalError.keyNotFound(key: key)
            }
    

    is not really safe to use in case when we try to access a field that is not an object. Say, we have this json:

    {
    	"contact_id": "123456",
    	"type": 0,
    	"avatar": "",
    	"org": {"name": "name"},
    }
    

    And this model:

    struct Test: Unmarshaling {
        let identifier: String
        let orgName: String?
        init(object: MarshaledObject) throws {
            self.identifier = try object.value(for: "contact_id")
            self.name = try object.value(for: "org.org_name")
        }
    }
    

    Sometimes, by accident, server may return this json:

    {
    	"contact_id": "123456",
    	"type": 0,
    	"avatar": "",
    	"org": "",
    }
    

    In that case I get SIGABRT: marshaldictionary swift 2018-10-26 20-45-39

    Right now I have to workaround it by implementing a private struct for org field and map to it first, but it would be better if the code above would just return nil without crashing

    opened by Sega-Zero 0
Releases(1.2.7)
Owner
Utah iOS and Mac Developers
Utah iOS and Mac Developers
JOP is the Json organization program. It can run on any platform that supports Swift.

JOP JOP is a program organized in JSON files. It is based on Swift, provides a syntax similar to Swift, and has the same strong security as Swift. Thi

Underthestars-zhy 1 Nov 18, 2021
A tiny SwiftUI app that displays Albums of Kanye West and a brief info about the artist.

West Day Ever It's a small SwiftUI app, that displays Albums of Kanye West and a brief info about the artist. Technical notes Low level packages that

Daniel Tombor 1 Apr 8, 2022
Notches Gone Wild!

Notchmeister Notches Gone Wild! Building First, clone the project. git clone https://github.com/chockenberry/Notchmeister.git You can locally override

Craig Hockenberry 114 Dec 23, 2022
Fashion Detection in the Wild (Deep Clothes Detector)

Deep Clothes Detector is a clothes detection framework based on Fast R-CNN. Given a fashion image, this software finds and localizes potential upper-body clothes, lower-body clothes and full-body clothes in it, respectively.

Ziwei Liu 451 Dec 17, 2022
StyledTextKit is a declarative attributed string library for fast rendering and easy string building.

StyledTextKit is a declarative attributed string library for fast rendering and easy string building. It serves as a simple replacement to NSAttribute

GitHawk 1.2k Dec 23, 2022
Swift String Validator. Simple lib for ios to validate string and UITextFields text for some criterias

Swift String validator About Library for easy and fastest string validation based on сciterias. Instalation KKStringValidator is available through Coc

Kostya 17 Dec 21, 2019
KIEM - Open Source Library, converting mistyped Korean string into English string

KIEM Open Source Library, converting misspelled Korean string into English strin

Samuel Kim 1 Feb 18, 2022
Util for executing shell commands, and getting the results easily(data, string, and any decodable).

ShellExecutor Util for executing shell commands, and getting the results easily(data, string, and any decodable). Requirements Xcode 14.0+ Swift 5.7+

Yozone Wang 2 Jul 30, 2022
Codeless drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView. Neither need to write any code nor any setup required and much more.

IQKeyboardManager While developing iOS apps, we often run into issues where the iPhone keyboard slides up and covers the UITextField/UITextView. IQKey

Mohd Iftekhar Qurashi 15.9k Jan 8, 2023
Use any custom view as custom callout view for MKMapView with cool animations. Use any image as annotation view.

MapViewPlus About MapViewPlus gives you the missing methods of MapKit which are: imageForAnnotation and calloutViewForAnnotationView delegate methods.

Okhan Okbay 162 Nov 16, 2022
A way to quickly add a notification badge icon to any view. Make any view of a full-fledged animated notification center.

BadgeHub A way to quickly add a notification badge icon to any view. Demo/Example For demo: $ pod try BadgeHub To run the example project, clone the r

Jogendra 772 Dec 28, 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
Automate box any value! Print log without any format control symbol! Change debug habit thoroughly!

LxDBAnything Automate box any value! Print log without any format control symbol! Change debug habit thoroughly! Installation You only need drag LxD

DeveloperLx 433 Sep 7, 2022
A way to quickly add a notification badge icon to any view. Make any view of a full-fledged animated notification center.

BadgeHub A way to quickly add a notification badge icon to any view. Demo/Example For demo: $ pod try BadgeHub To run the example project, clone the r

Jogendra 773 Dec 30, 2022
The Swift code generator for your assets, storyboards, Localizable.strings, … — Get rid of all String-based APIs!

SwiftGen SwiftGen is a tool to automatically generate Swift code for resources of your projects (like images, localised strings, etc), to make them ty

null 8.3k Dec 31, 2022
Convenience methods for creating color using RGBA hex string.

UIColor+Hex, now Swift. Convenience method for creating autoreleased color using RGBA hex string.

R0CKSTAR 1.2k Dec 23, 2022
NoOptionalInterpolation gets rid of "Optional(...)" and "nil" in Swift's string interpolation

NoOptionalInterpolation gets rid of "Optional(...)" and "nil" in Swift's string interpolation

Thanh Pham 48 Jun 5, 2022
BonMot is a Swift attributed string library

BonMot (pronounced Bon Mo, French for good word) is a Swift attributed string library. It abstracts away the complexities of the iOS, macOS, tvOS, and

Rightpoint 3.4k Dec 30, 2022
Croc is a swift emoji string parsing library

Croc is a library for parsing emojis on iOS. It provides a simple and lightweight interface for detecting, generating, categorizing and managing emoji characters, making emoji-powered features an easy task for developers.

Joe Kalash 127 Nov 20, 2022
Great Swift String Pluralize Extension

Pluralize.swift Great Swift String Pluralize Extension case-insensitive tons of rules for irregular nouns (plural form) supports uncountable nouns all

Joshua Arvin Lat 193 Nov 8, 2022