• Stars
    star
    575
  • Rank 77,622 (Top 2 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 10 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Even Swiftier JSON Handler

OBSOLETED by swift-sion

swift-sion can do all what swift-json can do plus:

Swift 4.1 MIT LiCENSE build status

swift-json

Handle JSON safely, fast, and expressively. Completely rewritten from ground up for Swift 4 and Swift Package Manager.

Synopsis

import JSON
let json:JSON = ["swift":["safe","fast","expressive"]]

Description

This module is a lot like SwiftyJSON in functionality. It wraps JSONSerialization nicely and intuitively. But it differs in how to do so.

  • SwiftyJSON's JSON is struct. JSON of this module is enum
  • SwiftyJSON keeps the output of JSONSerialization.jsonObject in its stored property and convert its value runtime. JSON of this module is static. Definitely Swiftier.
  • SwiftyJSON's JSON.swift is over 1,500 lines while that of this module is less than 400 (as of this writing). Since it is so compact you can use it without building framework.

Initialization

You can build JSON directly as a literal…

let json:JSON = [
    "null":     nil,
    "bool":     true,
    "int":      -42,
    "double":   42.195,
    "string":   "漢字、カタカナ、ひらがなと\"引用符\"の入ったstring😇",
    "array":    [nil, true, 1, "one", [1], ["one":1]],
    "object":   [
        "null":nil, "bool":false, "number":0, "string":"" ,"array":[], "object":[:]
    ],
    "url":"https://github.com/dankogai/"
]

…or String…

let str = """
{
    "null":     null,
    "bool":     true,
    "int":      -42,
    "double":   42.195,
    "string":   "漢字、カタカナ、ひらがなと\\"引用符\\"の入ったstring😇",
    "array":    [null, true, 1, "one", [1], {"one":1}],
    "object":   {
        "null":null, "bool":false, "number":0, "string":"" ,"array":[], "object":{}
    },
    "url":"https://github.com/dankogai/"

}
"""
JSON(string:str)

…or a content of the URL…

JSON(urlString:"https://api.github.com")

…or by decoding Codable data…

import Foundation
struct Point:Hashable, Codable { let (x, y):(Int, Int) }
var data = try JSONEncoder().encode(Point(x:3, y:4))
try JSONDecoder().decode(JSON.self, from:data)

Conversion

once you have the JSON object, converting to other formats is simple.

to JSON string, all you need is stringify it. .description or "(json)" would be enough.

json.description
"\(json)"				// JSON is CustomStringConvertible

If you need Data, simply call .data.

json.data

If you want to feed it (back) to Foundation framework, call .jsonObject

let json4plist = json.pick{ !$0.isNull }    // remove null
let plistData = try PropertyListSerialization.data (
    fromPropertyList:json4plist.jsonObject,
    format:.xml,
    options:0
)
print(String(data:plistData, encoding:.utf8)!)

Manipulation

a blank JSON Array is as simple as:

var json = JSON([])

and you can assign elements like an ordinary array

json[0] = nil
json[1] = true
json[2] = 1

note RHS literals are NOT nil, true and 1 but .Null, .Bool(true) and .Number(1). Therefore this does NOT work

let one = "one"
json[3] = one // error: cannot assign value of type 'String' to type 'JSON'

In which case you do this instead.

json[3].string = one

They are all getters and setters.

json[1].bool   = true
json[2].number = 1
json[3].string = "one"
json[4].array  = [1]
json[5].object = ["one":1]

As a getter they are optional which returns nil when the type mismaches.

json[1].bool    // Optional(true)
json[1].number  // nil

Therefore, you can mutate like so:

json[2].number! += 1            // now 2
json[3].string!.removeLast()    // now "on"
json[4].array!.append(2)        // now [1, 2]
json[5].object!["two"] = 2      // now ["one":1,"two":2]

When you assign values to JSON array with an out-of-bound index, it is automatically streched with unassigned elements set to null, just like an ECMAScript Array

json[10] = false	// json[6...9] are null

As you may have guessed by now, a blank JSON object(dictionary) is:

json = JSON([:])

And manipulate intuitively like so.

json["null"]    = nil		// not null
json["bool"]    = false
json["number"]  = 0
json["string"]  = ""
json["array"]   = []
json["object"]  = [:]		// not {}

deep traversal

JSON is a recursive data type. For recursive data types, you need a recursive method that traverses the data deep down. For that purpuse, JSON offers .pick and .walk.

.pick is a ".deepFilter" that filters recursively. You've already seen it above. It takes a filter function of type (JSON)->Bool. That function is applied to all leaf values of the tree and leaves that do not meet the predicate are pruned.

// because property list does not accept null
let json4plist = json.pick{ !$0.isNull }

.walk is a deepMap that transforms recursively. This one is a little harder because you have to consider what to do on node and leaves separately. To make your life easier three different versions of .walk are provided. The first one just takes a leaf node.

// square all numbers and leave anything else 
JSON([0,[1,[2,3,[4,5,6]]], true]).walk {
    guard let n = $0.number else { return $0 }
    return JSON(n * n)
}

The second forms just takes a node. Instead of explaining it, let me show you how .pick is implemented by extending JSON with .select that does exactly the same as .pick.

extension JSON {
    func select(picker:(JSON)->Bool)->JSON {
        return self.walk{ node, pairs, depth in
            switch node.type {
            case .array:
                return .Array(pairs.map{ $0.1 }.filter({ picker($0) }) )
            case .object:
                var o = [Key:Value]()
                pairs.filter{ picker($0.1) }.forEach{ o[$0.0.key!] = $0.1 }
                return .Object(o)
            default:
                return .Error(.notIterable(node.type))
            }
        }
    }
}

And the last form takes both. Unlike the previous ones this one can return other than JSON. Here is a quick and dirty .yaml that emits a YAML.

extension JSON {
    var yaml:String {
        return self.walk(depth:0, collect:{ node, pairs, depth in
            let indent = Swift.String(repeating:"  ", count:depth)
            var result = ""
            switch node.type {
            case .array:
                guard !pairs.isEmpty else { return "[]"}
                result = pairs.map{ "- " + $0.1}.map{indent + $0}.joined(separator: "\n")
            case .object:
                guard !pairs.isEmpty else { return "{}"}
                result = pairs.sorted{ $0.0.key! < $1.0.key! }.map{
                    let k = $0.0.key!
                    let q = k.rangeOfCharacter(from: .newlines) != nil
                    return (q ? k.debugDescription : k) + ": "  + $0.1
                    }.map{indent + $0}.joined(separator: "\n")
            default:
                break   // never reaches here
            }
            return "\n" + result
        },visit:{
            if $0.isNull { return  "~" }
            if let s = $0.string {
                return s.rangeOfCharacter(from: .newlines) == nil ? s : s.debugDescription
            }
            return $0.description
        })
    }
}

Protocol Conformance

  • JSON is Equatable so you can check if two JSONs are the same.
JSON(string:foo) == JSON(urlString:"https://example.com/whereever")
  • JSON is Hashable so you can use it as a dictionary key.

  • JSON is ExpressibleBy*Literal. That's why you can initialize w/ variable:JSON construct show above.

  • JSON is CustomStringConvertible whose .description is always a valid JSON.

  • JSON is Codable. You can use this module instead of JSONEncoder.

  • JSON is Sequence. But when you iterate, be careful with the key.

let ja:JSON = [nil, true, 1, "one", [1], ["one":1]]
// wrong!
for v in ja {
	//
}
// right!
for (i, v) in ja {
	// i is NOT an Integer but KeyType.Index.
	// To access its value, say i.index
}
let jo:JSON = [
    "null":nil, "bool":false, "number":0, "string":"",
    "array":[], "object":[:]
]
for (k, v) in jo {
	// k is NOT an Integer but KeyType.Key.
	// To access its value, say i.key
}

That is because swift demands to return same Element type. If you feel this counterintuitive, you can simply use .array or .object:

for v in ja.array! {
	// ...
}
for (k, v) in jo.object! {
	// ...
}

Error handling

Once inited, JSON never fails. That is, it never becomes nil. Instead of being failable or throwing exceptions, JSON has a special value .Error(.ErrorType) which propagates across the method invocations. The following code examines the error should it happen.

if let e = json.error {
	debugPrint(e.type)
	if let nsError = e.nsError {
		// do anything with nsError
	}
}

Usage

build

$ git clone https://github.com/dankogai/swift-json.git
$ cd swift-json # the following assumes your $PWD is here
$ swift build

REPL

Simply

$ scripts/run-repl.sh

or

$ swift build && swift -I.build/debug -L.build/debug -lJSON

and in your repl,

  1> import JSON
  2> let json:JSON = ["swift":["safe","fast","expressive"]]
json: JSON.JSON = Object {
  Object = 1 key/value pair {
    [0] = {
      key = "swift"
      value = Array {
        Array = 3 values {
          [0] = String {
            String = "safe"
          }
          [1] = String {
            String = "fast"
          }
          [2] = String {
            String = "expressive"
          }
        }
      }
    }
  }
}

Xcode

Xcode project is deliberately excluded from the repository because it should be generated via swift package generate-xcodeproj . For convenience, you can

$ scripts/prep-xcode

And the Workspace opens up for you with Playground on top. The playground is written as a manual.

iOS and Swift Playground

Unfortunately Swift Package Manager does not support iOS. To make matters worse Swift Playgrounds does not support modules. But don't worry. This module is so compact all you need is copy JSON.swift.

In case of Swift Playgrounds just copy it under Sources folder. If you are too lazy just run:

$ scripts/ios-prep.sh

and iOS/JSON.playground is all set. You do not have to import JSON therein.

From Your SwiftPM-Managed Projects

Add the following to the dependencies section:

.package(
  url: "https://github.com/dankogai/swift-json.git", from: "4.0.0"
)

and the following to the .target argument:

.target(
  name: "YourSwiftyPackage",
  dependencies: ["JSON"])

Now all you have to do is:

import JSON

in your code. Enjoy!

Prerequisite

Swift 4.1 or better, OS X or Linux to build.

More Repositories

1

js-base64

Base64 implementation for JavaScript
JavaScript
4,231
star
2

js-combinatorics

power set, combination, permutation and more in JavaScript
JavaScript
742
star
3

js-deflate

RFC 1951 raw deflate/inflate for JavaScript
JavaScript
402
star
4

swift2-pons

Protocol-Oriented Number System in Pure Swift
Swift
202
star
5

swift-sion

SION handler in Swift. also handles / JSON / Property List / msgpack.org[SION,Swift]
Swift
91
star
6

swift-complex

Complex numbers in Swift
Swift
70
star
7

p5-encode

Encode - character encodings (for Perl 5.8 or better)
Perl
37
star
8

js-hanzenkaku

Hankaku-Zenkaku Translator in JS
JavaScript
32
star
9

js-object-clone

Deep clone and comparison with ES5 property descriptor and object extensibility support
JavaScript
32
star
10

swift-bignum

Arbitrary-precision arithmetic for Swift, in Swift
Swift
29
star
11

swift-lazylist

A Haskell-Like Lazy List in Swift
Swift
26
star
12

swift-jsdemo

Use JavaScriptCore from Swift
Swift
25
star
13

js-math-complex

Complex Number in JavaScript
JavaScript
23
star
14

js-xiterable

Make ES6 Iterators Functional Again
TypeScript
21
star
15

swift-pons

Protocol-Oriented Number System in Pure Swift
Swift
19
star
16

js-lambda

DSL for lambda calculus
JavaScript
16
star
17

SION

Swift Interchangeable Object Notation
15
star
18

swift-prime

Prime number in Swift
Swift
15
star
19

p5-uri-escape-xs

URI::Escape::XS - Drop-In replacement for URI::Escape
Perl
15
star
20

js-codepoints

make your javascript handle unicode codepoints more correctly
JavaScript
15
star
21

js-math-bigint

BigInt in JavaScript
JavaScript
14
star
22

osx-mv2trash

mv2trash - move (file|folder)s? to trash on OS X
Perl
14
star
23

swift-combinatorics

Combinatorics in Swift
Swift
12
star
24

swift-perl

Run Perl from Swift
Swift
11
star
25

js-es2pi

ES6 + a little more on top of ES5
JavaScript
11
star
26

c-bucketsort

bucketsort - bucket sort that can be used for general purpose
Perl
8
star
27

swift-operators

Convenient operators for Swift
8
star
28

js-cocytus

A sandboxed web worker without eval() and assignments to global variables
JavaScript
8
star
29

p5-uri-amazon-apa

URI::Amazon::APA - URI to access Amazon Product Advertising API
Perl
7
star
30

js-wrap

Universal Wrapper Object System for ECMAScript 5 and beyond
JavaScript
7
star
31

p5-lingua-ja-numbers

Lingua::JA::Numbers - Converts numeric values into their Japanese string equivalents and vice versa
Perl
6
star
32

js-trie-patricia

Patricia Trie in JavaScript
JavaScript
6
star
33

js-list-lazy

Lazy List in JavaScript
JavaScript
6
star
34

p5-regexp-optimizer

Regexp::Optimizer - optimizes regular expressions
Perl
6
star
35

swift-tap

Test Anything Protocol for Swift
Swift
5
star
36

js-hexfloat

Rudimentary C99 Hexadecimal Floating Point Support in JS
JavaScript
5
star
37

p5-lingua-ja-kana

Lingua::JA::Kana - Kata-Romaji related utilities
Perl
5
star
38

p5-jcode

Jcode - Japanese Charset Handler
C
5
star
39

p5-http-response-encoding

HTTP::Response::Encoding - Adds encoding() to HTTP::Response
Perl
4
star
40

node-voorhees

JavaScript
3
star
41

p5-attribute-util

Attribute::Util - Assorted general utility attributes
Perl
3
star
42

p5-class-builtin

Class::Builtin - Scalar/Array/Hash as objects
Perl
3
star
43

swift-bf

Brainfuck Interpreter/Compiler in Swift
Swift
3
star
44

p5-attribute-tie

Attribute::Tie - Tie via Attribute
Perl
3
star
45

js-charnames

Unicode Character Names
3
star
46

swift-gmpint

Swift binding to GMP Integer
Swift
3
star
47

p5-text-darts

Text::Darts - Perl interface to DARTS by Taku Kudoh
C++
3
star
48

p5-tie-array-lazy

Tie::Array::Lazy - Lazy -- but mutable -- arrays.
Perl
2
star
49

p5-perl6-perl

Perl6::Perl - $obj->perl just like $obj.perl in Perl 6
Perl
2
star
50

p5-bsd-stat

BSD::stat - stat() with BSD 4.4 extentions
Perl
2
star
51

scripts

script files anything and everything
JavaScript
2
star
52

js-sion

SION deserializer/serializer for ECMAScript
JavaScript
2
star
53

p5-bsd-getloadavg

BSD::getloadavg - Perl Interface to getloadavg (3)
Perl
2
star
54

js-es6-map

Yet another ES6 Map/Set implementation which avoids linear search when possible
JavaScript
2
star
55

p5-data-lock

Data::Lock - makes variables (im)?mutable
Perl
2
star
56

swift-interval

Interval Arithmetic in Swift with Interval Type
Swift
2
star
57

p5-tie-array-pack

Tie::Array::Pack − An array implemented as a packed string
Perl
2
star
58

p5-text-tx

Text::Tx - Perl interface to Tx by OKANOHARA Daisuke
Perl
2
star
59

js-installproperty

defineProperty, undoably.
JavaScript
2
star
60

p5-file-glob-slurp

File::Glob::Slurp - Turns <> into a slurp operator
Perl
1
star
61

p5-encode-jis2k

Encode::JIS2K - JIS X 0212 (aka JIS 2000) Encodings
Perl
1
star
62

morscript

JavaScript
1
star
63

swift-toliteral

convert values to swift literals
Swift
1
star
64

p5-digest-siphash

Digest::SipHash - Perl XS interface to the SipHash algorithm
Perl
1
star
65

swift-int2x

Int2X made of IntX
Swift
1
star
66

p5-app-solo

run only one process up to given timeout
Perl
1
star
67

p5-acme-akashic-records

Acme::Akashic::Records - Access The Akashic Records
Perl
1
star
68

p5-psgi-or-cgi

PSGI::OR::CGI - Write a PSGI app that also runs as CGI
Perl
1
star
69

p5-data-decycle

Data::Decycle - (Cyclic|Circular) reference decycler
Perl
1
star