Skip to content

Latest commit

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

JSONKit

A lightweight, type-safe JSON toolkit for Swift. JSONKit models any valid JSON value as a Swift enum, provides ergonomic construction from native literals, throwing typed accessors, Codable support, and convenient conversions to and from Foundation and Core Data storable representations.

Features

  • Type-safe JSON modelJSONValue mirrors the JSON data model (null, bool, number, string, array, object).
  • Ergonomic construction — build JSON directly from Swift literals and collections via JSONValueConvertible.
  • Throwing typed accessors — read values by key with clear, descriptive errors when a value is missing or has the wrong type.
  • Codable conformance — encode and decode JSONValue with the standard JSONEncoder/JSONDecoder.
  • RawRepresentable bridging — convert to and from Foundation (Any/[String: Any]) representations, correctly distinguishing the numbers 0/1 from booleans.
  • Model mapping — map JSON objects onto your own types with the JSONInitializable / JSONEncodable / JSONDecodable protocols.
  • ISO 8601 dates — parse and format dates, including microsecond-precise fractional seconds.
  • Core Data interop — convert values into Core Data storable types.
  • SendableJSONValue is Hashable and Sendable.

Requirements

  • Swift 6.3+
  • Any Apple platform with Foundation.

Installation

Add JSONKit to your Package.swift dependencies:

dependencies:[.package(url:"https://github.com/thomasalbert1993/JSONKit.git", from:"1.0.0"),],

Then add it to your target:

.target(
name:"YourTarget",
dependencies:["JSONKit"]),

Usage

The JSONValue type

JSONValue is an indirect enum that represents any JSON value:

import JSONKit
letvalue:JSONValue=.object(["id":.number(1),"name":.string("Alice"),"active":.bool(true),"tags":.array([.string("swift"),.string("json")]),"deletedAt":.null,])

JSONObject is a convenience alias for [String: JSONValue].

Building JSON from Swift literals

Thanks to JSONValueConvertible, native Swift types convert to JSONValue automatically:

letvalue:JSONValue=["a","b","c"].jsonValue // .array([...])
letobject:JSONValue=["name":"Alice","age":30].jsonValue

The following types conform out of the box: JSONValue, NSNull, Bool, Int (stored as .number), Double, String, arrays of convertibles, and dictionaries of convertibles.

Note: You should not conform any additional types to JSONValueConvertible.

The JSONObject initializer also lets you express null naturally with Swift optionals — nil values become .null:

letobject=JSONObject(["a":1,"b":nil, // becomes .null
"c":"x",])

Typed accessors

Read values from a JSONObject with throwing accessors that validate types:

letid=try object.int(forKey:"id")letname=try object.string(forKey:"name")letactive=try object.bool(forKey:"active")lettags=try object.stringArray(forKey:"tags")

Optional variants return nil when the key is missing or its value is null:

letnickname=try object.optionalString(forKey:"nickname") // String?
letscores=try object.optionalIntArray(forKey:"scores") // [Int]?

Accessors exist for bool, int, double, string, object, arrays of each, dates, and their optional counterparts. Reading a value with the wrong type throws a descriptive JSONError.

You can also read typed values directly off a JSONValue:

letflag=tryJSONValue.bool(true).boolValue // true
letn=tryJSONValue.number(42).intValue // 42

Booleans and the numbers 0/1 are interchangeable where sensible, and intValue only accepts non-fractional numbers.

RawRepresentable enums

Decode enums backed by String, Int, or Double:

enumStatus:String{case active, inactive }letstatus:Status=try object.rawRepresentable(forKey:"status")letoptional:Status?=try object.optionalRawRepresentable(forKey:"status")

Mapping to your own models

Conform your types to JSONInitializable to build them from JSON:

structUser:JSONInitializable{letid:Intletname:Stringinit(from content:JSONObject)throws{
id =try content.int(forKey:"id")
name =try content.string(forKey:"name")}}letuser:User=try object.object(forKey:"user")letusers:[User]=try object.objectArray(forKey:"users")
// Or from a JSONObject directly:
letuser2=try object.to(User.self)letuser3:User=try object.instance()

Use JSONEncodable for the reverse direction, and the JSONConvertible alias (JSONEncodable & JSONDecodable) when a type does both.

Codable

JSONValue conforms to Codable, so it works with the standard encoders:

letdata=tryJSONEncoder().encode(value)letdecoded=tryJSONDecoder().decode(JSONValue.self, from: data)

Parsing and serializing

// Decode a JSONObject from raw Data:
letobject=tryJSONObject(data: data)
// Serialize back to a String (optionally pretty-printed):
letstring=try object.serialized(prettyPrinted:true)
// Convert an existing [String: Any] into a JSONObject:
letjson=try someDictionary.toJSON()

Comparing values

Default equality is exact and order-sensitive. To compare arrays while ignoring order and duplicates, use isEqual(to:handleArraysAsSets:):

leta:JSONValue=.array([.number(1),.number(2)])letb:JSONValue=.array([.number(2),.number(1)])
a == b // false
a.isEqual(to: b, handleArraysAsSets:true) // true

Working with null

JSONValue.null.nullAsNil // nil
JSONValue.string("x").nullAsNil // .string("x")
JSONValue.optionalString(nil) // .null
someValue.when(condition) // someValue or .null
object.removingNullValues() // drops all .null entries

ISO 8601 dates

Date accessors parse ISO 8601 strings automatically, choosing the right format based on the string's contents (date only, date-time, or fractional seconds):

letdate=try object.date(forKey:"createdAt")letdates=try object.dateArray(forKey:"timestamps")

Three shared formatters are available directly:

ISO8601DateFormatter.dateFormatter // full date, no time
ISO8601DateFormatter.dateTimeFormatter // date + time
ISO8601DateFormatter.dateTimeFractionalFormatter // microsecond precision

For microsecond precision beyond the default millisecond support, use MicroSecondISO8601DateFormatter.

Core Data interop

Convert values into Core Data storable representations:

letstorable= value.coreDataStorable // Any
letobjectStorable= object.coreDataStorable // [String: Any]

Error handling

Failed lookups and conversions throw JSONError:

  • .invalidValue(_:expected:) — a value has an unexpected type.
  • .invalidValue(forKey:expected:in:) — a keyed value has an unexpected type.
  • .missingOrNullValue(forKey:in:) — a required key is missing or null.
  • .unableToSerializeObject(_:) — serialization failed.
  • .unableToParseRawValue — a RawRepresentable or dictionary conversion failed.

Date parsing throws ISO8601DateFormatterError.invalidDate(_:).

Testing

The package includes a test suite built with the Swift Testing framework. Run it with:

swift test

License

JSONKit is available under the MIT license. See the LICENSE file for details.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages