{"id":15038128,"url":"https://github.com/jhurray/ladybug","last_synced_at":"2025-08-25T20:15:23.649Z","repository":{"id":56919576,"uuid":"98942280","full_name":"jhurray/Ladybug","owner":"jhurray","description":"A powerful model framework for Swift 4 ","archived":false,"fork":false,"pushed_at":"2017-09-22T16:44:59.000Z","size":93,"stargazers_count":147,"open_issues_count":2,"forks_count":5,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-08-21T18:44:56.014Z","etag":null,"topics":["codable","json","json-objects","model","swift-4","swift4"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jhurray.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-01T00:38:39.000Z","updated_at":"2023-07-18T11:42:57.000Z","dependencies_parsed_at":"2022-08-21T04:50:31.456Z","dependency_job_id":null,"html_url":"https://github.com/jhurray/Ladybug","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/jhurray/Ladybug","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhurray%2FLadybug","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhurray%2FLadybug/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhurray%2FLadybug/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhurray%2FLadybug/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jhurray","download_url":"https://codeload.github.com/jhurray/Ladybug/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhurray%2FLadybug/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272124786,"owners_count":24877726,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-25T02:00:12.092Z","response_time":1107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codable","json","json-objects","model","swift-4","swift4"],"created_at":"2024-09-24T20:37:13.257Z","updated_at":"2025-08-25T20:15:23.622Z","avatar_url":"https://github.com/jhurray.png","language":"Swift","readme":"# Ladybug 🐞\n\nLadybug makes it easy to write a model or data-model layer in Swift 4. Full `Codable` conformance without the headache.\n\n![language](https://img.shields.io/badge/Language-Swift4-56A4D3.svg)\n![Version](https://img.shields.io/badge/Pod-%202.0.0%20-96281B.svg)\n![MIT License](https://img.shields.io/github/license/mashape/apistatus.svg)\n![Platform](https://img.shields.io/badge/platform-%20iOS|tvOS|macOS|watchOS%20-lightgrey.svg)\n\n### Quick Links\n* [Codable vs JSONCodable](#why-bug)\n* [Installation](#ingestion)\n* [Decoding](#decoding)\n* [Encoding](#encoding)\n* [Mapping JSON to properties](#json-to-property)\n  * [JSONKeyPath](#jsonkeypath) \n  * [Nested Objects](#nested-objects)\n  * [Dates](#dates)\n  * [Additional Mapping](#mapping)\n  * [Default Values / Migration](#default-values)\n* [Generic Constraints](#generics)\n* [Handling Failure](#failure)\n* [Class Conformance](#class-conformance)\n* [Musings 🤔](#musings)\n\n## `Codable` vs `JSONCodable`? \u003ca name=\"why-bug\"\u003e\u003c/a\u003e\n\nLadybug provides the `JSONCodable` protocol which is a subprotocol of [`Codable`](https://developer.apple.com/documentation/swift/codable). Lets compare how we would create an object using `Codable` vs. using `JSONCodable`.\n\nLets model a `Tree`. I want this object to be `Codable` so I can decode from JSON and encode to JSON.\n\nHere is some JSON:\n\n```json\n{\n    \"tree_names\": {\n        \"colloquial\": [\"pine\", \"big green\"],\n        \"scientific\": [\"piniferous scientificus\"]\n    },\n    \"age\": 121,\n    \"family\": 1,\n    \"planted_at\": \"7-4-1896\",\n    \"leaves\": [\n        {\n            \"size\": \"large\",\n            \"is_attached\": true\n        },\n        {\n            \"size\": \"small\",\n            \"is_attached\": false\n        }\n    ]\n}\n```\n\n## Using `Codable` 😱\n\n#### `Tree: Codable` Implementation\n\n```swift \nstruct Tree: Codable {\n    \n    enum Family: Int, Codable {\n        case deciduous, coniferous\n    }\n    \n    let name: String\n    let family: Family\n    let age: Int\n    let plantedAt: Date\n    let leaves: [Leaf]\n    \n    enum CodingKeys: String, CodingKey {\n        case names = \"tree_names\"\n        case family\n        case age\n        case plantedAt = \"planted_at\"\n        case leaves\n    }\n    \n    enum NameKeys: String, CodingKey {\n        case name = \"colloquial\"\n    }\n    \n    enum DecodingError: Error {\n        case emptyColloquialNames\n    }\n    \n    init(from decoder: Decoder) throws {\n        let values = try decoder.container(keyedBy: CodingKeys.self)\n        let namesContainer = try values.nestedContainer(keyedBy: NameKeys.self, forKey: .names)\n        let names = try namesContainer.decode([String].self, forKey: .name)\n        guard let firstColloquialName = names.first else {\n            throw DecodingError.emptyColloquialNames\n        }\n        name = firstColloquialName\n        family = try values.decode(Family.self, forKey: .family)\n        age = try values.decode(Int.self, forKey: .age)\n        plantedAt = try values.decode(Date.self, forKey: .plantedAt)\n        leaves = try values.decode([Leaf].self, forKey: .leaves)\n    }\n    \n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        var nameContainer = container.nestedContainer(keyedBy: NameKeys.self, forKey: .names)\n        let colloquialNames = [name]\n        try nameContainer.encode(colloquialNames, forKey: .name)\n        try container.encode(family, forKey: .family)\n        try container.encode(age, forKey: .age)\n        try container.encode(plantedAt, forKey: .plantedAt)\n        try container.encode(leaves, forKey: .leaves)\n    }\n    \n    struct Leaf: Codable {\n        \n        enum Size: String, Codable {\n            case small, medium, large\n        }\n        \n        let size: Size\n        let isAttached: Bool\n        \n        enum CodingKeys: String, CodingKey {\n            case isAttached = \"is_attached\"\n            case size\n        }\n    }\n}\n```\n\n[`Codable`](https://developer.apple.com/documentation/swift/codable) is a great step for Swift, but as you can see here, it can get [complicated](#why-not-codable) really fast. \n\n#### Decoding `Tree: Codable`\n\n```swift\nlet dateFormatter = DateFormatter()\ndateFormatter.dateFormat = \"MM-dd-yyyy\"\nlet decoder = JSONDecoder()\ndecoder.dateDecodingStrategy = .formatted(dateFormatter)\nlet tree = try decoder.decode(Tree_Codable.self, from: jsonData)\n```\n\n\n#### Encoding `Tree: Codable`\n\n```swift\nlet dateFormatter = DateFormatter()\ndateFormatter.dateFormat = \"MM-dd-yyyy\"\nlet encoder = JSONEncoder()\nencoder.dateEncodingStrategy = .formatted(dateFormatter)\nlet data = try encoder.encode(tree)\n```\n\n\n## 🐞 to the Rescue!\n\n By conforming to the `JSONCodable` protocol, you can skip all the boilerplate that comes with `Codable` while still getting `Codable` conformance.\n\n#### `Tree: JSONCodable` Implementation\n\n```swift\nstruct Tree: JSONCodable {\n    \n    enum Family: Int, Codable {\n        case deciduous, coniferous\n    }\n    \n    let name: String\n    let family: Family\n    let age: Int\n    let plantedAt: Date\n    let leaves: [Leaf]\n    \n    static let transformersByPropertyKey: [PropertyKey: JSONTransformer] = [\n        \"name\": JSONKeyPath(\"tree_names\", \"colloquial\", 0),\n        \"plantedAt\": \"planted_at\" \u003c- format(\"MM-dd-yyyy\"),\n        \"leaves\": [Leaf].transformer,\n    ]\n    \n    struct Leaf: JSONCodable {\n        \n        enum Size: String, Codable {\n            case small, medium, large\n        }\n        \n        let size: Size\n        let isAttached: Bool\n        \n        static let transformersByPropertyKey: [PropertyKey: JSONTransformer] = [\n            \"isAttached\": \"is_attached\"\n        ]\n    }\n}\n```\n\nAs you can see, you only need provide mappings for the JSON keys that don't explicitly map to property names.\n\n### Decoding `Tree: JSONCodable`\n\n```swift\nlet tree = try Tree(data: jsonData)\n```\n\n### Encoding `Tree: JSONCodable`\n\n```swift\nlet data = try tree.toData()\n```\n\nLadybug will save you time and energy when creating models in Swift by providing `Codable` conformance without the headache.\n\n## Installation \u003ca name=\"ingestion\"\u003e\u003c/a\u003e\n\n#### Cocoapods\n\nAdd the following to your `Podfile`\n\n```ruby\npod 'Ladybug', '~\u003e 2.0.0'\n```\n\n#### Carthage\n\nAdd the following to your `Cartfile`\n\n```ruby\ngithub \"jhurray/Ladybug\" ~\u003e 2.0.0\n```\n\n## Decoding \u003ca name=\"decoding\"\u003e\u003c/a\u003e\n\nYou can decode any object or array of objects conforming to `JSONCodable` from a JSON object, or `Data`. \n\n```swift\n/// Decode the given object from a JSON object\ninit(json: Any) throws\n/// Decode the given object from `Data`\ninit(data: Data) throws\n```\n\n**Example:**    \n\n```swift\nlet tree = try Tree(json: treeJSON)\nlet forest = try Array\u003cTree\u003e(json: [treeJSON, treeJSON, treeJSON])\n```\n\nBoth initializers will throw an error if decoding fails.\n\n## Encoding \u003ca name=\"encoding\"\u003e\u003c/a\u003e\n\nYou can encode any object or array of objects conforming to `JSONCodable` to a JSON object or to `Data` \n\n```swift\n/// Encode the object into a JSON object\nfunc toJSON() throws -\u003e Any\n/// Encode the object into Data\nfunc toData() throws -\u003e Data\n```\n\n**Example:**    \n\n```swift\nlet jsonObject = try tree.toJSON()\nlet jsonData = try forest.toData()\n```\n\nBoth functions will throw an error if encoding fails.\n\n## Mapping JSON Keys to Properties \u003ca name=\"json-to-property\"\u003e\u003c/a\u003e\n\nBy conforming to the `JSONCodable` protocol provided by Ladybug, you can initialize any `struct` or `final class` with `Data` or a JSON object. If your JSON structure diverges form your data model, you can override the static `transformersByPropertyKey` property to provide custom mapping.\n\n\n```swift\nstruct Flight: JSONCodable {\n    \n    enum Airline: String, Codable {\n        case delta, united, jetBlue, spirit, other\n    }\n    \n    let airline: Airline\n    let number: Int\n    \n    static let transformersByPropertyKey: [PropertyKey: JSONTransformer] = [\n    \t\"number\": JSONKeyPath(\"flight_number\")\n    ]\n}\n...\nlet flightJSON = [\n  \"airline\": \"united\",\n  \"flight_number\": 472,\n]\n...\nlet directFlight = try Flight(json: flightJSON)\nlet flightWithLayover = try Array\u003cFlight\u003e(json: [flightJSON, otherFlightJSON])\n\n```\n\n`directFlight ` and `flightWithLayover ` are fully initialized and can be encoded and decoded. Simple as that. \n\n**Note:** Any nested enum must conform to `Codable` and `RawRepresentable` where the `RawValue` is `Codable`.\n\n**Note:** `PropertyKey` is a `String` typealias.\n\nYou can associate JSON keys with properties via different objects conforming to `JSONTransformer`.\n\nTransformers are provided via a readonly `static` property of the `JSONCodable` protocol, and are indexed by `PropertyKey`.\n\n```swift\nstatic var transformersByPropertyKey: [PropertyKey: JSONTransformer] { get }\n```\n\nOk, it gets a little more complicated, but its easy, I swear. \n\n### Accessing JSON Values: `JSONKeyPath` \u003ca name=\"jsonkeypath\"\u003e\u003c/a\u003e\n\nIn the example at the beginning we used `JSONKeyPath` to map the value associated with the `tree_name` field to the `name` property of `Tree`.\n\n`JSONKeyPath` is used to access values in JSON. It is initialized with a variadic list of json subscripts (`Int` or `String`).\n\nIn the example below:\n\n* `JSONKeyPath(\"foo\")` maps to `{\"hello\": \"world\"}`\n* Similarly, `\"foo\"` maps to `{\"hello\": \"world\"}`\n* `JSONKeyPath(\"foo\", \"hello\")` maps to `\"world\"`\n* `JSONKeyPath(\"bar\", 0)` maps to `\"lorem\"`\n\n```json\n{\n  \"foo\": {\n     \"hello\": \"world\"\n  },\n  \"bar\": [ \n  \t \"lorem\",\n  \t \"ipsum\"\n  ]  \n}\n```\n\n**Note:** These key paths are used optionally in objects conforming to `JSONTransformer` when the property being mapped to does not match the json structure. If the property name is the same as the key path, you dont need to include the key path.\n\n**Note:** JSONKeyPath can also be expressed as a string literal.\n\u003e `JSONKeyPath(\"some_key\")` == `\"some_key\"`\n\n**Note:** You can also use Objective-C keypath notation.\n\u003e `JSONKeyPath(\"foo\", \"hello\")` == `JSONKeyPath(\"foo.hello\")` ==  `\"foo.hello\"`\n\nThis does not work for `Int` subscripts\n\u003e `JSONKeyPath(\"bar\", 1)` != `JSONKeyPath(\"bar.1\")`\n\n\n### Nested Objects \u003ca name=\"nested-objects\"\u003e\u003c/a\u003e\n\nLets add a nested class, `Passenger`. Flights have passengers. Nice.\n\nYou can denote a nested object via the static `transformer` property of any object or array of objects conforming to `JSONCodable`.\n\nYou can combine transformers using the `\u003c-` operator. In this case, for the `airMarshal` property, both the key path and the nested object need explicit transforms.\n\n```json\n{\n  \"airline\": \"united\",\n  \"flight_number\": 472,\n  \"air_marshal\" {\n     \"name\": \"50 Cent\",\n  },\n  \"passengers\": [\n  \t  {\n  \t  \"name\": \"Jennifer Lawrence\",\n  \t  },\n  \t  {\n  \t  \"name\": \"Chris Pratt\"\n  \t  },\n  \t  ... \n  ]\n}\n\n```\n\n```swift\nstruct Flight: JSONCodable {\n\t...\n    let passengers: [Passenger]\n    let airMarshal: Passenger\n    ...\n    static let transformersByPropertyKey: [PropertyKey: JSONTransformer] = [\n    \t...\n    \t\"passengers\": [Passenger].transformer,\n    \t\"airMarshal\": \"air_marshal\" \u003c- Passenger.transformer\n    ]\n    \n    struct Passenger: JSONCodable {\n        let name: String\n    }\n}\n```\n\n**Note:** When using the `\u003c-` operator, always put the `JSONKeyPath` transformer first.\n\n### Dates\n\nFinally, lets add dates to the mix. Ladybug provides multiple date transformers:\n\n* `secondsSince1970`: Decode the date as a UNIX timestamp from a JSON number.\n* `millisecondsSince1970`: Decode the date as UNIX millisecond timestamp from a JSON number.\n* `iso8601`: Decode the date as an ISO-8601-formatted string (in RFC 3339 format).\n* `format(_ format: String)`: Decode the date with a custom date format string.\n* `custom(_ adapter: @escaping (Any?) -\u003e Date?)`: Return a `Date` from the JSON value.\n\n```json\n{\n\"july4th\": \"7/4/1776\",\n\"y2k\": 946684800,\n}\n```\n\n```swift\nstruct SomeDates: JSONCodable {\n    let july4th: Date\n    let Y2K: Date\n    let createdAt: Date\n    \n    static let transformersByPropertyKey: [PropertyKey: JSONTransformer] = [\n        \"july4th\": format(\"MM/dd/yyyy\"),\n        \"Y2K\": \"y2k\" \u003c- secondsSince1970,\n        \"createdAt\": custom { _ in return Date() }\n    ]\n}\n```\n\n**Note:** If using `custom` to map to a non-optional `Date`, returning `nil` will result in an error being thrown during decoding. \n\n### Additional Mapping: `Map\u003cT: Codable\u003e` \u003ca name=\"mapping\"\u003e\u003c/a\u003e\n\nIf you need to provide a simple mapping from a JSON value to a property, use `MapTransformer`. A great example is using this to convert a string to an integer.\n\n```swift\n{\n\"count\": \"100\"\n}\n...\nstruct BottlesOfBeerOnTheWall: JSONCodable {\n    let count: Int\n    \n    static let transformersByPropertyKey: [PropertyKey: JSONTransformer] = [\n    \t\"count\": Map\u003cInt\u003e { return Int($0 as! String) }\n    ]\n}\n``` \n\n### Default Values / Migrations: `Default` \u003ca name=\"default-values\"\u003e\u003c/a\u003e\n\nIf you wish to supply a default value for a property, you can use `Default`. You supply the default value, and can also control whether or not to override the property if the property key exists in the JSON payload.\n\n```swift\ninit(value: Any, override: Bool = false)\n```\n\nThe default transformer is useful when API's change, and can help migration from cached JSON data to `JSONCodable` objects with new properties.\n\n## Using `JSONCodable` as a Generic Constraint \u003ca name=\"generics\"\u003e\u003c/a\u003e\n\nBecause `Array` does not explicitly conform to `JSONCodable`, `JSONCodable` does not support list types when used as a generic constraint. If you need this support, you can use the `List\u003cT: JSONCodable\u003e` wrapper type.\n\n```swift\nstruct Tweet: JSONCodable { ... }\nclass ContentProvider\u003cT: JSONCodable\u003e { ... }\n\nlet tweetDetailProvider = ContentProvider\u003cTweet\u003e()\nlet timelineProvider = ContentProvider\u003cList\u003cTweet\u003e\u003e()\n```\n\n## Handling Failure \u003ca name=\"failure\"\u003e\u003c/a\u003e\n\n### Errors\nLadybug is failure driven, and all `JSONCodable` initializers and encoding mechanisms throw errors if they fail. There is a `JSONCodableError` type that Ladybug will throw if there is a typecasting error, and Ladybug will also throw errors from `JSONSerialization`, `JSONDecoder`, and `JSONEncoder`.\n\n### Optionals\nIf a value is optional in your JSON payload, it should be optional in your data model. Ladybug will only throw an error if a key is missing and the property it is being mapped to is non-optional. Play it safe kids, use optionals.\n\n### Safety for `Map` and Custom Dates\n\nThere are 2 transformers that can return `nil` values: `Map\u003cT: Codable\u003e` and `custom(_ adapter: @escaping (Any?) -\u003e Date?)`.\n\nIf you are decoding from an already encoded `JSONCodable` object, returning `nil` is fine.\n\nIf you are decoding from a `URLResponse`, returning nil can lead to an error being thrown.\n\n## Class Conformance to `JSONCodable` \u003ca name=\"class-conformance\"\u003e\u003c/a\u003e\n\nThere are 2 small caveat to keep in mind when you are conforming a `class` to `JSONCodable`:\n\n1) Because classes in swift dont come with baked in default initializers like structs do, you have to make sure properties are initialized. You can do this by supplying default values, or a default initializer that initializes these values.\n\nYou can see examples in [`ClassConformanceTests.swift`](https://github.com/jhurray/Ladybug/blob/master/Tests/ClassConformanceTests.swift).\n\n2) Subclassing an object conforming to `Codable` will not work, so it won't work for `JSONCodable` either.\n\nBecause of these caveats, I would suggest using structs for your data models. \n\n## Thoughts About 🐞 \u003ca name=\"musings\"\u003e\u003c/a\u003e\n\n### It would be pretty great if `AnyKeyPath` could generate a string for its associated property\n\nIf Swift 4 key paths exposed a string value, we could use `PartialKeyPath\u003cSelf\u003e` as our `PropertyKey` typealias instead of `String`. This would be a much safer alternative.\n\n```swift\n typealias PropertyKey = PartialKeyPath\u003cSelf\u003e\n...\nstatic var transformersByPropertyKey: [PropertyKey: JSONTransformer] = [\n\t\\Tree.name: JSONKeyPath(\"tree_name\") \n]\n```\n\nThere was no disussion of this in [SE-0161](https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md).\n\n### It would also be great if `Mirror` could be created for types instead of instances\n\nThis would allow us to implicitly map nested objects conforming to `JSONCodable`.\n\n### Whats wrong with `Codable`? \u003ca name=\"why-not-codable\"\u003e\u003c/a\u003e\n\nAs mentioned before, `Codable` is a great step towards simplifying JSON parsing in swift, but the O(n) boilerplate that has become a mainstay in swift JSON parsing still exists when using `Codable` (e.g. For every property your object has, you need to write 1 or more lines of code to map the json to said property). In Apple's documentation on [Encoding and Decoding Custom Types](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types), you can see that as soon as JSON keys diverge from property keys, you have to write a ton of boilerplate code to get `Codable` conformance. Ladybug sidesteps this, and  does a lot of this for you under the hood.\n\n### Be careful with `MapTransformer`\n\nIts easy to go a little to far with `MapTransformer`. In the example below, the map transformer is being used to calculate a sum instead of mapping a JSON value to a `Codable` type. To me, this promotes bad data modeling. I'm a firm believer that data models should closely mirror JSON responses. When used in the wrong way, map transformers can give too data models too much responsibility.\n\n```swift\n{\n\"values\": [1, 1, 2, 3, 5, 8, 13]\n}\n... \nstruct FibonacciSequence: JSONCodable {\n\tlet values: [Int]\n\tlet sum: Int\n\t\n\tstatic let transformersByPropertyKey: [PropertyKey: JSONTransformer] = [\n\t\t\"sum\": MapTransformer\u003cInt\u003e(keyPath: \"values\") { value in\n\t\t\tlet values = value as! [Int]\n\t\t\treturn values.reduce(0) { $0 + $1 }\n\t\t}\n\t]\n}\n```\n\n## Credits\n\nShoutout to the good folks at [Mantle](https://github.com/Mantle/Mantle) for giving me some inspiration on this project. I'm pretty happy a similar framework is finally possible for Swift without mixing in Obj-C runtime.\n\n## Contact Info \u0026\u0026 Contributing\n\nFeel free to email me at [jhurray33@gmail.com](mailto:jhurray33@gmail.com) or [hit me up on the twitterverse](https://twitter.com/JeffHurray). I'd love to hear your thoughts on this, or see examples where this has been used.\n\n[MIT License](https://github.com/jhurray/Ladybug/blob/master/LICENSE)\n\n\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhurray%2Fladybug","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjhurray%2Fladybug","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhurray%2Fladybug/lists"}