thoughtbot / argo Goto Github PK
View Code? Open in Web Editor NEWFunctional JSON parsing library for Swift
Home Page: https://thoughtbot.com
License: MIT License
Functional JSON parsing library for Swift
Home Page: https://thoughtbot.com
License: MIT License
Let's say the server returns a JSON array of photos. Each photo should have an ID, a URL, and maybe the name of the person who took it.
struct Photo {
let id: String
let authorName: String
let URL: NSURL
}
Now, we don't wanna show a photo in the app if info is missing. A photo without the author's name breaks things, so I don't wanna mark it as optional. However, in it's current form, the decode function would return nil if one photo out of 20 were missing the name.
How complicated would it be to make decode return the 19 valid photos and maybe have information somewhere to say "this one photo was invalid" instead of showing 0 photos in the app when 19 are totally fine to show?
When following the instructions to install Argo using Carthage it hangs on getting the runes package.
> carthage update
*** Cloning Argo
*** Cloning Box
*** Cloning runes
It will sit there (left it overnight) until ^C
Running with the carthage update --verbose flag gives no additional info and after retrying I get this instead:
> carthage update
*** Fetching Argo
*** Fetching Box
*** Fetching runes
I'm running Xcode 6.4 (6E14) command line tools but tried this with the 6.3 with the same result.
Hi,
I'm implementing for the first time a struct using Argo and I have a strange problem.
struct Shop {
let name: String
let displayName: String
let order: Int
}
extension Shop: JSONDecodable {
static func create(name: String)(displayName: String)(order: Int) -> Shop {
return Shop(name: name, displayName: displayName, order: order)
}
static func decode(j: JSONValue) -> Shop? {
return Shop.create
<*> j <| "name"
<*> j <| "displayName"
<^> j <| "order"
}
}
Tell me if I missed something, but I think everything is ok. The problem is that I have an error saying that 'JSONValue' has been renamed to JSON and because of that the extension is not conform to the protocol 'JSONDecodable'. If I look in the protocol, a JSONValue is required so I don't really understand ...
Because of the amount of overhead for this library, we really need to up our documentation game. We should add a Documentation directory with tutorials, examples, and explanations.
I just updated Argo to 1.0.0 and I'm seeing a regression:
I have a model with some optional values and when I try to parse a JSON containing a null
value (an NSNull
), I'm getting a TypeMismatch: "Null is not a Int" in runtime.
Here's the model:
public struct Identifiers {
public let trakt: Int
public let tvdb: Int?
public let imdb: String?
public let tmdb: Int?
public let tvrage: Int?
public let slug: String?
}
extension Identifiers: Decodable {
static func create(trakt: Int)(tvdb: Int?)(imdb: String?)(tmdb: Int?)(tvrage: Int?)(slug: String?) -> Identifiers {
return Identifiers(trakt: trakt, tvdb: tvdb, imdb: imdb, tmdb: tmdb, tvrage: tvrage, slug: slug)
}
public static func decode(j: JSON) -> Decoded<Identifiers> {
return Identifiers.create
<^> j <| "trakt"
<*> j <|? "tvdb"
<*> j <|? "imdb"
<*> j <|? "tmdb"
<*> j <|? "tvrage"
<*> j <|? "slug"
}
}
And the parse itself:
let json: NSDictionary = [
"trakt": 36440,
"tvdb": 3254641,
"imdb": "tt1480055",
"tmdb": 63056,
"tvrage": NSNull()
]
let value = JSON.parse(json)
let id = Identifiers.decode(value)
Any thoughts? I could remove the keys with NSNull
values from the dictionary, but I think Argo should handle it (as it was before 1.0.0).
A temporary solution:
I created a wrapper struct, wrap the array in a dictionary ["Objects": json] and called <||"Object" in the decoder
I can't find any examples of how to decode into an array/list of values. For instance, if I had a json array
[{"foo":"bar"}, {"foo":"bar"}]
and wanted to decode:
let foos: [Foo] = decode(j)
Apologies for the enormous code block - figure I should just show this in its entirety. I've seen previous issues related to this need to break up the object into multiple pieces to make the 'decode' function work properly. However, I also appear to be having an issue with the curried 'create' function - it seems to be giving up around "item3" due to too many parameters.
Some people have shown potential fixes by breaking up 'decode' like so but this is not compiling for me, either.
If I need to break up the object into multiple pieces, I'll have to. Though this can be pretty annoying, requiring more curried 'create' functions.
public struct RawStats {
let assists: Int
let barracksKilled: Int
let championsKilled: Int
let combatPlayerScore: Int
let consumablesPurchased: Int
let damageDealtPlayer: Int
let doubleKills: Int
let firstBlood: Int
let gold: Int
let goldEarned: Int
let goldSpent: Int
let item0: Int
let item1: Int
let item2: Int
let item3: Int
let item4: Int
let item5: Int
let item6: Int
let itemsPurchased: Int
let killingSprees: Int
let largestCriticalStrike: Int
let largestKillingSpree: Int
let largestMultiKill: Int
let legendaryItemsCreated: Int
let level: Int
let magicDamageDealtPlayer: Int
let magicDamageDealtToChampions: Int
let magicDamageTaken: Int
let minionsDenied: Int
let minionsKilled: Int
let neutralMinionsKilled: Int
let neutralMinionsKilledEnemyJungle: Int
let neutralMinionsKilledYourJungle: Int
let nexusKilled: Bool
let nodeCapture: Int
let nodeCaptureAssist: Int
let nodeNeutralize: Int
let nodeNeutralizeAssist: Int
let numDeaths: Int
let numItemsBought: Int
let objectivePlayerScore: Int
let pentaKills: Int
let physicalDamageDealtPlayer: Int
let physicalDamageDealtToChampions: Int
let physicalDamageTaken: Int
let playerPosition: Int
let playerRole: Int
let quadraKills: Int
let sightWardsBought: Int
let spell1Cast: Int
let spell2Cast: Int
let spell3Cast: Int
let spell4Cast: Int
let summonSpell1Cast: Int
let summonSpell2Cast: Int
let superMonsterKilled: Int
let team: Int
let teamObjective: Int
let timePlayed: Int
let totalDamageDealt: Int
let totalDamageDealtToChampions: Int
let totalDamageTaken: Int
let totalHeal: Int
let totalPlayerScore: Int
let totalScoreRank: Int
let totalTimeCrowdControlDealt: Int
let totalUnitsHealed: Int
let tripleKills: Int
let trueDamageDealtPlayer: Int
let trueDamageDealtToChampions: Int
let trueDamageTaken: Int
let turretsKilled: Int
let unrealKills: Int
let victoryPointTotal: Int
let visionWardsBought: Int
let wardKilled: Int
let wardPlaced: Int
let win : Bool
}
extension RawStats: JSONDecodable {
static func create(assists: Int)(barracksKilled: Int)(championsKilled: Int)(combatPlayerScore: Int)(consumablesPurchased: Int)(damageDealtPlayer: Int)(doubleKills: Int)(firstBlood: Int)(gold: Int)(goldEarned: Int)(goldSpent: Int)(item0: Int)(item1: Int)(item2: Int)(item3: Int)(item4: Int)(item5: Int)(item6: Int)(itemsPurchased: Int)(killingSprees: Int)(largestCriticalStrike: Int)(largestKillingSpree: Int)(largestMultiKill: Int)(legendaryItemsCreated: Int)(level: Int)(magicDamageDealtPlayer: Int)(magicDamageDealtToChampions: Int)(magicDamageTaken: Int)(minionsDenied: Int)(minionsKilled: Int)(neutralMinionsKilled: Int)(neutralMinionsKilledEnemyJungle: Int)(neutralMinionsKilledYourJungle: Int)(nexusKilled: Bool)(nodeCapture: Int)(nodeCaptureAssist: Int)(nodeNeutralize: Int)(nodeNeutralizeAssist: Int)(numDeaths: Int)(numItemsBought: Int)(objectivePlayerScore: Int)(pentaKills: Int)(physicalDamageDealtPlayer: Int)(physicalDamageDealtToChampions: Int)(physicalDamageTaken: Int)(playerPosition: Int)(playerRole: Int)(quadraKills: Int)(sightWardsBought: Int)(spell1Cast: Int)(spell2Cast: Int)(spell3Cast: Int)(spell4Cast: Int)(summonSpell1Cast: Int)(summonSpell2Cast: Int)(superMonsterKilled: Int)(team: Int)(teamObjective: Int)(timePlayed: Int)(totalDamageDealt: Int)(totalDamageDealtToChampions: Int)(totalDamageTaken: Int)(totalHeal: Int)(totalPlayerScore: Int)(totalScoreRank: Int)(totalTimeCrowdControlDealt: Int)(totalUnitsHealed: Int)(tripleKills: Int)(trueDamageDealtPlayer: Int)(trueDamageDealtToChampions: Int)(trueDamageTaken: Int)(turretsKilled: Int)(unrealKills: Int)(victoryPointTotal: Int)(visionWardsBought: Int)(wardKilled: Int)(wardPlaced: Int)(win: Bool) -> RawStats {
return RawStats(assists: assists, barracksKilled: barracksKilled, championsKilled: championsKilled, combatPlayerScore: combatPlayerScore, consumablesPurchased: consumablesPurchased, damageDealtPlayer: damageDealtPlayer, doubleKills: doubleKills, firstBlood: firstBlood, gold: gold, goldEarned: goldEarned, goldSpent: goldSpent, item0: item0, item1: item1, item2: item2, item3: item3, item4: item4, item5: item5, item6: item6, itemsPurchased: itemsPurchased, killingSprees: killingSprees, largestCriticalStrike: largestCriticalStrike, largestKillingSpree: largestKillingSpree, largestMultiKill: largestMultiKill, legendaryItemsCreated: legendaryItemsCreated, level: level, magicDamageDealtPlayer: magicDamageDealtPlayer, magicDamageDealtToChampions: magicDamageDealtToChampions, magicDamageTaken: magicDamageTaken, minionsDenied: minionsDenied, minionsKilled: minionsKilled, neutralMinionsKilled: neutralMinionsKilled, neutralMinionsKilledEnemyJungle: neutralMinionsKilledEnemyJungle, neutralMinionsKilledYourJungle: neutralMinionsKilledYourJungle, nexusKilled: nexusKilled, nodeCapture: nodeCapture, nodeCaptureAssist: nodeCaptureAssist, nodeNeutralize: nodeNeutralize, nodeNeutralizeAssist: nodeNeutralizeAssist, numDeaths: numDeaths, numItemsBought: numItemsBought, objectivePlayerScore: objectivePlayerScore, pentaKills: pentaKills, physicalDamageDealtPlayer: physicalDamageDealtPlayer, physicalDamageDealtToChampions: physicalDamageDealtToChampions, physicalDamageTaken: physicalDamageTaken, playerPosition: playerPosition, playerRole: playerRole, quadraKills: quadraKills, sightWardsBought: sightWardsBought, spell1Cast: spell1Cast, spell2Cast: spell2Cast, spell3Cast: spell3Cast, spell4Cast: spell4Cast, summonSpell1Cast: summonSpell1Cast, summonSpell2Cast: summonSpell2Cast, superMonsterKilled: superMonsterKilled, team: team, teamObjective: teamObjective, timePlayed: timePlayed, totalDamageDealt: totalDamageDealt, totalDamageDealtToChampions: totalDamageDealtToChampions, totalDamageTaken: totalDamageTaken, totalHeal: totalHeal, totalPlayerScore: totalPlayerScore, totalScoreRank: totalScoreRank, totalTimeCrowdControlDealt: totalTimeCrowdControlDealt, totalUnitsHealed: totalUnitsHealed, tripleKills: tripleKills, trueDamageDealtPlayer: trueDamageDealtPlayer, trueDamageDealtToChampions: trueDamageDealtToChampions, trueDamageTaken: trueDamageTaken, turretsKilled: turretsKilled, unrealKills: unrealKills, victoryPointTotal: victoryPointTotal, visionWardsBought: visionWardsBought, wardKilled: wardKilled, wardPlaced: wardPlaced, win: win)
}
public static func decode(j: JSON) -> Decoded<RawStats> {
return RawStats.create
<*> j <| "assists"
<*> j <| "barracksKilled"
<*> j <| "championsKilled"
<*> j <| "combatPlayerScore"
<*> j <| "consumablesPurchased"
<*> j <| "damageDealtPlayer"
<*> j <| "doubleKills"
<*> j <| "firstBlood"
<*> j <| "gold"
<*> j <| "goldEarned"
<*> j <| "goldSpent"
<*> j <| "item0"
<*> j <| "item1"
<*> j <| "item2"
<*> j <| "item3"
<*> j <| "item4"
<*> j <| "item5"
<*> j <| "item6"
<*> j <| "itemsPurchased"
<*> j <| "killingSprees"
<*> j <| "largestCriticalStrike"
<*> j <| "largestKillingSpree"
<*> j <| "largestMultiKill"
<*> j <| "legendaryItemsCreated"
<*> j <| "level"
<*> j <| "magicDamageDealtPlayer"
<*> j <| "magicDamageDealtToChampions"
<*> j <| "magicDamageTaken"
<*> j <| "minionsDenied"
<*> j <| "minionsKilled"
<*> j <| "neutralMinionsKilled"
<*> j <| "neutralMinionsKilledEnemyJungle"
<*> j <| "neutralMinionsKilledYourJungle"
<*> j <| "nexusKilled"
<*> j <| "nodeCapture"
<*> j <| "nodeCaptureAssist"
<*> j <| "nodeNeutralize"
<*> j <| "nodeNeutralizeAssist"
<*> j <| "numDeaths"
<*> j <| "numItemsBought"
<*> j <| "objectivePlayerScore"
<*> j <| "pentaKills"
<*> j <| "physicalDamageDealtPlayer"
<*> j <| "physicalDamageDealtToChampions"
<*> j <| "physicalDamageTaken"
<*> j <| "playerPosition"
<*> j <| "playerRole"
<*> j <| "quadraKills"
<*> j <| "sightWardsBought"
<*> j <| "spell1Cast"
<*> j <| "spell2Cast"
<*> j <| "spell3Cast"
<*> j <| "spell4Cast"
<*> j <| "summonSpell1Cast"
<*> j <| "summonSpell2Cast"
<*> j <| "superMonsterKilled"
<*> j <| "team"
<*> j <| "teamObjective"
<*> j <| "timePlayed"
<*> j <| "totalDamageDealt"
<*> j <| "totalDamageDealtToChampions"
<*> j <| "totalDamageTaken"
<*> j <| "totalHeal"
<*> j <| "totalPlayerScore"
<*> j <| "totalScoreRank"
<*> j <| "totalTimeCrowdControlDealt"
<*> j <| "totalUnitsHealed"
<*> j <| "tripleKills"
<*> j <| "trueDamageDealtPlayer"
<*> j <| "trueDamageDealtToChampions"
<*> j <| "trueDamageTaken"
<*> j <| "turretsKilled"
<*> j <| "unrealKills"
<*> j <| "victoryPointTotal"
<*> j <| "visionWardsBought"
<*> j <| "wardKilled"
<*> j <| "wardPlaced"
<*> j <| "win"
}
}
I'd love to be able to use Argo within Carthage, but there's currently only an iOS target. ๐ข
ie:first_name
in json to firstName
in swift.
Something like this, that doesn't come wrapped in a root Dictionary with a key to pull it out of...
[
{
"id": 12345,
"foo": "bar"
},
{
"id": 23456,
"foo": "bar"
},
{
"id": 34567,
"foo": "bar"
},
]
Hi... first time Argo user, so apologies if I'm misinterpreting things.
The example shown in Argo's readme.md has the following example:
static func decode(j: JSONValue) -> User? {
return User.create
<^> j <| "id"
<*> j <| "name"
<*> j <|? "email" // Use ? for parsing optional values
<*> j <| "role" // Custom types that also conform to JSONDecodable just work
<*> j <| ["company", "name"] // Parse nested objects
<*> j <|| "friends" // parse arrays of objects
}
However, JSONDecodable
's definition is as follows:
static func decode(json: JSON) -> Decoded<DecodedType>
I assume that the correct code is:
static func decode(j: JSON) -> Decoded<User>
Is that correct? I'd submit a pull request, except that as a novice, it is possible (likely?) that I have no clue what I'm doing ๐
The file structure of the project is a little messy. We should take some time and clean it up.
Hi,
I have a struct that has 16 fields. Is that too large for your library? Here It is:
extension ContractOverview {
static func create
(status: ContractOverviewStatusValue)
(key: ContractKey?)
(customerNumber: String?)
(customerName: String?)
(phoneNumber: String?)
(tariffName: String?)
(debitor: String?)
(endOfBinding: String?)
(prepaidBalance: PrepaidBalance?)
(consumption: [ConsumptionItem])
(cost: CostItem)
(pass: Pass?)
(hasOpenOrder: ContractOverviewHasOpenOrderValue)
(informationservice: Bool)
(invoiceType: ContractOverviewInvoiceType)
(amount : String?) -> ContractOverview {
return ContractOverview(status: status, key: key, customerNumber: customerNumber, customerName: customerName, phoneNumber: phoneNumber, tariffName: tariffName, debitor: debitor, endOfBinding: endOfBinding, prepaidBalance: prepaidBalance, consumption: consumption, cost: cost, pass: pass, hasOpenOrder: hasOpenOrder, informationservice: informationservice, invoiceType: invoiceType, amount: amount)
}
static func decode(j: JSONValue) -> ContractOverview? {
return ContractOverview.create
<^> j <| "status"
<*> j <|? "key"
<*> j <|? "customerNumber"
<*> j <|? "customerName"
<*> j <|? "phoneNumber"
<*> j <|? "tariffName"
<*> j <|? "Debitor"
<*> j <|? "endOfBinding"
<*> j <|? "prepaidBalance"
<*> j <| "consumption"
<*> j <| "currentCost"
<*> j <|? "pass"
<*> j <| "hasOpenOrder"
<*> j <| "informationservice"
<*> j <| "invoiceType"
<*> j <|? "amount"
}
}
Maybe I made a typo but I already looked at it for about an hour. help
This is the enum:
struct ContractOverview {
var status = ContractOverviewStatusValue.incomplete
var key : ContractKey?
var customerNumber : String?
var customerName : String?
var phoneNumber : String?
var tariffName : String?
var debitor : String?
var endOfBinding : String?
var prepaidBalance : PrepaidBalance?
var consumption = [ConsumptionItem]()
var cost: CostItem
var pass : Pass?
var hasOpenOrder = ContractOverviewHasOpenOrderValue.indeterminable
var informationservice = false
var invoiceType = ContractOverviewInvoiceType.unknown
var amount: String?
}
Iโm getting an error Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions
when using Argo to parse GitHubโs pull request JSON into my struct. I have the following decoder:
static var decoder: JSONValue -> PullRequest? {
return PullRequest.create
<^> <|"number"
<*> <|"state"
<*> <|"title"
<*> <||"labels"
<*> <|*"created_at"
<*> <|*"updated_at"
<*> <|*"merged_at"
<*> <|*"closed_at"
<*> <|["user", "login"]
<*> <|*"assignee"
<*> <|["repository", "owner", "login"]
<*> <|"comments"
<*> <|"html_url"
<*> <|*"statuses_url"
}
That is 14 properties for the pull request entity from GitHubโs API (which returns a ton more). Iโm currently using the code in #2 as master
would lead to the compiler and indexer more or less hanging (they did not but they never finished in reasonable time ^^).
This seems to be caused by a bug in swiftc where only 11 statements per expression works.
Do you have a suggestion for workaround of this? As it would be great to be able to have more than 11 properties ๐
Summary: The "Fastest [-O
]" optimization level used by a release build breaks Argo, with behavior ranging from incorrect to crashy.
I first noticed this issue when using Argo via Carthage. I linked my code to the Mac framework produced by carthage build
(which uses the Release configuration by default). I tried to parse the following JSON:
{
"from": "Alpha",
"to": "Omega"
}
The JSON object produced by NSJSONSerialization.JSONObjectWithData()
is correct:
{
from = Alpha;
to = Omega;
}
However, the output from JSONValue.parse(jsonObject)
was:
Object([to: Null, from: Null])
When using a Debug build of the framework (via carthage build --configuration Debug
) the output is correct:
Object([to: String(Omega), from: String(Alpha)])
Running Argo's own tests in Release configuration (by changing the Test configuration for the current scheme) leads to several issues. Most of the tests fail before a call to XCTAssertEqual()
brings the whole thing crashing to a halt with an EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subdued=0x0)
.
The cause of the issue seems to be the compiler's optimization. Swapping the SWIFT_OPTIMIZATION_LEVEL
setting between Fastest [-O
] and None [-Onone
] appears to toggle these issues.
Additional info: I'm building with Xcode 6.1.1 (6A2008a) on OS X 10.10.1 (14B25). I haven't tried this yet with the latest Xcode beta.
api that returns true
or false
should map to Boolean type in swift
When using the Carthage flag --use-submodules
and --no-build
in a project that depends on Argo, Argo's framework search paths do not exist because they point to the Carthage build directory. However, removing these breaks the build when not using those flags.
This issue can be a discussion on how to fix this.
@paulyoung Please add any info to this issue that I might have missed above.
I am unable to use Argo successfully in a new app using Carthage as my dependency manager.
https://github.com/czeluff/TryArgo
I have taken the standard Xcode new project and incorporated Argo by linking against Runes & Argo. In User.swift, I attempt to write the 'create' method for a User. I receive the error saying that it cannot find the '<*>' operator. If I attempt to 'import Runes' as well to the top of my file, Xcode builds infinitely.
Apologies if this is user error on my part. Based on the solution, I am happy to recommend new instructions for Argo + Carthage installation that make more sense.
For Example, what if I have this:
"thumbnails": {
"default": {
"url": "a"
},
"medium": {
"url": "b"
},
"high": {
"url": "c"
}
},
I can't define a struct with a property of default.
If JSONValue
conforms to JSONDecodable
, we can cut the number of operators we define in half in favor of using chained parsing operators.
j <| ["foo", "bar"]
would become j <| "foo" <| "bar"
This would also allow us to get rid of subscript
, as well as find
probably.
There might be performance implications here.
I don't know if it's seen as in the scope of this project, but wouldn't it be nice to have a JSONEncodable protocol as well? Something like this:
public protocol JSONEncodable {
func encode() -> JSONValue
}
With some smart use of the LiteralConvertible protocols the implementation could look something like this:
extension User: JSONEncodable {
func encode() -> JSONValue {
return ["id":id,"name":name,"email":email]
}
}
I've made an example of this in my fork of Argo: https://github.com/nvh/Argo/blob/master/Argo/Globals/JSONValue.swift#L95
Will this be something worth making a pull request from?
I think I just ran into another Swift Compiler isse, but want to report it here just to make sure.
// Inside `decode:`
let x = (j <| "valid_until") >>- parseDate
// ...
class func parseDate(dateString: String) -> NSDate? {
return NSDate()
}
Using Xcode 6.2 beta 4, the above code fails with
Command failed due to signal: Segmentation fault: 11
However, this works as expected
// Inside `decode:`
let x = parseDate(j <| "valid_until")
// ...
class func parseDate(dateString: String?) -> NSDate? { // note optional dateString here
return NSDate()
}
The REST API I'm using, uses the same structure to return paged results:
{
"count": 17,
"next": "http://52.16.199.142/api/products/12/?format=json&page=2",
"previous": null,
"results": [
{
}
]
}
I would ideally like to model this using generics like this:
public struct Page<T> {
public let count : Int
public let next : String?
public let previous : String?
public let results : [T]
}
I would not like to expose that T conforms to JSONDecodable in the declaration of the struct, but instead on the extension Page : JSONDecodable
.
However, no matter how I write this implementation, the compiler either segfaults or takes forever to fail. Has anyone accomplished something like this yet?
Is it possible to map a JSON key name to a value with Argo? For example, say I have the following objects I want to map JSON into...
struct Model {
...
let properties: [Property]
}
struct Property {
let label: String
let type: String
let required: Bool
}
...and the corresponding JSON...
...
"model": {
"identifier": {
"type": "Dental",
"version": "1.0"
},
"properties": {
"patient": {
"type": "string",
"required": true
},
"historyOfPresentIllness": {
"type": "string"
},
"medicalSurgicalHistory": {
"type": "string"
},
"physicalExam": {
"type": "string"
},
...
}
},
...
I know I can map the nested type
and required
values, but since the property keys are dynamic in the JSON (i.e., patient
, historyOfPresentIllness
, medicalSurgicalHistory
, etc, can potentially be any string and are referenced in a different part of the JSON) I want to map them to the label
property on the Property
object.
Hey,
can you give me please an example how I could parse a JSON when one of my struct fields is of an enum type? Lets take the following example:
struct Money {
let amount: String
let currecy: CurrencyValue
}
enum CurrencyValue : String {
case EUR = "EUR"
case unknown = "unknown"
}
The JSON keys are the same as the struct variable names amount and currency. I get the error
Type 'CurrencyValue' does not conform to protocol 'JSONDecodable'
How can I get rid of this? Can you give me an example how to make an enum conform to this protocol?
Aeson uses Value
for the name of its enum value. Maybe we should follow suit?
Hello all,
When parsing a huge 200KB JSON file, I'm seeing performance of an order of magnitude higher in Argo than when using Mantle in Objective C. Parsing the same JSON in Mantle+ObjC takes ~70ms and Argo+Swift instead take ~620ms on an iPhone 6 (measured with [XCTestCase measureBlock:]
)
Profiling the sample app, I can see that the vast majority of the CPU time in my code is in the JSON.parse
call:
Going deeper, I can see this:
I don't think there's a more efficient way of writing this parser, or is there anything you guys have in the roadmap that could make this more performant?
All the best!
Hey,
I use Argo for parsing a JSON with 16 different elements.
In that case Xcode compiler said that it's too complex to do it.
So I remove one argument in my parsing code to only parse 15 elements and in that case Xcode compile it very slowly.
See below my JSON parsing example:
extension Post: JSONDecodable {
class func create(post_id: Int)(title: String)(description: String)(author: String)(opinion: String?)(nb_love: Int)(nb_like: Int)(nb_support: Int)(nb_nocare: Int)(nbcomment : Int)(mediaURL_small: String)(mediaURL_medium: String)(mediaURL_big: String)(type_media: Int)(create_date: String)(update_date: String) -> Post {
return Post(post_id: post_id, title: title, description: description, author: author, opinion: opinion, nb_love: nb_love, nb_like: nb_like, nb_support: nb_support, nb_nocare: nb_nocare, nbcomment: nbcomment, mediaURL_small: mediaURL_small, mediaURL_medium: mediaURL_medium, mediaURL_big: mediaURL_big, type_media: type_media, create_date: create_date, update_date: update_date)
}
class func decode(j: JSONValue) -> Post? {
return Post.create
<^> j <| "post_id"
<*> j <| "title"
<*> j <| "description"
<*> j <| ["author", "username"]
<*> j <|? "opinion"
<*> j <| ["opinions", "love"]
<*> j <| ["opinions", "like"]
<*> j <| ["opinions", "support"]
<*> j <| ["opinions", "nocare"]
<*> j <| "nbcomments"
<*> j <| ["media", "url_small"]
<*> j <| ["media", "url_medium"]
<*> j <| ["media", "url_big"]
<*> j <| ["media", "type_id"]
<*> j <| "create"
<*> j <| "update"
}
}
I had this same problem with a big addition. Xcode refuse to compile because make 7 or more addition is to complex to compile on Swift (normal, it's functional language ;) ). The solution was to split the addition in 7 instructions.
So, I have done the same thing to my JSON parsing.
See below my example:
extension Post: JSONDecodable {
class func create(post_id: Int)(title: String)(description: String)(author: String)(opinion: String?)(nb_love: Int)(nb_like: Int)(nb_support: Int)(nb_nocare: Int)(nbcomment : Int)(mediaURL_small: String)(mediaURL_medium: String)(mediaURL_big: String)(type_media: Int)(create_date: String)(update_date: String) -> Post {
return Post(post_id: post_id, title: title, description: description, author: author, opinion: opinion, nb_love: nb_love, nb_like: nb_like, nb_support: nb_support, nb_nocare: nb_nocare, nbcomment: nbcomment, mediaURL_small: mediaURL_small, mediaURL_medium: mediaURL_medium, mediaURL_big: mediaURL_big, type_media: type_media, create_date: create_date, update_date: update_date)
}
class func decode(j: JSONValue) -> Post? {
return decode_media(j)
<*> j <| "create"
<*> j <| "update"
}
class func decode_media(j: JSONValue) -> ((create_date: String) -> (update_date: String) -> Post){
return (decode_comment(j)
<*> j <| ["media", "url_small"]
<*> j <| ["media", "url_medium"]
<*> j <| ["media", "url_big"]
<*> j <| ["media", "type_id"])!
}
class func decode_comment(j: JSONValue) -> ((mediaURL_small: String) -> (mediaURL_medium: String) -> (mediaURL_big: String) -> (type_media: Int) -> (create_date: String) -> (update_date: String) -> Post){
return (decode_init(j)
<*> j <|? "opinion"
<*> j <| ["opinions", "love"]
<*> j <| ["opinions", "like"]
<*> j <| ["opinions", "support"]
<*> j <| ["opinions", "nocare"]
<*> j <| "nbcomments")!
}
class func decode_init(j: JSONValue) -> ((opinion: String?) -> (nb_love: Int) -> (nb_like: Int) -> (nb_support: Int) -> (nb_nocare: Int) -> (nbcomment : Int) -> (mediaURL_small: String) -> (mediaURL_medium: String) -> (mediaURL_big: String) -> (type_media: Int) -> (create_date: String) -> (update_date: String) -> Post){
return (Post.create
<^> j <| "post_id"
<*> j <| "title"
<*> j <| "description"
<*> j <| ["author", "username"])!
}
}
Now with this little trick my sources compile faster and without any errors or warnings.
I don't know if this is the better solution for this problem but this trick resolve it.
If a better solution exist, I take it.
Antoine
CocoaPods doesn't know about the current version of Argo. A search for Argo reveals v0.2 as current.
Please update Argo on CocoaPods.
Installed via carthage, added Argo and Runes frameworks, etc. Build succeeds fine, but get this error when running the app (without using Argo in the code at all):
dyld: Symbol not found: _globalinit_33_06E7F1D906492AE070936A9B58CBAE1C_func2
Referenced from: /Users/ian/Library/Developer/CoreSimulator/Devices/7F24576E-DDA0-4DAC-95A7-F63A5DF54BD1/data/Containers/Bundle/Application/D42A6048-A58E-44CC-909F-F0C40EE06D14/Headquarters.app/Frameworks/Argo.framework/Argo
Expected in: /Users/ian/Library/Developer/CoreSimulator/Devices/7F24576E-DDA0-4DAC-95A7-F63A5DF54BD1/data/Containers/Bundle/Application/D42A6048-A58E-44CC-909F-F0C40EE06D14/Headquarters.app/Frameworks/libswiftCore.dylib
in /Users/ian/Library/Developer/CoreSimulator/Devices/7F24576E-DDA0-4DAC-95A7-F63A5DF54BD1/data/Containers/Bundle/Application/D42A6048-A58E-44CC-909F-F0C40EE06D14/Headquarters.app/Frameworks/Argo.framework/Argo
Aeson returns a Parser
type, not a Maybe
type. Generalizing away from Optional
might help us support other return formats in the future (like Result
)
I love Argo but am currently considering whether adding it and its dependencies is appropriate for a framework.
The README says this:
extension User: JSONDecodable {
static func decode(j: JSONValue) -> User? {
return User.create
<^> j <| "id"
<*> j <| "name"
}
}
For comparison, this same function without Argo would look like so:
extension User {
static func decode(j: NSDictionary) -> User? {
if let id = j["id"] as Int {
if let name = j["name"] as String {
return User(id: id, name: name)
}
}
return .None
}
}
Am I correct in thinking that a full comparison would be something like the following?
struct User {
let id: Int
let name: String
let email: String?
let role: Role
let companyName: String
let friends: [User]
let teamName: String?
let teamMembers: [User]?
}
extension User: JSONDecodable {
static func decode(j: JSONValue) -> User? {
return User.create
<^> j <| "id"
<*> j <| "name"
<*> j <|? "email"
<*> j <| "role"
<*> j <| ["company", "name"]
<*> j <|| "friends"
<*> j <|? ["team", "name"]
<*> j <||? ["team", "members"]
}
}
For comparison, this same function without Argo would look like so:
extension User {
static func decode(j: NSDictionary) -> User? {
if let id = j["id"] as? Int,
name = j["name"] as? String,
email = j["email"] as? String?
role = j["role"] as? Role,
companyName = j["company"]["name"] as? String,
friends = j["friends"] as? [User],
teamName = j["team"]["name"] as? String?,
teamMembers = j["team"]["members"] as? [User]? {
return User(
id: id,
name: name,
email: email,
role: role,
companyName: companyName,
friends: friends,
teamName: teamName,
teamMembers: teamMembers
)
}
return .None
}
}
Hi guys,
I'm trying to use Argo and convert a String to NSURL inside the decode
method. I thought that using flatMap (>>-
) was enough, but this way the compiler just hangs:
struct JSONParseUtils {
static func parseURL(URLString: String) -> NSURL? {
return NSURL(string: URLString)
}
}
public struct ImagesURLs {
public let fullImageURL: NSURL?
public let mediumImageURL: NSURL?
public let thumbImageURL: NSURL?
}
extension ImagesURLs: JSONDecodable {
static func create(fullImageURL: NSURL?)(mediumImageURL: NSURL?)(thumbImageURL: NSURL?) -> ImagesURLs {
return ImagesURLs(fullImageURL: fullImageURL, mediumImageURL: mediumImageURL, thumbImageURL: thumbImageURL)
}
public static func decode(j: JSONValue) -> ImagesURLs? {
return ImagesURLs.create
<^> j <|? "full" >>- JSONParseUtils.parseURL
<*> j <|? "medium" >>- JSONParseUtils.parseURL
<*> j <|? "thumb" >>- JSONParseUtils.parseURL
}
}
Changing parseURL
to receive an String?
(and adapting it) works, but once a field is nil
in a JSON, decode
returns nil
(it should continue evaluating).
NOTE: I solved this by creating an extension on NSURL
that conforms to JSONDecodable
, but sometimes this may not be possible. For example: I could want to create an NSDate
and have two possible formats (e.g. UNIX timestamp and ISO-8601).
Also, it'd be great to include this kind of example on README or some other place.
Howdy, first I wanted to say thanks so much for this library - we are getting a huge amount of use out of it.
We're hitting a third party API that is returning a root array much like how you have in your test suite. Currently, we are typealiasing the plural of the Model like so:
struct Person . . .
typealias People = [Person]
My question is, is there an easy to way to decode into this? We haven't been able to figure it out syntactically yet. For the Person
type, the syntax goes like this
if let j: AnyObject = body {
let value = JSONValue.parse(j)
let person = Person.decode(value)
callback(.Success(Box(person)))
}
We even tried giving up on the typealias People = [Person]
and used a simple struct w/ one data type and the JSONDecodable extension, but I just can't quite grasp how the global decode
function works - Swift 1.2
is most certainly not a fan, claiming it can't find it.
Thanks!
When I upgrade an Xcode 6.2 project to 6.3 beta, and update the Podfile to point Argo to GitHub, then I get the error about missing module Box.
In case it's helpful to know, I also have a Podfile entry for Runes, also updated to point to GitHub.
I don't see any definition for "Box" in the Argo source, nor in the Runes source.
Any ideas for a solution?
The line below in JSONDecodable.swift caused the error:
static func decode(JSONValue) -> DecodedType?
Error: Static methods are only allowed within structs and enums; use 'class' to declare a class method
changing static to class like the error said seems to solve the issue.
I have just released Argonaut, a framework that aims to accompany Argo with two main goals in mind:
JSONDecodeable
models, also support parsing models directly from ReactiveCocoa signals.NSURL
, etc.) to be directly used with Argo/Runes.I am not sure if this should be part of Argo itself, mainly because it is partly dependent on ReactiveCocoa, but I thought I put it out there just in case someone else might find it useful.
What do you think? Looking forward to get your feedback. โจ
Hi,
I would switch to Iโm trying to implement argo on an NSManagedObject subclass however when I implement JSONDecodable Xcode is unable to finish building.
Is this an issue with how I am trying to use Argo or something else?
Thanks
Is there any way Argo can be used to parse an array where different kind of structs are stored?
Example:
{
"result": [
{
"item": {
"itemId": 490538
}
"collection": {
"collectionId": 1101
}
}
]
}
Given off course struct Item
and struct Collection
that comply to JSONDecodable
protocol.
All the best and thanks for this amazing library!
Is there a recommended way to turn back into JSON?
Or would it be best to use NSJSONSerialization like so:
var params = ["id": id, "name":name, "email":email] as NSDictionary
var err: NSError?
var stringJSON = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: &err)
Hi, I've been using Argo in a new Swift project and am loving it so far - it is my first real foray into functional programming so pretty much everything is a learning curve :-)
I've managed to create my own model objects and can successfully parse my JSON except for one last thing - handling [String:String]
data. Imagine that my (simplified) model object looks something like this:
struct User {
let name: String
let age: Int
let metadata: [String:String]
}
My JSON would look like this:
{
"name" : "John Smith",
"age" : 42,
"metadata" : {
"field1" : "some value",
"otherField" : "foo bar"
}
}
The metadata
property is really just a collection of random name/value pairs. There could be none, or there could be 50 and I don't know ahead of time what the keys are (hence it is modelled as a [String:String]
)
Unfortunately, Decoded<[String:String]>
is not a supported return type for the <|
operator. At the moment, my half-baked solution is to create an object that represents this type of dictionary and then traverse over the JSON
values (see below). But it just doesn't feel right. Just curious if there is a better way? Any thoughts or guidance would be much appreciated. Thanks.
struct StringDictionary {
let values: [String:String]
init(_ values: [String:String]) {
self.values = values
}
}
extension StringDictionary : JSONDecodable {
static func decode(j: JSON) -> Decoded<StringDictionary> {
switch j {
case .Object(let s):
var values = [String:String]()
// TODO not sure how to traverse a list of un-named keys
return .Success(Box(StringDictionary(values)))
default: return .TypeMismatch("\(j) is not a valid [String:String] dictionary")
}
}
}
Hi again,
how can I parse a dictionary of JSON objects like that:
"creatures": {
"mammal": {
"name": "human",
"description": "description text"
},
"fish": {
"name": "shark",
"description": "attention: snappy"
}
}
The destination structure should be [CreatureTypeEnum : Creature]. Is that possible? Of course Creatures
and CreatureTypeEnum
conforms to JSONDecodable
too.
I was debating with myself if I should change the previous Optional-based implementation to use some kind of custom type with more info than just "it didn't work", but I thought y'all might be working on that anyway. Turns out you are! Upgraded today, things are much nicer now. Thanks! ๐
I have 2 stupid questions, using the comment.json
file from ArgoTests as sample JSON data:
{
"id": 6,
"text": "Cool story bro.",
"author": {
"id": 1,
"name": "Cool User"
}
}
// Import JSONFileReader from ArgoTests
let jsonData: AnyObject? = JSONFileReader.JSON(fromFile: "comment")
let jsonComment = JSONValue.parse <^> jsonData!
let j = jsonComment!
let id: Int = j["id"]?.value() // Error: Value of optional type 'Int?' not unwrapped"
let id: Int = j["id"]?.value()! // Same error message as above
let text: String = j["text"]?.value() // Ditto
let id: Int = j["id"]?.value() // Error: Value of optional type 'Int?' not unwrapped, did you mean to use ! or ?
I would like id = 6.j["author"]
as a Swift Dictionary<[String: JSONValue]>? So I can then iterate over the keys and use my knowledge from question 1, above, to extract the JSONValue's.I am using Swift 1.1 in Xcode 6.2 latest Beta (6C121). Any help will be greatly appreciated. Thank you
Using the example from the README
, how would one set up decoding of bestFriend
?
struct User {
let id: Int
let name: String
let email: String?
let role: Role
let companyName: String
let friends: [User]
let bestFriend: Box<User>
}
I'm using the Xcode 6.3-Beta and the latest Argo from master. When I'm running tests for example on iPhone 5 simulator it crashes on first attempt to map json property to model property. For example in
EmbeddedJSONDecodingTests.swift:
func testCommentDecodingWithEmbeddedUserName() {
let json = JSONValue.parse <^> JSONFileReader.JSON(fromFile: "comment")
self.json2 = json
let comment = self.json2 >>- Comment.decode
XCTAssert(comment != nil)
XCTAssert(comment?.id == "6")
XCTAssert(comment?.text == "Cool story bro.")
XCTAssert(comment?.authorName == "Cool User")
}
Comment.swift:
return Comment.create
<^> j <| "id" // Crashes on this line
<*> j <| "text"
<*> j <| ["author", "name"]
This happens also when running app which uses Argo on the simulator. When running tests on iPhone 6 simulator it does not crash.
Lets remove some boilerplate and give users a way to get a JSON
instance directly from the NSData
returned from their network client.
I used cocoapods to fetch Argo. It has a dependency on Runes in the JSONOperators file. But Runes doesn't have a Podspec so Argo won't compile..
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.