├── .gitignore ├── .swift-version ├── .travis.yml ├── LICENSE ├── Package.swift ├── README.md ├── Source ├── JSON.swift ├── JSONParser.swift └── JSONSerializer.swift └── Tests ├── JSONTests └── JSONTests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/xcode 2 | 3 | ### Xcode ### 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## Build generated 9 | .build/ 10 | build/ 11 | DerivedData 12 | Carthage/ 13 | Packages/ 14 | 15 | ## Various settings 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata 25 | 26 | ## Other 27 | *.xccheckout 28 | *.moved-aside 29 | *.xcuserstate 30 | *.xcscmblueprint 31 | *.xcodeproj 32 | XcodeDevelopment/ 33 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | DEVELOPMENT-SNAPSHOT-2016-08-07-a 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | slack: zewo:VjyVCCQvTOw9yrbzQysZezD1 3 | os: 4 | - linux 5 | - osx 6 | language: generic 7 | sudo: required 8 | dist: trusty 9 | osx_image: xcode8 10 | install: 11 | - eval "$(curl -sL https://raw.githubusercontent.com/Zewo/Zewo/5254525d9da56df29346fd76e99529c22034d61d/Scripts/install-swiftenv.sh)" 12 | script: 13 | - swift build 14 | - swift build -c release 15 | - swift test 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Zewo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "JSON", 5 | dependencies: [ 6 | .Package(url: "https://github.com/open-swift/C7.git", majorVersion: 0, minor: 12), 7 | ] 8 | ) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON 2 | 3 | [![Swift][swift-badge]][swift-url] 4 | [![Zewo][zewo-badge]][zewo-url] 5 | [![Platform][platform-badge]][platform-url] 6 | [![License][mit-badge]][mit-url] 7 | [![Slack][slack-badge]][slack-url] 8 | [![Travis][travis-badge]][travis-url] 9 | [![Codebeat][codebeat-badge]][codebeat-url] 10 | 11 | This is a fork of [Swift-PureJsonSerializer](https://github.com/gfx/Swift-PureJsonSerializer) 12 | 13 | ## Installation 14 | 15 | ```swift 16 | import PackageDescription 17 | 18 | let package = Package( 19 | dependencies: [ 20 | .Package(url: "https://github.com/Zewo/JSON.git", majorVersion: 0, minor: 6), 21 | ] 22 | ) 23 | ``` 24 | 25 | ## Support 26 | 27 | If you need any help you can join our [Slack](http://slack.zewo.io) and go to the **#help** channel. Or you can create a Github [issue](https://github.com/Zewo/Zewo/issues/new) in our main repository. When stating your issue be sure to add enough details, specify what module is causing the problem and reproduction steps. 28 | 29 | ## Community 30 | 31 | [![Slack][slack-image]][slack-url] 32 | 33 | The entire Zewo code base is licensed under MIT. By contributing to Zewo you are contributing to an open and engaged community of brilliant Swift programmers. Join us on [Slack](http://slack.zewo.io) to get to know us! 34 | 35 | ## License 36 | 37 | This project is released under the MIT license. See [LICENSE](LICENSE) for details. 38 | 39 | [swift-badge]: https://img.shields.io/badge/Swift-3.0-orange.svg?style=flat 40 | [swift-url]: https://swift.org 41 | [zewo-badge]: https://img.shields.io/badge/Zewo-0.5-FF7565.svg?style=flat 42 | [zewo-url]: http://zewo.io 43 | [platform-badge]: https://img.shields.io/badge/Platforms-OS%20X%20--%20Linux-lightgray.svg?style=flat 44 | [platform-url]: https://swift.org 45 | [mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat 46 | [mit-url]: https://tldrlegal.com/license/mit-license 47 | [slack-image]: http://s13.postimg.org/ybwy92ktf/Slack.png 48 | [slack-badge]: https://zewo-slackin.herokuapp.com/badge.svg 49 | [slack-url]: http://slack.zewo.io 50 | [travis-badge]: https://travis-ci.org/Zewo/JSON.svg?branch=master 51 | [travis-url]: https://travis-ci.org/Zewo/JSON 52 | [codebeat-badge]: https://codebeat.co/badges/74bdc7b0-746f-40e3-a752-a06fc655915d 53 | [codebeat-url]: https://codebeat.co/projects/github-com-zewo-json 54 | -------------------------------------------------------------------------------- /Source/JSON.swift: -------------------------------------------------------------------------------- 1 | // JSON.swift 2 | // 3 | // The MIT License (MIT) 4 | // 5 | // Copyright (c) 2015 Zewo 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | @_exported import C7 26 | 27 | extension JSON { 28 | public enum JSONError: Error { 29 | case incompatibleType 30 | } 31 | } 32 | 33 | extension JSON { 34 | public static func infer(_ value: Bool) -> JSON { 35 | return .boolean(value) 36 | } 37 | 38 | public static func infer(_ value: Int) -> JSON { 39 | return .number(JSON.Number.integer(value)) 40 | } 41 | 42 | public static func infer(_ value: UInt) -> JSON { 43 | return .number(JSON.Number.unsignedInteger(value)) 44 | } 45 | 46 | public static func infer(_ value: Double) -> JSON { 47 | return .number(JSON.Number.double(value)) 48 | } 49 | 50 | public static func infer(_ value: String) -> JSON { 51 | return .string(value) 52 | } 53 | 54 | public static func infer(_ value: [JSON]) -> JSON { 55 | return .array(value) 56 | } 57 | 58 | public static func infer(_ value: [String: JSON]) -> JSON { 59 | return .object(value) 60 | } 61 | } 62 | 63 | extension JSON { 64 | public var isBool: Bool { 65 | if case .boolean = self { 66 | return true 67 | } 68 | return false 69 | } 70 | 71 | public var isNumber: Bool { 72 | if case .number = self { 73 | return true 74 | } 75 | return false 76 | } 77 | 78 | public var isString: Bool { 79 | if case .string = self { 80 | return true 81 | } 82 | return false 83 | } 84 | 85 | public var isArray: Bool { 86 | if case .array = self { 87 | return true 88 | } 89 | return false 90 | } 91 | 92 | public var isDictionary: Bool { 93 | if case .object = self { 94 | return true 95 | } 96 | return false 97 | } 98 | } 99 | 100 | extension JSON { 101 | public func get() throws -> T { 102 | switch self { 103 | case .boolean(let value as T): 104 | return value 105 | case .number(let value): 106 | switch value { 107 | case .integer(let value as T): 108 | return value 109 | case .unsignedInteger(let value as T): 110 | return value 111 | case .double(let value as T): 112 | return value 113 | default: break 114 | } 115 | 116 | case .string(let value as T): 117 | return value 118 | case .array(let value as T): 119 | return value 120 | case .object(let value as T): 121 | return value 122 | default: break 123 | } 124 | throw JSONError.incompatibleType 125 | } 126 | 127 | public func get(_ key: String) throws -> T { 128 | if let value = self[key] { 129 | return try value.get() 130 | } 131 | 132 | throw JSONError.incompatibleType 133 | } 134 | 135 | public func get() -> T? { 136 | return try? get() 137 | } 138 | } 139 | 140 | extension JSON { 141 | public var booleanValue: Bool? { 142 | return try? get() 143 | } 144 | 145 | public var doubleValue: Double? { 146 | return try? get() 147 | } 148 | 149 | public var intValue: Int? { 150 | return try? get() 151 | } 152 | 153 | public var uintValue: UInt? { 154 | return try? get() 155 | } 156 | 157 | public var stringValue: String? { 158 | return try? get() 159 | } 160 | 161 | public var dataValue: Data? { 162 | return try? get() 163 | } 164 | 165 | public var arrayValue: [JSON]? { 166 | return try? get() 167 | } 168 | 169 | public var objectValue: [String: JSON]? { 170 | return try? get() 171 | } 172 | } 173 | 174 | extension JSON { 175 | public func asBool() throws -> Bool { 176 | return try get() 177 | } 178 | 179 | public func asDouble() throws -> Double { 180 | return try get() 181 | } 182 | 183 | public func asInt() throws -> Int { 184 | return try get() 185 | } 186 | 187 | public func asUInt() throws -> UInt { 188 | if let uint = uintValue { 189 | return UInt(uint) 190 | } 191 | throw JSONError.incompatibleType 192 | } 193 | 194 | public func asString() throws -> String { 195 | return try get() 196 | } 197 | 198 | public func asData() throws -> Data { 199 | return try get() 200 | } 201 | 202 | public func asArray() throws -> [JSON] { 203 | return try get() 204 | } 205 | 206 | public func asDictionary() throws -> [String: JSON] { 207 | return try get() 208 | } 209 | } 210 | 211 | extension JSON { 212 | public subscript(index: Int) -> C7.JSON? { 213 | get { 214 | guard let array = arrayValue, index >= 0 && index < array.count else { 215 | return nil 216 | } 217 | return array[index] 218 | } 219 | 220 | set(JSON) { 221 | switch self { 222 | case .array(let array): 223 | var array = array 224 | if index >= 0 && index < array.count { 225 | array[index] = JSON ?? .null 226 | self = .array(array) 227 | } 228 | default: 229 | break 230 | } 231 | } 232 | } 233 | 234 | public subscript(key: String) -> C7.JSON? { 235 | get { 236 | return objectValue?[key] 237 | } 238 | 239 | set(JSON) { 240 | switch self { 241 | case .object(let object): 242 | var object = object 243 | object[key] = JSON 244 | self = .object(object) 245 | default: break 246 | } 247 | } 248 | } 249 | } 250 | 251 | extension JSON.Number: Equatable {} 252 | 253 | public func ==(lhs: JSON.Number, rhs: JSON.Number) -> Bool { 254 | switch (lhs, rhs) { 255 | case (.integer(let l), .integer(let r)) where l == r: 256 | return true 257 | case (.unsignedInteger(let l), .unsignedInteger(let r)) where l == r: 258 | return true 259 | case (.double(let l), .double(let r)) where l == r: 260 | return true 261 | default: 262 | return false 263 | } 264 | } 265 | 266 | extension JSON: Equatable {} 267 | 268 | public func ==(lhs: JSON, rhs: JSON) -> Bool { 269 | switch (lhs, rhs) { 270 | case (.null, .null): 271 | return true 272 | case (.boolean(let l), .boolean(let r)) where l == r: 273 | return true 274 | case (.string(let l), .string(let r)) where l == r: 275 | return true 276 | case (.number(let l), .number(let r)) where l == r: 277 | return true 278 | case (.array(let l), .array(let r)) where l == r: 279 | return true 280 | case (.object(let l), .object(let r)) where l == r: 281 | return true 282 | default: 283 | return false 284 | } 285 | } 286 | 287 | extension JSON: ExpressibleByNilLiteral { 288 | public init(nilLiteral value: Void) { 289 | self = .null 290 | } 291 | } 292 | 293 | extension JSON: ExpressibleByBooleanLiteral { 294 | public init(booleanLiteral value: Bool) { 295 | self = .boolean(value) 296 | } 297 | } 298 | 299 | extension JSON: ExpressibleByIntegerLiteral { 300 | public init(integerLiteral value: Int) { 301 | self = .number(JSON.Number.integer(value)) 302 | } 303 | } 304 | 305 | extension JSON: ExpressibleByFloatLiteral { 306 | public init(floatLiteral value: Float) { 307 | self = .number(JSON.Number.double(Double(value))) 308 | } 309 | } 310 | 311 | extension JSON: ExpressibleByStringLiteral { 312 | public init(unicodeScalarLiteral value: String) { 313 | self = .string(value) 314 | } 315 | 316 | public init(extendedGraphemeClusterLiteral value: String) { 317 | self = .string(value) 318 | } 319 | 320 | public init(stringLiteral value: String) { 321 | self = .string(value) 322 | } 323 | } 324 | 325 | extension JSON: ExpressibleByStringInterpolation { 326 | public init(stringInterpolation strings: JSON...) { 327 | let string = strings.reduce("") { $0 + ($1.stringValue ?? "") } 328 | self = .string(string) 329 | } 330 | 331 | public init(stringInterpolationSegment expr: T) { 332 | self = .string(String(describing: expr)) 333 | } 334 | } 335 | 336 | extension JSON: ExpressibleByArrayLiteral { 337 | public init(arrayLiteral elements: JSON...) { 338 | self = .array(elements) 339 | } 340 | } 341 | 342 | extension JSON: ExpressibleByDictionaryLiteral { 343 | public init(dictionaryLiteral elements: (String, JSON)...) { 344 | var object = [String: JSON](minimumCapacity: elements.count) 345 | 346 | for (key, value) in elements { 347 | object[key] = value 348 | } 349 | 350 | self = .object(object) 351 | } 352 | } 353 | 354 | extension JSON.Number: CustomStringConvertible { 355 | public var description: String { 356 | switch self { 357 | case .integer(let i): return String(i) 358 | case .unsignedInteger(let u): return String(u) 359 | case .double(let d): return String(d) 360 | } 361 | } 362 | } 363 | 364 | extension JSON: CustomStringConvertible { 365 | public var description: String { 366 | var indentLevel = 0 367 | 368 | func serialize(_ data: JSON) -> String { 369 | switch data { 370 | case .null: return "null" 371 | case .boolean(let b): return String(b) 372 | case .number(let n): return String(describing: n) 373 | case .string(let s): return escape(s) 374 | case .array(let a): return serialize(array: a) 375 | case .object(let o): return serialize(object: o) 376 | } 377 | } 378 | 379 | func serialize(array: [JSON]) -> String { 380 | var s = "[" 381 | indentLevel += 1 382 | 383 | for i in 0 ..< array.count { 384 | s += "\n" 385 | s += indent() 386 | s += serialize(array[i]) 387 | 388 | if i != (array.count - 1) { 389 | s += "," 390 | } 391 | } 392 | 393 | indentLevel -= 1 394 | return s + "\n" + indent() + "]" 395 | } 396 | 397 | func serialize(object: [String: JSON]) -> String { 398 | var s = "{" 399 | indentLevel += 1 400 | var i = 0 401 | 402 | for (key, value) in object { 403 | s += "\n" 404 | s += indent() 405 | s += "\(escape(key)): \(serialize(value))" 406 | 407 | if i != (object.count - 1) { 408 | s += "," 409 | } 410 | i += 1 411 | } 412 | 413 | indentLevel -= 1 414 | return s + "\n" + indent() + "}" 415 | } 416 | 417 | func indent() -> String { 418 | let spaceCount = indentLevel * 4 419 | return String(repeating: " ", count: spaceCount) 420 | } 421 | 422 | return serialize(self) 423 | } 424 | } 425 | 426 | func escape(_ source: String) -> String { 427 | var s = "\"" 428 | 429 | for c in source.characters { 430 | if let escapedSymbol = escapeMapping[c] { 431 | s.append(escapedSymbol) 432 | } else { 433 | s.append(c) 434 | } 435 | } 436 | 437 | s.append("\"") 438 | 439 | return s 440 | } 441 | -------------------------------------------------------------------------------- /Source/JSONParser.swift: -------------------------------------------------------------------------------- 1 | // JSONParser.swift 2 | // 3 | // The MIT License (MIT) 4 | // 5 | // Copyright (c) 2015 Zewo 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | // This file has been modified from its original project Swift-JsonSerializer 26 | 27 | #if os(Linux) 28 | import Glibc 29 | #else 30 | import Darwin.C 31 | #endif 32 | 33 | public enum JSONParseError: Error, CustomStringConvertible { 34 | case unexpectedTokenError(reason: String, lineNumber: Int, columnNumber: Int) 35 | case insufficientTokenError(reason: String, lineNumber: Int, columnNumber: Int) 36 | case extraTokenError(reason: String, lineNumber: Int, columnNumber: Int) 37 | case nonStringKeyError(reason: String, lineNumber: Int, columnNumber: Int) 38 | case invalidStringError(reason: String, lineNumber: Int, columnNumber: Int) 39 | case invalidNumberError(reason: String, lineNumber: Int, columnNumber: Int) 40 | 41 | public var description: String { 42 | switch self { 43 | case .unexpectedTokenError(let r, let l, let c): 44 | return "UnexpectedTokenError!\nLine: \(l)\nColumn: \(c)]\nReason: \(r)" 45 | case .insufficientTokenError(let r, let l, let c): 46 | return "InsufficientTokenError!\nLine: \(l)\nColumn: \(c)]\nReason: \(r)" 47 | case .extraTokenError(let r, let l, let c): 48 | return "ExtraTokenError!\nLine: \(l)\nColumn: \(c)]\nReason: \(r)" 49 | case .nonStringKeyError(let r, let l, let c): 50 | return "NonStringKeyError!\nLine: \(l)\nColumn: \(c)]\nReason: \(r)" 51 | case .invalidStringError(let r, let l, let c): 52 | return "InvalidStringError!\nLine: \(l)\nColumn: \(c)]\nReason: \(r)" 53 | case .invalidNumberError(let r, let l, let c): 54 | return "InvalidNumberError!\nLine: \(l)\nColumn: \(c)]\nReason: \(r)" 55 | } 56 | } 57 | } 58 | 59 | public struct JSONParser { 60 | public init() {} 61 | 62 | public func parse(data: Data) throws -> JSON { 63 | return try GenericJSONParser(data).parse() 64 | } 65 | } 66 | 67 | class GenericJSONParser where ByteSequence.Iterator.Element == UInt8 { 68 | typealias Source = ByteSequence 69 | typealias Char = Source.Iterator.Element 70 | 71 | let source: Source 72 | var cur: Source.Index 73 | let end: Source.Index 74 | 75 | var lineNumber = 1 76 | var columnNumber = 1 77 | 78 | init(_ source: Source) { 79 | self.source = source 80 | self.cur = source.startIndex 81 | self.end = source.endIndex 82 | } 83 | 84 | func parse() throws -> JSON { 85 | let JSON = try parseValue() 86 | skipWhitespaces() 87 | if (cur == end) { 88 | return JSON 89 | } else { 90 | throw JSONParseError.extraTokenError( 91 | reason: "extra tokens found", 92 | lineNumber: lineNumber, 93 | columnNumber: columnNumber 94 | ) 95 | } 96 | } 97 | } 98 | 99 | // MARK: - Private 100 | 101 | extension GenericJSONParser { 102 | fileprivate func parseValue() throws -> JSON { 103 | skipWhitespaces() 104 | if cur == end { 105 | throw JSONParseError.insufficientTokenError( 106 | reason: "unexpected end of tokens", 107 | lineNumber: lineNumber, 108 | columnNumber: columnNumber 109 | ) 110 | } 111 | 112 | switch currentChar { 113 | case Char(ascii: "n"): return try parseSymbol("null", JSON.null) 114 | case Char(ascii: "t"): return try parseSymbol("true", JSON.boolean(true)) 115 | case Char(ascii: "f"): return try parseSymbol("false", JSON.boolean(false)) 116 | case Char(ascii: "-"), Char(ascii: "0") ... Char(ascii: "9"): return try parseNumber() 117 | case Char(ascii: "\""): return try parseString() 118 | case Char(ascii: "{"): return try parseObject() 119 | case Char(ascii: "["): return try parseArray() 120 | case (let c): throw JSONParseError.unexpectedTokenError( 121 | reason: "unexpected token: \(c)", 122 | lineNumber: lineNumber, 123 | columnNumber: columnNumber 124 | ) 125 | } 126 | } 127 | 128 | private var currentChar: Char { 129 | return source[cur] 130 | } 131 | 132 | private var nextChar: Char { 133 | return source[source.index(after: cur)] 134 | } 135 | 136 | private var currentSymbol: Character { 137 | return Character(UnicodeScalar(currentChar)) 138 | } 139 | 140 | private func parseSymbol(_ target: StaticString, _ iftrue: @autoclosure (Void) -> JSON) throws -> JSON { 141 | if expect(target) { 142 | return iftrue() 143 | } else { 144 | throw JSONParseError.unexpectedTokenError( 145 | reason: "expected \"\(target)\" but \(currentSymbol)", 146 | lineNumber: lineNumber, 147 | columnNumber: columnNumber 148 | ) 149 | } 150 | } 151 | 152 | private func parseString() throws -> JSON { 153 | assert(currentChar == Char(ascii: "\""), "points a double quote") 154 | advance() 155 | var buffer: [CChar] = [] 156 | 157 | LOOP: while cur != end { 158 | switch currentChar { 159 | case Char(ascii: "\\"): 160 | advance() 161 | if (cur == end) { 162 | throw JSONParseError.invalidStringError( 163 | reason: "unexpected end of a string literal", 164 | lineNumber: lineNumber, 165 | columnNumber: columnNumber 166 | ) 167 | } 168 | 169 | if let c = parseEscapedChar() { 170 | for u in String(c).utf8 { 171 | buffer.append(CChar(bitPattern: u)) 172 | } 173 | } else { 174 | throw JSONParseError.invalidStringError( 175 | reason: "invalid escape sequence", 176 | lineNumber: lineNumber, 177 | columnNumber: columnNumber 178 | ) 179 | } 180 | case Char(ascii: "\""): break LOOP 181 | default: buffer.append(CChar(bitPattern: currentChar)) 182 | } 183 | advance() 184 | } 185 | 186 | if !expect("\"") { 187 | throw JSONParseError.invalidStringError( 188 | reason: "missing double quote", 189 | lineNumber: lineNumber, 190 | columnNumber: columnNumber 191 | ) 192 | } 193 | 194 | buffer.append(0) 195 | let s = String(validatingUTF8: buffer)! 196 | return .string(s) 197 | } 198 | 199 | private func parseEscapedChar() -> UnicodeScalar? { 200 | let c = UnicodeScalar(currentChar) 201 | 202 | if c == "u" { 203 | var length = 0 204 | var value: UInt32 = 0 205 | 206 | while let d = hexToDigit(nextChar) { 207 | advance() 208 | length += 1 209 | 210 | if length > 8 { 211 | break 212 | } 213 | 214 | value = (value << 4) | d 215 | } 216 | 217 | if length < 2 { 218 | return nil 219 | } 220 | 221 | return UnicodeScalar(value) 222 | } else { 223 | let c = UnicodeScalar(currentChar) 224 | return unescapeMapping[c] ?? c 225 | } 226 | } 227 | 228 | private func parseNumber() throws -> JSON { 229 | let sign = expect("-") ? -1.0 : 1.0 230 | var integer: Int64 = 0 231 | 232 | switch currentChar { 233 | case Char(ascii: "0"): advance() 234 | case Char(ascii: "1") ... Char(ascii: "9"): 235 | while cur != end { 236 | if let value = digitToInt(currentChar) { 237 | integer = (integer * 10) + Int64(value) 238 | } else { 239 | break 240 | } 241 | advance() 242 | } 243 | default: 244 | throw JSONParseError.invalidStringError( 245 | reason: "missing double quote", 246 | lineNumber: lineNumber, 247 | columnNumber: columnNumber 248 | ) 249 | } 250 | 251 | if integer != Int64(Double(integer)) { 252 | throw JSONParseError.invalidNumberError( 253 | reason: "too large number", 254 | lineNumber: lineNumber, 255 | columnNumber: columnNumber 256 | ) 257 | } 258 | 259 | var fraction: Double = 0.0 260 | 261 | if expect(".") { 262 | var factor = 0.1 263 | var fractionLength = 0 264 | 265 | while cur != end { 266 | if let value = digitToInt(currentChar) { 267 | fraction += (Double(value) * factor) 268 | factor /= 10 269 | fractionLength += 1 270 | } else { 271 | break 272 | } 273 | advance() 274 | } 275 | 276 | if fractionLength == 0 { 277 | throw JSONParseError.invalidNumberError( 278 | reason: "insufficient fraction part in number", 279 | lineNumber: lineNumber, 280 | columnNumber: columnNumber 281 | ) 282 | } 283 | } 284 | 285 | var exponent: Int64 = 0 286 | 287 | if expect("e") || expect("E") { 288 | var expSign: Int64 = 1 289 | 290 | if expect("-") { 291 | expSign = -1 292 | } else if expect("+") {} 293 | 294 | exponent = 0 295 | var exponentLength = 0 296 | 297 | while cur != end { 298 | if let value = digitToInt(currentChar) { 299 | exponent = (exponent * 10) + Int64(value) 300 | exponentLength += 1 301 | } else { 302 | break 303 | } 304 | advance() 305 | } 306 | 307 | if exponentLength == 0 { 308 | throw JSONParseError.invalidNumberError( 309 | reason: "insufficient exponent part in number", 310 | lineNumber: lineNumber, 311 | columnNumber: columnNumber 312 | ) 313 | } 314 | 315 | exponent *= expSign 316 | } 317 | 318 | return .number(JSON.Number.double(sign * (Double(integer) + fraction) * pow(10, Double(exponent)))) 319 | } 320 | 321 | private func parseObject() throws -> JSON { 322 | assert(currentChar == Char(ascii: "{"), "points \"{\"") 323 | advance() 324 | skipWhitespaces() 325 | var object: [String: JSON] = [:] 326 | 327 | LOOP: while cur != end && !expect("}") { 328 | let keyValue = try parseValue() 329 | 330 | switch keyValue { 331 | case .string(let key): 332 | skipWhitespaces() 333 | 334 | if !expect(":") { 335 | throw JSONParseError.unexpectedTokenError( 336 | reason: "missing colon (:)", 337 | lineNumber: lineNumber, 338 | columnNumber: columnNumber 339 | ) 340 | } 341 | 342 | skipWhitespaces() 343 | let value = try parseValue() 344 | object[key] = value 345 | skipWhitespaces() 346 | 347 | if expect(",") { 348 | break 349 | } else if expect("}") { 350 | break LOOP 351 | } else { 352 | throw JSONParseError.unexpectedTokenError( 353 | reason: "missing comma (,)", 354 | lineNumber: lineNumber, 355 | columnNumber: columnNumber 356 | ) 357 | } 358 | default: 359 | throw JSONParseError.nonStringKeyError( 360 | reason: "unexpected value for object key", 361 | lineNumber: lineNumber, 362 | columnNumber: columnNumber 363 | ) 364 | } 365 | } 366 | 367 | return .object(object) 368 | } 369 | 370 | private func parseArray() throws -> JSON { 371 | assert(currentChar == Char(ascii: "["), "points \"[\"") 372 | advance() 373 | skipWhitespaces() 374 | 375 | var array: [JSON] = [] 376 | 377 | LOOP: while cur != end && !expect("]") { 378 | let JSON = try parseValue() 379 | skipWhitespaces() 380 | array.append(JSON) 381 | 382 | if expect(",") { 383 | continue 384 | } else if expect("]") { 385 | break LOOP 386 | } else { 387 | throw JSONParseError.unexpectedTokenError( 388 | reason: "missing comma (,) (token: \(currentSymbol))", 389 | lineNumber: lineNumber, 390 | columnNumber: columnNumber 391 | ) 392 | } 393 | } 394 | 395 | return .array(array) 396 | } 397 | 398 | 399 | private func expect(_ target: StaticString) -> Bool { 400 | if cur == end { 401 | return false 402 | } 403 | 404 | if !isIdentifier(target.utf8Start.pointee) { 405 | if target.utf8Start.pointee == currentChar { 406 | advance() 407 | return true 408 | } else { 409 | return false 410 | } 411 | } 412 | 413 | let start = cur 414 | let l = lineNumber 415 | let c = columnNumber 416 | 417 | var p = target.utf8Start 418 | let endp = p.advanced(by: Int(target.utf8CodeUnitCount)) 419 | 420 | while p != endp { 421 | if p.pointee != currentChar { 422 | cur = start 423 | lineNumber = l 424 | columnNumber = c 425 | return false 426 | } 427 | p += 1 428 | advance() 429 | } 430 | 431 | return true 432 | } 433 | 434 | // only "true", "false", "null" are identifiers 435 | private func isIdentifier(_ char: Char) -> Bool { 436 | switch char { 437 | case Char(ascii: "a") ... Char(ascii: "z"): 438 | return true 439 | default: 440 | return false 441 | } 442 | } 443 | 444 | private func advance() { 445 | assert(cur != end, "out of range") 446 | cur = source.index(after: cur) 447 | 448 | if cur != end { 449 | switch currentChar { 450 | 451 | case Char(ascii: "\n"): 452 | lineNumber += 1 453 | columnNumber = 1 454 | 455 | default: 456 | columnNumber += 1 457 | } 458 | } 459 | } 460 | 461 | fileprivate func skipWhitespaces() { 462 | while cur != end { 463 | switch currentChar { 464 | case Char(ascii: " "), Char(ascii: "\t"), Char(ascii: "\r"), Char(ascii: "\n"): 465 | break 466 | default: 467 | return 468 | } 469 | advance() 470 | } 471 | } 472 | } 473 | 474 | let unescapeMapping: [UnicodeScalar: UnicodeScalar] = [ 475 | "t": "\t", 476 | "r": "\r", 477 | "n": "\n" 478 | ] 479 | 480 | let escapeMapping: [Character: String] = [ 481 | "\r": "\\r", 482 | "\n": "\\n", 483 | "\t": "\\t", 484 | "\\": "\\\\", 485 | "\"": "\\\"", 486 | 487 | "\u{2028}": "\\u2028", 488 | "\u{2029}": "\\u2029", 489 | 490 | "\r\n": "\\r\\n" 491 | ] 492 | 493 | let hexMapping: [UnicodeScalar: UInt32] = [ 494 | "0": 0x0, 495 | "1": 0x1, 496 | "2": 0x2, 497 | "3": 0x3, 498 | "4": 0x4, 499 | "5": 0x5, 500 | "6": 0x6, 501 | "7": 0x7, 502 | "8": 0x8, 503 | "9": 0x9, 504 | "a": 0xA, "A": 0xA, 505 | "b": 0xB, "B": 0xB, 506 | "c": 0xC, "C": 0xC, 507 | "d": 0xD, "D": 0xD, 508 | "e": 0xE, "E": 0xE, 509 | "f": 0xF, "F": 0xF 510 | ] 511 | 512 | let digitMapping: [UnicodeScalar:Int] = [ 513 | "0": 0, 514 | "1": 1, 515 | "2": 2, 516 | "3": 3, 517 | "4": 4, 518 | "5": 5, 519 | "6": 6, 520 | "7": 7, 521 | "8": 8, 522 | "9": 9 523 | ] 524 | 525 | public func escapeAsJSON(_ source : String) -> String { 526 | var s = "\"" 527 | 528 | for c in source.characters { 529 | if let escapedSymbol = escapeMapping[c] { 530 | s.append(escapedSymbol) 531 | } else { 532 | s.append(c) 533 | } 534 | } 535 | 536 | s.append("\"") 537 | 538 | return s 539 | } 540 | 541 | func digitToInt(_ byte: UInt8) -> Int? { 542 | return digitMapping[UnicodeScalar(byte)] 543 | } 544 | 545 | func hexToDigit(_ byte: UInt8) -> UInt32? { 546 | return hexMapping[UnicodeScalar(byte)] 547 | } 548 | -------------------------------------------------------------------------------- /Source/JSONSerializer.swift: -------------------------------------------------------------------------------- 1 | // JSONSerializer.swift 2 | // 3 | // The MIT License (MIT) 4 | // 5 | // Copyright (c) 2015 Zewo 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | // This file has been modified from its original project Swift-JsonSerializer 26 | 27 | public class JSONSerializer { 28 | public init() {} 29 | 30 | public func serialize(json: JSON) -> Data { 31 | return serializeToString(json: json).data 32 | } 33 | 34 | public func serializeToString(json: JSON) -> String { 35 | switch json { 36 | case .null: return "null" 37 | case .boolean(let b): return String(b) 38 | case .number(let n): return String(describing: n) 39 | case .string(let s): return escapeAsJSON(s) 40 | case .array(let a): return serialize(array: a) 41 | case .object(let o): return serialize(object: o) 42 | } 43 | } 44 | 45 | func serialize(array: [JSON]) -> String { 46 | var s = "[" 47 | 48 | for i in 0 ..< array.count { 49 | s += serializeToString(json: array[i]) 50 | 51 | if i != (array.count - 1) { 52 | s += "," 53 | } 54 | } 55 | 56 | return s + "]" 57 | } 58 | 59 | func serialize(object: [String: JSON]) -> String { 60 | var s = "{" 61 | var i = 0 62 | 63 | for entry in object { 64 | s += "\(escapeAsJSON(entry.0)):\(serialize(json: entry.1))" 65 | if i != (object.count - 1) { 66 | s += "," 67 | } 68 | i += 1 69 | } 70 | 71 | return s + "}" 72 | } 73 | } 74 | 75 | public final class PrettyJSONSerializer: JSONSerializer { 76 | var indentLevel = 0 77 | 78 | override public func serialize(array: [JSON]) -> String { 79 | var s = "[" 80 | indentLevel += 1 81 | 82 | for i in 0 ..< array.count { 83 | s += "\n" 84 | s += indent() 85 | s += serializeToString(json: array[i]) 86 | 87 | if i != (array.count - 1) { 88 | s += "," 89 | } 90 | } 91 | 92 | indentLevel -= 1 93 | return s + "\n" + indent() + "]" 94 | } 95 | 96 | override public func serialize(object: [String: JSON]) -> String { 97 | var s = "{" 98 | indentLevel += 1 99 | var i = 0 100 | 101 | for (key, value) in object { 102 | s += "\n" 103 | s += indent() 104 | s += "\(escapeAsJSON(key)): \(serialize(json: value))" 105 | 106 | if i != (object.count - 1) { 107 | s += "," 108 | } 109 | 110 | i += 1 111 | } 112 | 113 | indentLevel -= 1 114 | return s + "\n" + indent() + "}" 115 | } 116 | 117 | func indent() -> String { 118 | var s = "" 119 | 120 | for _ in 0 ..< indentLevel { 121 | s += " " 122 | } 123 | 124 | return s 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Tests/JSONTests/JSONTests.swift: -------------------------------------------------------------------------------- 1 | @testable import JSON 2 | import XCTest 3 | 4 | class JSONTests: XCTestCase { 5 | func testStringInterpolation() { 6 | 7 | let string: String = "string" 8 | let json: JSON = [ 9 | "key": "\(string)" 10 | ] 11 | 12 | XCTAssertNotNil(json["key"]?.stringValue) 13 | 14 | XCTAssert(json["key"]!.stringValue! == "string") 15 | 16 | let serialized = JSONSerializer().serializeToString(json: json) 17 | XCTAssert(serialized == "{\"key\":\"string\"}") 18 | } 19 | 20 | func testJSONBasicUsage() { 21 | let value = "value" 22 | 23 | var json: JSON = [ 24 | "key": .string(value) 25 | ] 26 | 27 | json["int"] = 3 28 | 29 | XCTAssertEqual(json["key"]?.stringValue, "value") 30 | XCTAssertNotEqual(json["int"]?.doubleValue, Double(3)) 31 | } 32 | } 33 | 34 | extension JSONTests { 35 | static var allTests: [(String, (JSONTests) -> () throws -> Void)] { 36 | return [ 37 | ("testStringInterpolation", testStringInterpolation), 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import JSONTests 4 | 5 | XCTMain([ 6 | testCase(JSONTests.allTests), 7 | ]) 8 | --------------------------------------------------------------------------------