";
126 | };
127 | OBJ_7 /* Sources */ = {
128 | isa = PBXGroup;
129 | children = (
130 | OBJ_8 /* JSONFeed */,
131 | );
132 | name = Sources;
133 | sourceTree = SOURCE_ROOT;
134 | };
135 | OBJ_8 /* JSONFeed */ = {
136 | isa = PBXGroup;
137 | children = (
138 | OBJ_9 /* Attachment.swift */,
139 | OBJ_10 /* Author.swift */,
140 | OBJ_11 /* Feed.swift */,
141 | OBJ_12 /* Hub.swift */,
142 | OBJ_13 /* Item.swift */,
143 | );
144 | name = JSONFeed;
145 | path = Sources/JSONFeed;
146 | sourceTree = SOURCE_ROOT;
147 | };
148 | /* End PBXGroup section */
149 |
150 | /* Begin PBXNativeTarget section */
151 | "JSONFeed::JSONFeed" /* JSONFeed */ = {
152 | isa = PBXNativeTarget;
153 | buildConfigurationList = OBJ_23 /* Build configuration list for PBXNativeTarget "JSONFeed" */;
154 | buildPhases = (
155 | OBJ_26 /* Sources */,
156 | OBJ_32 /* Frameworks */,
157 | );
158 | buildRules = (
159 | );
160 | dependencies = (
161 | );
162 | name = JSONFeed;
163 | productName = JSONFeed;
164 | productReference = "JSONFeed::JSONFeed::Product" /* JSONFeed.framework */;
165 | productType = "com.apple.product-type.framework";
166 | };
167 | "JSONFeed::JSONFeedTests" /* JSONFeedTests */ = {
168 | isa = PBXNativeTarget;
169 | buildConfigurationList = OBJ_45 /* Build configuration list for PBXNativeTarget "JSONFeedTests" */;
170 | buildPhases = (
171 | OBJ_48 /* Sources */,
172 | OBJ_51 /* Frameworks */,
173 | F803FE8A21DD28430044D780 /* Resources */,
174 | );
175 | buildRules = (
176 | );
177 | dependencies = (
178 | OBJ_53 /* PBXTargetDependency */,
179 | );
180 | name = JSONFeedTests;
181 | productName = JSONFeedTests;
182 | productReference = "JSONFeed::JSONFeedTests::Product" /* JSONFeedTests.xctest */;
183 | productType = "com.apple.product-type.bundle.unit-test";
184 | };
185 | "JSONFeed::SwiftPMPackageDescription" /* JSONFeedPackageDescription */ = {
186 | isa = PBXNativeTarget;
187 | buildConfigurationList = OBJ_34 /* Build configuration list for PBXNativeTarget "JSONFeedPackageDescription" */;
188 | buildPhases = (
189 | OBJ_37 /* Sources */,
190 | );
191 | buildRules = (
192 | );
193 | dependencies = (
194 | );
195 | name = JSONFeedPackageDescription;
196 | productName = JSONFeedPackageDescription;
197 | productType = "com.apple.product-type.framework";
198 | };
199 | /* End PBXNativeTarget section */
200 |
201 | /* Begin PBXProject section */
202 | OBJ_1 /* Project object */ = {
203 | isa = PBXProject;
204 | attributes = {
205 | LastUpgradeCheck = 9999;
206 | };
207 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "JSONFeed" */;
208 | compatibilityVersion = "Xcode 3.2";
209 | developmentRegion = English;
210 | hasScannedForEncodings = 0;
211 | knownRegions = (
212 | en,
213 | );
214 | mainGroup = OBJ_5 /* */;
215 | productRefGroup = OBJ_19 /* Products */;
216 | projectDirPath = "";
217 | projectRoot = "";
218 | targets = (
219 | "JSONFeed::JSONFeed" /* JSONFeed */,
220 | "JSONFeed::SwiftPMPackageDescription" /* JSONFeedPackageDescription */,
221 | "JSONFeed::JSONFeedPackageTests::ProductTarget" /* JSONFeedPackageTests */,
222 | "JSONFeed::JSONFeedTests" /* JSONFeedTests */,
223 | );
224 | };
225 | /* End PBXProject section */
226 |
227 | /* Begin PBXResourcesBuildPhase section */
228 | F803FE8A21DD28430044D780 /* Resources */ = {
229 | isa = PBXResourcesBuildPhase;
230 | buildActionMask = 2147483647;
231 | files = (
232 | F803FE8D21DD285D0044D780 /* feed.json in Resources */,
233 | );
234 | runOnlyForDeploymentPostprocessing = 0;
235 | };
236 | /* End PBXResourcesBuildPhase section */
237 |
238 | /* Begin PBXSourcesBuildPhase section */
239 | OBJ_26 /* Sources */ = {
240 | isa = PBXSourcesBuildPhase;
241 | buildActionMask = 0;
242 | files = (
243 | OBJ_27 /* Attachment.swift in Sources */,
244 | OBJ_28 /* Author.swift in Sources */,
245 | OBJ_29 /* Feed.swift in Sources */,
246 | OBJ_30 /* Hub.swift in Sources */,
247 | OBJ_31 /* Item.swift in Sources */,
248 | );
249 | runOnlyForDeploymentPostprocessing = 0;
250 | };
251 | OBJ_37 /* Sources */ = {
252 | isa = PBXSourcesBuildPhase;
253 | buildActionMask = 0;
254 | files = (
255 | OBJ_38 /* Package.swift in Sources */,
256 | );
257 | runOnlyForDeploymentPostprocessing = 0;
258 | };
259 | OBJ_48 /* Sources */ = {
260 | isa = PBXSourcesBuildPhase;
261 | buildActionMask = 0;
262 | files = (
263 | OBJ_49 /* JSONFeedTests.swift in Sources */,
264 | OBJ_50 /* XCTestManifests.swift in Sources */,
265 | );
266 | runOnlyForDeploymentPostprocessing = 0;
267 | };
268 | /* End PBXSourcesBuildPhase section */
269 |
270 | /* Begin PBXTargetDependency section */
271 | OBJ_43 /* PBXTargetDependency */ = {
272 | isa = PBXTargetDependency;
273 | target = "JSONFeed::JSONFeedTests" /* JSONFeedTests */;
274 | targetProxy = F803FE8821DD27E40044D780 /* PBXContainerItemProxy */;
275 | };
276 | OBJ_53 /* PBXTargetDependency */ = {
277 | isa = PBXTargetDependency;
278 | target = "JSONFeed::JSONFeed" /* JSONFeed */;
279 | targetProxy = F803FE8721DD27E30044D780 /* PBXContainerItemProxy */;
280 | };
281 | /* End PBXTargetDependency section */
282 |
283 | /* Begin XCBuildConfiguration section */
284 | OBJ_24 /* Debug */ = {
285 | isa = XCBuildConfiguration;
286 | buildSettings = {
287 | ENABLE_TESTABILITY = YES;
288 | FRAMEWORK_SEARCH_PATHS = (
289 | "$(inherited)",
290 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
291 | );
292 | HEADER_SEARCH_PATHS = "$(inherited)";
293 | INFOPLIST_FILE = JSONFeed.xcodeproj/JSONFeed_Info.plist;
294 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
295 | OTHER_CFLAGS = "$(inherited)";
296 | OTHER_LDFLAGS = "$(inherited)";
297 | OTHER_SWIFT_FLAGS = "$(inherited)";
298 | PRODUCT_BUNDLE_IDENTIFIER = JSONFeed;
299 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
300 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
301 | SKIP_INSTALL = YES;
302 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
303 | SWIFT_VERSION = 4.0;
304 | TARGET_NAME = JSONFeed;
305 | };
306 | name = Debug;
307 | };
308 | OBJ_25 /* Release */ = {
309 | isa = XCBuildConfiguration;
310 | buildSettings = {
311 | ENABLE_TESTABILITY = YES;
312 | FRAMEWORK_SEARCH_PATHS = (
313 | "$(inherited)",
314 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
315 | );
316 | HEADER_SEARCH_PATHS = "$(inherited)";
317 | INFOPLIST_FILE = JSONFeed.xcodeproj/JSONFeed_Info.plist;
318 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
319 | OTHER_CFLAGS = "$(inherited)";
320 | OTHER_LDFLAGS = "$(inherited)";
321 | OTHER_SWIFT_FLAGS = "$(inherited)";
322 | PRODUCT_BUNDLE_IDENTIFIER = JSONFeed;
323 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
324 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
325 | SKIP_INSTALL = YES;
326 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
327 | SWIFT_VERSION = 4.0;
328 | TARGET_NAME = JSONFeed;
329 | };
330 | name = Release;
331 | };
332 | OBJ_3 /* Debug */ = {
333 | isa = XCBuildConfiguration;
334 | buildSettings = {
335 | CLANG_ENABLE_OBJC_ARC = YES;
336 | COMBINE_HIDPI_IMAGES = YES;
337 | COPY_PHASE_STRIP = NO;
338 | DEBUG_INFORMATION_FORMAT = dwarf;
339 | DYLIB_INSTALL_NAME_BASE = "@rpath";
340 | ENABLE_NS_ASSERTIONS = YES;
341 | GCC_OPTIMIZATION_LEVEL = 0;
342 | GCC_PREPROCESSOR_DEFINITIONS = (
343 | "DEBUG=1",
344 | "$(inherited)",
345 | );
346 | MACOSX_DEPLOYMENT_TARGET = 10.10;
347 | ONLY_ACTIVE_ARCH = YES;
348 | OTHER_SWIFT_FLAGS = "-DXcode";
349 | PRODUCT_NAME = "$(TARGET_NAME)";
350 | SDKROOT = macosx;
351 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
352 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "SWIFT_PACKAGE DEBUG";
353 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
354 | USE_HEADERMAP = NO;
355 | };
356 | name = Debug;
357 | };
358 | OBJ_35 /* Debug */ = {
359 | isa = XCBuildConfiguration;
360 | buildSettings = {
361 | LD = /usr/bin/true;
362 | OTHER_SWIFT_FLAGS = "-swift-version 4 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
363 | SWIFT_VERSION = 4.0;
364 | };
365 | name = Debug;
366 | };
367 | OBJ_36 /* Release */ = {
368 | isa = XCBuildConfiguration;
369 | buildSettings = {
370 | LD = /usr/bin/true;
371 | OTHER_SWIFT_FLAGS = "-swift-version 4 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
372 | SWIFT_VERSION = 4.0;
373 | };
374 | name = Release;
375 | };
376 | OBJ_4 /* Release */ = {
377 | isa = XCBuildConfiguration;
378 | buildSettings = {
379 | CLANG_ENABLE_OBJC_ARC = YES;
380 | COMBINE_HIDPI_IMAGES = YES;
381 | COPY_PHASE_STRIP = YES;
382 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
383 | DYLIB_INSTALL_NAME_BASE = "@rpath";
384 | GCC_OPTIMIZATION_LEVEL = s;
385 | MACOSX_DEPLOYMENT_TARGET = 10.10;
386 | OTHER_SWIFT_FLAGS = "-DXcode";
387 | PRODUCT_NAME = "$(TARGET_NAME)";
388 | SDKROOT = macosx;
389 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
390 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
391 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
392 | USE_HEADERMAP = NO;
393 | };
394 | name = Release;
395 | };
396 | OBJ_41 /* Debug */ = {
397 | isa = XCBuildConfiguration;
398 | buildSettings = {
399 | };
400 | name = Debug;
401 | };
402 | OBJ_42 /* Release */ = {
403 | isa = XCBuildConfiguration;
404 | buildSettings = {
405 | };
406 | name = Release;
407 | };
408 | OBJ_46 /* Debug */ = {
409 | isa = XCBuildConfiguration;
410 | buildSettings = {
411 | CLANG_ENABLE_MODULES = YES;
412 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
413 | FRAMEWORK_SEARCH_PATHS = (
414 | "$(inherited)",
415 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
416 | );
417 | HEADER_SEARCH_PATHS = "$(inherited)";
418 | INFOPLIST_FILE = JSONFeed.xcodeproj/JSONFeedTests_Info.plist;
419 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
420 | OTHER_CFLAGS = "$(inherited)";
421 | OTHER_LDFLAGS = "$(inherited)";
422 | OTHER_SWIFT_FLAGS = "$(inherited)";
423 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
424 | SWIFT_VERSION = 4.0;
425 | TARGET_NAME = JSONFeedTests;
426 | };
427 | name = Debug;
428 | };
429 | OBJ_47 /* Release */ = {
430 | isa = XCBuildConfiguration;
431 | buildSettings = {
432 | CLANG_ENABLE_MODULES = YES;
433 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
434 | FRAMEWORK_SEARCH_PATHS = (
435 | "$(inherited)",
436 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
437 | );
438 | HEADER_SEARCH_PATHS = "$(inherited)";
439 | INFOPLIST_FILE = JSONFeed.xcodeproj/JSONFeedTests_Info.plist;
440 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
441 | OTHER_CFLAGS = "$(inherited)";
442 | OTHER_LDFLAGS = "$(inherited)";
443 | OTHER_SWIFT_FLAGS = "$(inherited)";
444 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
445 | SWIFT_VERSION = 4.0;
446 | TARGET_NAME = JSONFeedTests;
447 | };
448 | name = Release;
449 | };
450 | /* End XCBuildConfiguration section */
451 |
452 | /* Begin XCConfigurationList section */
453 | OBJ_2 /* Build configuration list for PBXProject "JSONFeed" */ = {
454 | isa = XCConfigurationList;
455 | buildConfigurations = (
456 | OBJ_3 /* Debug */,
457 | OBJ_4 /* Release */,
458 | );
459 | defaultConfigurationIsVisible = 0;
460 | defaultConfigurationName = Release;
461 | };
462 | OBJ_23 /* Build configuration list for PBXNativeTarget "JSONFeed" */ = {
463 | isa = XCConfigurationList;
464 | buildConfigurations = (
465 | OBJ_24 /* Debug */,
466 | OBJ_25 /* Release */,
467 | );
468 | defaultConfigurationIsVisible = 0;
469 | defaultConfigurationName = Release;
470 | };
471 | OBJ_34 /* Build configuration list for PBXNativeTarget "JSONFeedPackageDescription" */ = {
472 | isa = XCConfigurationList;
473 | buildConfigurations = (
474 | OBJ_35 /* Debug */,
475 | OBJ_36 /* Release */,
476 | );
477 | defaultConfigurationIsVisible = 0;
478 | defaultConfigurationName = Release;
479 | };
480 | OBJ_40 /* Build configuration list for PBXAggregateTarget "JSONFeedPackageTests" */ = {
481 | isa = XCConfigurationList;
482 | buildConfigurations = (
483 | OBJ_41 /* Debug */,
484 | OBJ_42 /* Release */,
485 | );
486 | defaultConfigurationIsVisible = 0;
487 | defaultConfigurationName = Release;
488 | };
489 | OBJ_45 /* Build configuration list for PBXNativeTarget "JSONFeedTests" */ = {
490 | isa = XCConfigurationList;
491 | buildConfigurations = (
492 | OBJ_46 /* Debug */,
493 | OBJ_47 /* Release */,
494 | );
495 | defaultConfigurationIsVisible = 0;
496 | defaultConfigurationName = Release;
497 | };
498 | /* End XCConfigurationList section */
499 | };
500 | rootObject = OBJ_1 /* Project object */;
501 | }
502 |
--------------------------------------------------------------------------------
/JSONFeed.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/JSONFeed.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/JSONFeed.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
--------------------------------------------------------------------------------
/JSONFeed.xcodeproj/xcshareddata/xcschemes/JSONFeed.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
55 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
74 |
76 |
77 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/JSONFeed.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SchemeUserState
5 |
6 | JSONFeed-Package.xcscheme
7 |
8 |
9 | SuppressBuildableAutocreation
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/JSONFeed.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/JSONFeed.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2018 Read Evaluate Press, LLC
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a
4 | copy of this software and associated documentation files (the "Software"),
5 | to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | DEALINGS IN THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "JSONFeed",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "JSONFeed",
12 | targets: ["JSONFeed"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "JSONFeed",
23 | dependencies: []),
24 | .testTarget(
25 | name: "JSONFeedTests",
26 | dependencies: ["JSONFeed"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JSONFeed
2 |
3 | [![Build Status][build status badge]][build status]
4 |
5 | A Swift encoder and decoder for the [JSON Feed](https://jsonfeed.org) format.
6 |
7 | ## Usage
8 |
9 | ```swift
10 | // For an example, see https://jsonfeed.org/feed.json
11 | let json = "{ ... }"
12 |
13 | let decoder = JSONDecoder()
14 | decoder.dateDecodingStrategy = .iso8601
15 |
16 | let feed = try! decoder.decode(Feed.self, from: data)
17 |
18 | print(feed.title)
19 |
20 | for item in feed.items {
21 | print("* \(item.title!) - \(item.datePublished!)")
22 | }
23 | ```
24 |
25 | ## License
26 |
27 | MIT
28 |
29 | ## Contact
30 |
31 | Mattt ([@mattt](https://twitter.com/mattt))
32 |
33 | [build status]: https://github.com/Flight-School/JSONFeed/actions?query=workflow%3ACI
34 | [build status badge]: https://github.com/Flight-School/JSONFeed/workflows/CI/badge.svg
35 |
--------------------------------------------------------------------------------
/Sources/JSONFeed/Attachment.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct Attachment: Codable, Equatable {
4 | /**
5 | The location of the attachment.
6 | */
7 | public var url: URL
8 |
9 | /**
10 | The type of the attachment, such as "audio/mpeg."
11 | */
12 | public var mimeType: String
13 |
14 | /**
15 | A name for the attachment.
16 |
17 | - Important: If there are multiple attachments,
18 | and two or more have the exact same title
19 | (when title is present), then they are considered as
20 | alternate representations of the same thing.
21 | In this way a podcaster, for instance,
22 | might provide an audio recording in different formats.
23 | */
24 | public var title: String?
25 |
26 | /**
27 | The size of the file, in bytes.
28 | */
29 | public var size: Int?
30 |
31 | /**
32 | The duration of the audio or video, when played at normal speed.
33 | */
34 | public var duration: TimeInterval?
35 |
36 | // MARK: -
37 |
38 | public init(url: URL,
39 | mimeType: String,
40 | title: String? = nil,
41 | size: Int? = nil,
42 | duration: TimeInterval? = nil)
43 | {
44 | self.url = url
45 | self.mimeType = mimeType
46 | self.title = title
47 | self.size = size
48 | self.duration = duration
49 | }
50 |
51 | // MARK: Codable
52 |
53 | private enum CodingKeys: String, CodingKey {
54 | case url
55 | case mimeType = "mime_type"
56 | case title
57 | case size = "size_in_bytes"
58 | case duration = "duration_in_seconds"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Sources/JSONFeed/Author.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct Author: Codable, Equatable {
4 | /**
5 | The author's name.
6 | */
7 | public var name: String?
8 |
9 | /**
10 | The URL of a site owned by the author.
11 |
12 | The URL could be a blog, micro-blog, Twitter account, and so on.
13 | Ideally the linked-to page provides a way to contact the author,
14 | but that's not required.
15 | The URL could be a `mailto:` link,
16 | though we suspect that will be rare.
17 | */
18 | public var url: URL?
19 |
20 | /**
21 | The URL for an image for the author.
22 |
23 | As with `icon`, it should be square and relatively large —
24 | such as 512 x 512 — and should use transparency where appropriate,
25 | since it may be rendered on a non-white background.
26 | */
27 | public var avatar: URL?
28 |
29 | // MARK: -
30 |
31 | public init?(name: String? = nil, url: URL? = nil, avatar: URL?) {
32 | if name == nil && url == nil && avatar == nil {
33 | return nil
34 | }
35 |
36 | self.name = name
37 | self.url = url
38 | self.avatar = url
39 | }
40 |
41 | // MARK: Codable
42 |
43 | private enum CodingKeys: String, CodingKey {
44 | case name
45 | case url
46 | case avatar
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/JSONFeed/Feed.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct Feed: Codable, Equatable {
4 | /// JSON Feed Version 1
5 | public static let v1 = URL(string: "https://jsonfeed.org/version/1")!
6 |
7 | /**
8 | The URL of the version of the format the feed uses.
9 |
10 | This should appear at the very top,
11 | though we recognize that not all JSON generators allow for ordering.
12 | */
13 | public var version: URL
14 |
15 | /**
16 | The name of the feed, which will often correspond
17 | to the name of the website (blog, for instance),
18 | though not necessarily.
19 | */
20 | public var title: String
21 |
22 | /**
23 | The URL of the resource that the feed describes.
24 |
25 | This resource may or may not actually be a "home" page,
26 | but it should be an HTML page. If a feed is published on the public web,
27 | this should be considered as required.
28 | But it may not make sense in the case
29 | of a file created on a desktop computer,
30 | when that file is not shared or is shared only privately.
31 | */
32 | public var homePageURL: URL?
33 |
34 | /**
35 | The URL of the feed, serving as the unique identifier for the feed.
36 |
37 | As with `homePageURL`,
38 | this should be considered required for feeds on the public web.
39 | */
40 | public var feedURL: URL?
41 |
42 | /**
43 | Provides more detail, beyond the title,
44 | on what the feed is about.
45 |
46 | A feed reader may display this text.
47 | */
48 | public var description: String?
49 |
50 | /**
51 | A description of the purpose of the feed.
52 |
53 | This is for the use of people looking at the raw JSON,
54 | and should be ignored by feed readers.
55 | */
56 | public var userComment: String?
57 |
58 | /**
59 | The URL of a feed that provides the next `n` items,
60 | where `n` is determined by the publisher.
61 | This allows for pagination, but with the expectation that
62 | reader software is not required to use it
63 | and probably won't use it very often.
64 |
65 | - Warning: `nextURL` must not be the same as `feedURL`,
66 | and it must not be the same as a previous `nextURL`
67 | (to avoid infinite loops).
68 | */
69 | public var nextURL: URL?
70 |
71 | /**
72 | The URL of an image for the feed suitable to be used in a timeline,
73 | much the way an avatar might be used.
74 |
75 | The avatar should be square and relatively large — such as 512 x 512 —
76 | so that it can be scaled-down and so that
77 | it can look good on retina displays.
78 | It should use transparency where appropriate,
79 | since it may be rendered on a non-white background.
80 | */
81 | public var icon: URL?
82 |
83 | /**
84 | The URL of an image for the feed suitable to be used in a source list.
85 |
86 | The favicon should be square and relatively small,
87 | but not smaller than 64 x 64 (so that it can look good on retina displays).
88 | As with `icon`, this image should use transparency where appropriate,
89 | since it may be rendered on a non-white background.
90 | */
91 | public var favicon: URL?
92 |
93 | /**
94 | The feed author.
95 | */
96 | public var author: Author?
97 |
98 |
99 | /**
100 | Whether or not the feed is finished — that is,
101 | whether or not it will ever update again.
102 |
103 | A feed for a temporary event,
104 | such as an instance of the Olympics, could expire.
105 | */
106 | public var expired: Bool?
107 |
108 | /**
109 | Endpoints that can be used to subscribe to real-time notifications
110 | from the publisher of this feed.
111 | */
112 | public var hubs: [Hub]?
113 |
114 | /**
115 | The items in the feed.
116 | */
117 | public var items: [Item]
118 |
119 | // MARK: -
120 |
121 | public init(version: URL = Feed.v1,
122 | title: String,
123 | homePageURL: URL? = nil,
124 | feedURL: URL? = nil,
125 | description: String? = nil,
126 | userComment: String? = nil,
127 | nextURL: URL? = nil,
128 | icon: URL? = nil,
129 | favicon: URL? = nil,
130 | author: Author? = nil,
131 | expired: Bool? = nil,
132 | hubs: [Hub]? = nil,
133 | items: [Item])
134 | {
135 | self.version = version
136 | self.title = title
137 | self.homePageURL = homePageURL
138 | self.feedURL = feedURL
139 | self.description = description
140 | self.userComment = userComment
141 | self.nextURL = nextURL
142 | self.icon = icon
143 | self.favicon = favicon
144 | self.author = author
145 | self.expired = expired
146 | self.hubs = hubs
147 | self.items = items
148 | }
149 |
150 | // MARK: Codable
151 |
152 | private enum CodingKeys: String, CodingKey {
153 | case version
154 | case title
155 | case homePageURL = "home_page_url"
156 | case feedURL = "feed_url"
157 | case description
158 | case userComment = "user_comment"
159 | case nextURL = "next_url"
160 | case icon
161 | case favicon
162 | case author
163 | case expired
164 | case hubs
165 | case items
166 | }
167 | }
168 |
169 |
170 |
--------------------------------------------------------------------------------
/Sources/JSONFeed/Hub.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct Hub: Codable, Equatable {
4 | /// The type of hub.
5 | public var type: String
6 |
7 | /// The URL for the hub.
8 | public var url: URL
9 |
10 | // MARK: -
11 |
12 | public init(type: String, url: URL) {
13 | self.type = type
14 | self.url = url
15 | }
16 |
17 | // MARK: Codable
18 |
19 | private enum CodingKeys: String, CodingKey {
20 | case type
21 | case url
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/JSONFeed/Item.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct Item: Codable, Equatable {
4 | /**
5 | A unique identifier for an item in a feed.
6 |
7 | If an item is ever updated, the `id` should be unchanged.
8 | New items should never use a previously-used `id`.
9 | If an `id` is presented as a number or other type,
10 | a JSON Feed reader must coerce it to a string.
11 | Ideally, the `id` is the full URL of the resource described by the item,
12 | since URLs make great unique identifiers.
13 | */
14 | public var id: String
15 |
16 | /**
17 | The URL of the resource described by the item.
18 |
19 | It's the permalink.
20 | This may be the same as the id — but should be present regardless.
21 | */
22 | public var url: URL?
23 |
24 | /**
25 | The URL of a page elsewhere.
26 |
27 | This is especially useful for linkblogs.
28 | If url links to where you're talking about a thing,
29 | then `externalURL` links to the thing you're talking about.
30 | */
31 | public var externalURL: URL?
32 |
33 | /**
34 | The title in plain text.
35 |
36 | Microblog items in particular may omit titles.
37 | */
38 | public var title: String?
39 |
40 | /**
41 | The HTML of the item.
42 |
43 | - Important: This is the only place HTML is allowed in this format.
44 | A Twitter-like service might use `contentText`,
45 | while a blog might use `contentHTML`.
46 | Use whichever makes sense for your resource.
47 | (It doesn't even have to be the same for each item in a feed.)
48 | - SeeAlso: `contentText`
49 | */
50 | public var contentHTML: String?
51 |
52 | /**
53 | The plain text of the item.
54 |
55 | - SeeAlso: `contentHTML`
56 | */
57 | public var contentText: String?
58 |
59 | /**
60 | A plain text sentence or two describing the item.
61 |
62 | This might be presented in a timeline,
63 | for instance, where a detail view would display
64 | all of `contentHTML` or `contentText`.
65 | */
66 | public var summary: String?
67 |
68 | /**
69 | The URL of the main image for the item.
70 |
71 | This image may also appear in the `contentHTML` — if so,
72 | it's a hint to the feed reader that this is the main, featured image.
73 | Feed readers may use the image as a preview
74 | (probably resized as a thumbnail and placed in a timeline).
75 | */
76 | public var image: URL?
77 |
78 | /**
79 | The URL of an image to use as a banner.
80 |
81 | Some blogging systems (such as Medium)
82 | display a different banner image chosen to go with each post,
83 | but that image wouldn't otherwise appear in the `contentHTML`.
84 |
85 | A feed reader with a detail view may choose to show this banner image
86 | at the top of the detail view,
87 | possibly with the title overlaid.
88 | */
89 | public var bannerImage: URL?
90 |
91 | /**
92 | The publication date.
93 | */
94 | public var datePublished: Date?
95 |
96 | /**
97 | The modification date
98 | */
99 | public var dateModified: Date?
100 |
101 | /**
102 | The author of the item.
103 |
104 | If not specified in an item, then the top-level author, if present,
105 | is the author of the item.
106 | */
107 | public var author: Author?
108 |
109 | /**
110 | Tags related to the item.
111 |
112 | Tags tend to be just one word, but they may be anything.
113 |
114 | - Note: Tags are not the equivalent of Twitter hashtags.
115 | Some blogging systems and other feed formats call these categories.
116 | */
117 | public var tags: [String]?
118 |
119 | /**
120 | Resources related to the item.
121 |
122 | Podcasts, for instance,
123 | would include an attachment that's an audio or video file.
124 | */
125 | public var attachments: [Attachment]?
126 |
127 | // MARK: -
128 |
129 | public init?(id: String,
130 | url: URL? = nil,
131 | externalURL: URL? = nil,
132 | title: String? = nil,
133 | contentHTML: String? = nil,
134 | contentText: String? = nil,
135 | summary: String? = nil,
136 | image: URL? = nil,
137 | bannerImage: URL? = nil,
138 | datePublished: Date? = nil,
139 | dateModified: Date? = nil,
140 | author: Author? = nil,
141 | tags: [String]? = nil,
142 | attachments: [Attachment]? = nil)
143 | {
144 | if contentHTML == nil && contentText == nil {
145 | return nil
146 | }
147 |
148 | self.id = id
149 | self.url = url
150 | self.externalURL = externalURL
151 | self.title = title
152 | self.contentHTML = contentHTML
153 | self.contentText = contentText
154 | self.summary = summary
155 | self.image = image
156 | self.bannerImage = bannerImage
157 | self.datePublished = datePublished
158 | self.dateModified = dateModified
159 | self.author = author
160 | self.tags = tags
161 | self.attachments = attachments
162 | }
163 |
164 | // MARK: Codable
165 |
166 | private enum CodingKeys: String, CodingKey {
167 | case id
168 | case url
169 | case externalURL = "external_url"
170 | case title
171 | case contentHTML = "content_html"
172 | case contentText = "content_text"
173 | case summary
174 | case image
175 | case bannerImage = "banner_image"
176 | case datePublished = "date_published"
177 | case dateModified = "date_modified"
178 | case author
179 | case tags
180 | case attachments
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/Tests/JSONFeedTests/JSONFeedTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import JSONFeed
3 |
4 | @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
5 | final class JSONFeedTests: XCTestCase {
6 | var decoder: JSONDecoder!
7 |
8 | override func setUp() {
9 | self.decoder = JSONDecoder()
10 | self.decoder.dateDecodingStrategy = .iso8601
11 | }
12 |
13 | func testJSONFeedDecoding() {
14 | let feed = try! self.decoder.decode(Feed.self, from: json)
15 |
16 | XCTAssertEqual(feed.version.absoluteString, "https://jsonfeed.org/version/1")
17 | XCTAssertEqual(feed.title, "JSON Feed")
18 | XCTAssertEqual(feed.description, "JSON Feed is a pragmatic syndication format for blogs, microblogs, and other time-based content.")
19 | XCTAssertEqual(feed.homePageURL?.absoluteString, "https://jsonfeed.org/")
20 | XCTAssertEqual(feed.feedURL?.absoluteString, "https://jsonfeed.org/feed.json")
21 |
22 | XCTAssertEqual(feed.author?.name, "Brent Simmons and Manton Reece")
23 | XCTAssertEqual(feed.author?.url?.absoluteString, "https://jsonfeed.org/")
24 |
25 | XCTAssertEqual(feed.items.count, 1)
26 | XCTAssertEqual(feed.items.first?.title, "Announcing JSON Feed")
27 | XCTAssertEqual(feed.items.first?.datePublished, Date(timeIntervalSince1970: 1495033332))
28 | XCTAssertEqual(feed.items.first?.id, "https://jsonfeed.org/2017/05/17/announcing_json_feed")
29 | XCTAssertEqual(feed.items.first?.url?.absoluteString, "https://jsonfeed.org/2017/05/17/announcing_json_feed")
30 | XCTAssert((feed.items.first?.contentHTML?.starts(with: "We — Manton Reece and Brent Simmons"))!)
31 | }
32 | }
33 |
34 | fileprivate let json = #"""
35 | {
36 | "version": "https://jsonfeed.org/version/1",
37 | "user_comment": "This feed allows you to read the posts from this site in any feed reader that supports the JSON Feed format. To add this feed to your reader, copy the following URL — https://jsonfeed.org/feed.json — and add it your reader.",
38 | "title": "JSON Feed",
39 | "description": "JSON Feed is a pragmatic syndication format for blogs, microblogs, and other time-based content.",
40 | "home_page_url": "https://jsonfeed.org/",
41 | "feed_url": "https://jsonfeed.org/feed.json",
42 | "author": {
43 | "name": "Brent Simmons and Manton Reece",
44 | "url": "https://jsonfeed.org/"
45 | },
46 | "items": [
47 | {
48 | "title": "Announcing JSON Feed",
49 | "date_published": "2017-05-17T08:02:12-07:00",
50 | "id": "https://jsonfeed.org/2017/05/17/announcing_json_feed",
51 | "url": "https://jsonfeed.org/2017/05/17/announcing_json_feed",
52 | "content_html": "
We — Manton Reece and Brent Simmons — have noticed that JSON has become the developers’ choice for APIs, and that developers will often go out of their way to avoid XML. JSON is simpler to read and write, and it’s less prone to bugs.
\n\nSo we developed JSON Feed, a format similar to RSS and Atom but in JSON. It reflects the lessons learned from our years of work reading and publishing feeds.
\n\nSee the spec. It’s at version 1, which may be the only version ever needed. If future versions are needed, version 1 feeds will still be valid feeds.
\n\nNotes
\n\nWe have a WordPress plugin and, coming soon, a JSON Feed Parser for Swift. As more code is written, by us and others, we’ll update the code page.
\n\nSee Mapping RSS and Atom to JSON Feed for more on the similarities between the formats.
\n\nThis website — the Markdown files and supporting resources — is up on GitHub, and you’re welcome to comment there.
\n\nThis website is also a blog, and you can subscribe to the RSS feed or the JSON feed (if your reader supports it).
\n\nWe worked with a number of people on this over the course of several months. We list them, and thank them, at the bottom of the spec. But — most importantly — Craig Hockenberry spent a little time making it look pretty. :)
\n"
53 | }
54 | ]
55 | }
56 | """#.data(using: .utf8)!
57 |
--------------------------------------------------------------------------------