1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
@_implementationOnly import CoreFoundation
extension JSONSerialization {
public struct ReadingOptions : OptionSet, Sendable {
public let rawValue: UInt
public init(rawValue: UInt) { self.rawValue = rawValue }
public static let mutableContainers = ReadingOptions(rawValue: 1 << 0)
public static let mutableLeaves = ReadingOptions(rawValue: 1 << 1)
public static let fragmentsAllowed = ReadingOptions(rawValue: 1 << 2)
@available(swift, deprecated: 100000, renamed: "JSONSerialization.ReadingOptions.fragmentsAllowed")
public static let allowFragments = ReadingOptions(rawValue: 1 << 2)
}
public struct WritingOptions : OptionSet, Sendable {
public let rawValue: UInt
public init(rawValue: UInt) { self.rawValue = rawValue }
public static let prettyPrinted = WritingOptions(rawValue: 1 << 0)
public static let sortedKeys = WritingOptions(rawValue: 1 << 1)
public static let fragmentsAllowed = WritingOptions(rawValue: 1 << 2)
public static let withoutEscapingSlashes = WritingOptions(rawValue: 1 << 3)
}
}
extension JSONSerialization {
// Structures with container nesting deeper than this limit are not valid if passed in in-memory for validation, nor if they are read during deserialization.
// This matches Darwin Foundation's validation behavior.
fileprivate static let maximumRecursionDepth = 512
}
/* A class for converting JSON to Foundation/Swift objects and converting Foundation/Swift objects to JSON.
An object that may be converted to JSON must have the following properties:
- Top level object is a `Swift.Array` or `Swift.Dictionary`
- All objects are `Swift.String`, `Foundation.NSNumber`, `Swift.Array`, `Swift.Dictionary`,
or `Foundation.NSNull`
- All dictionary keys are `Swift.String`s
- `NSNumber`s are not NaN or infinity
*/
@available(*, unavailable)
extension JSONSerialization : @unchecked Sendable { }
open class JSONSerialization : NSObject {
/* Determines whether the given object can be converted to JSON.
Other rules may apply. Calling this method or attempting a conversion are the definitive ways
to tell if a given object can be converted to JSON data.
- parameter obj: The object to test.
- returns: `true` if `obj` can be converted to JSON, otherwise `false`.
*/
open class func isValidJSONObject(_ obj: Any) -> Bool {
var recursionDepth = 0
// TODO: - revisit this once bridging story gets fully figured out
func isValidJSONObjectInternal(_ obj: Any?) -> Bool {
// Match Darwin Foundation in not considering a deep object valid.
guard recursionDepth < JSONSerialization.maximumRecursionDepth else { return false }
recursionDepth += 1
defer { recursionDepth -= 1 }
// Emulate the SE-0140 behavior bridging behavior for nils
guard let obj = obj else {
return true
}
if !(obj is _NSNumberCastingWithoutBridging) {
if obj is String || obj is NSNull || obj is Int || obj is Bool || obj is UInt ||
obj is Int8 || obj is Int16 || obj is Int32 || obj is Int64 ||
obj is UInt8 || obj is UInt16 || obj is UInt32 || obj is UInt64 {
return true
}
}
// object is a Double and is not NaN or infinity
if let number = obj as? Double {
return number.isFinite
}
// object is a Float and is not NaN or infinity
if let number = obj as? Float {
return number.isFinite
}
if let number = obj as? Decimal {
return number.isFinite
}
// object is Swift.Array
if let array = obj as? [Any?] {
for element in array {
guard isValidJSONObjectInternal(element) else {
return false
}
}
return true
}
// object is Swift.Dictionary
if let dictionary = obj as? [String: Any?] {
for (_, value) in dictionary {
guard isValidJSONObjectInternal(value) else {
return false
}
}
return true
}
// object is NSNumber and is not NaN or infinity
// For better performance, this (most expensive) test should be last.
if let number = __SwiftValue.store(obj) as? NSNumber {
if CFNumberIsFloatType(number._cfObject) {
let dv = number.doubleValue
let invalid = dv.isInfinite || dv.isNaN
return !invalid
} else {
return true
}
}
// invalid object
return false
}
// top level object must be an Swift.Array or Swift.Dictionary
guard obj is [Any?] || obj is [String: Any?] else {
return false
}
return isValidJSONObjectInternal(obj)
}
/* Generate JSON data from a Foundation object. If the object will not produce valid JSON then an exception will be thrown. Setting the NSJSONWritingPrettyPrinted option will generate JSON with whitespace designed to make the output more readable. If that option is not set, the most compact possible JSON will be generated. If an error occurs, the error parameter will be set and the return value will be nil. The resulting data is a encoded in UTF-8.
*/
internal class func _data(withJSONObject value: Any, options opt: WritingOptions, stream: Bool) throws -> Data {
var jsonStr = [UInt8]()
var writer = JSONWriter(
options: opt,
writer: { (str: String?) in
if let str = str {
jsonStr.append(contentsOf: str.utf8)
}
}
)
if let container = value as? NSArray {
try writer.serializeJSON(container._bridgeToSwift())
} else if let container = value as? NSDictionary {
try writer.serializeJSON(container._bridgeToSwift())
} else if let container = value as? Array<Any> {
try writer.serializeJSON(container)
} else if let container = value as? Dictionary<AnyHashable, Any> {
try writer.serializeJSON(container)
} else {
guard opt.contains(.fragmentsAllowed) else {
fatalError("Top-level object was not NSArray or NSDictionary") // This is a fatal error in objective-c too (it is an NSInvalidArgumentException)
}
try writer.serializeJSON(value)
}
let count = jsonStr.count
return Data(bytes: &jsonStr, count: count)
}
open class func data(withJSONObject value: Any, options opt: WritingOptions = []) throws -> Data {
return try _data(withJSONObject: value, options: opt, stream: false)
}
/* Create a Foundation object from JSON data. Set the NSJSONReadingAllowFragments option if the parser should allow top-level objects that are not an NSArray or NSDictionary. Setting the NSJSONReadingMutableContainers option will make the parser generate mutable NSArrays and NSDictionaries. Setting the NSJSONReadingMutableLeaves option will make the parser generate mutable NSString objects. If an error occurs during the parse, then the error parameter will be set and the result will be nil.
The data must be in one of the 5 supported encodings listed in the JSON specification: UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE. The data may or may not have a BOM. The most efficient encoding to use for parsing is UTF-8, so if you have a choice in encoding the data passed to this method, use UTF-8.
*/
open class func jsonObject(with data: Data, options opt: ReadingOptions = []) throws -> Any {
do {
let jsonValue = try data.withUnsafeBytes { (ptr) -> JSONValue in
let (encoding, advanceBy) = JSONSerialization.detectEncoding(ptr)
if encoding == .utf8 {
// we got utf8... happy path
var parser = JSONParser(bytes: Array(ptr[advanceBy..<ptr.count]))
return try parser.parse()
}
guard let utf8String = String(bytes: ptr[advanceBy..<ptr.count], encoding: encoding) else {
throw JSONError.cannotConvertInputDataToUTF8
}
var parser = JSONParser(bytes: Array(utf8String.utf8))
return try parser.parse()
}
if jsonValue.isValue, !opt.contains(.fragmentsAllowed) {
throw JSONError.singleFragmentFoundButNotAllowed
}
return try jsonValue.toObjcRepresentation(options: opt)
} catch let error as JSONError {
switch error {
case .cannotConvertInputDataToUTF8:
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : "Cannot convert input string to valid utf8 input."
])
case .unexpectedEndOfFile:
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : "Unexpected end of file during JSON parse."
])
case .unexpectedCharacter(_, let characterIndex):
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : "Invalid value around character \(characterIndex)."
])
case .expectedLowSurrogateUTF8SequenceAfterHighSurrogate:
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : "Unexpected end of file during string parse (expected low-surrogate code point but did not find one)."
])
case .couldNotCreateUnicodeScalarFromUInt32:
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : "Unable to convert hex escape sequence (no high character) to UTF8-encoded character."
])
case .unexpectedEscapedCharacter(_, _, let index):
// we lower the failure index by one to match the darwin implementations counting
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : "Invalid escape sequence around character \(index - 1)."
])
case .singleFragmentFoundButNotAllowed:
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : "JSON text did not start with array or object and option to allow fragments not set."
])
case .tooManyNestedArraysOrDictionaries(characterIndex: let characterIndex):
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : "Too many nested arrays or dictionaries around character \(characterIndex + 1)."
])
case .invalidHexDigitSequence(let string, index: let index):
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : #"Invalid hex encoded sequence in "\#(string)" at \#(index)."#
])
case .unescapedControlCharacterInString(ascii: let ascii, in: _, index: let index) where ascii == UInt8(ascii: "\\"):
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : #"Invalid escape sequence around character \#(index)."#
])
case .unescapedControlCharacterInString(ascii: _, in: _, index: let index):
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : #"Unescaped control character around character \#(index)."#
])
case .numberWithLeadingZero(index: let index):
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : #"Number with leading zero around character \#(index)."#
])
case .numberIsNotRepresentableInSwift(parsed: let parsed):
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : #"Number \#(parsed) is not representable in Swift."#
])
case .invalidUTF8Sequence(let data, characterIndex: let index):
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [
NSDebugDescriptionErrorKey : #"Invalid UTF-8 sequence \#(data) starting from character \#(index)."#
])
}
} catch {
preconditionFailure("Only `JSONError` expected")
}
}
#if !os(WASI)
/* Write JSON data into a stream. The stream should be opened and configured. The return value is the number of bytes written to the stream, or 0 on error. All other behavior of this method is the same as the dataWithJSONObject:options:error: method.
*/
open class func writeJSONObject(_ obj: Any, toStream stream: OutputStream, options opt: WritingOptions) throws -> Int {
let jsonData = try _data(withJSONObject: obj, options: opt, stream: true)
return jsonData.withUnsafeBytes { (rawBuffer: UnsafeRawBufferPointer) -> Int in
let ptr = rawBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
let res: Int = stream.write(ptr, maxLength: rawBuffer.count)
/// TODO: If the result here is negative the error should be obtained from the stream to propagate as a throw
return res
}
}
/* Create a JSON object from JSON data stream. The stream should be opened and configured. All other behavior of this method is the same as the JSONObjectWithData:options:error: method.
*/
open class func jsonObject(with stream: InputStream, options opt: ReadingOptions = []) throws -> Any {
var data = Data()
guard stream.streamStatus == .open || stream.streamStatus == .reading else {
fatalError("Stream is not available for reading")
}
repeat {
let buffer = try [UInt8](unsafeUninitializedCapacity: 1024) { buf, initializedCount in
let bytesRead = stream.read(buf.baseAddress!, maxLength: buf.count)
initializedCount = bytesRead
guard bytesRead >= 0 else {
throw stream.streamError!
}
}
data.append(buffer, count: buffer.count)
} while stream.hasBytesAvailable
return try jsonObject(with: data, options: opt)
}
#endif
}
//MARK: - Encoding Detection
private extension JSONSerialization {
/// Detect the encoding format of the NSData contents
static func detectEncoding(_ bytes: UnsafeRawBufferPointer) -> (String.Encoding, Int) {
// According to RFC8259, the text encoding in JSON must be UTF8 in nonclosed systems
// https://tools.ietf.org/html/rfc8259#section-8.1
// However, since Darwin Foundation supports utf16 and utf32, so should Swift Foundation.
// First let's check if we can determine the encoding based on a leading Byte Ordering Mark
// (BOM).
if bytes.count >= 4 {
if bytes.starts(with: Self.utf8BOM) {
return (.utf8, 3)
}
if bytes.starts(with: Self.utf32BigEndianBOM) {
return (.utf32BigEndian, 4)
}
if bytes.starts(with: Self.utf32LittleEndianBOM) {
return (.utf32LittleEndian, 4)
}
if bytes.starts(with: [0xFF, 0xFE]) {
return (.utf16LittleEndian, 2)
}
if bytes.starts(with: [0xFE, 0xFF]) {
return (.utf16BigEndian, 2)
}
}
// If there is no BOM present, we might be able to determine the encoding based on
// occurences of null bytes.
if bytes.count >= 4 {
switch (bytes[0], bytes[1], bytes[2], bytes[3]) {
case (0, 0, 0, _):
return (.utf32BigEndian, 0)
case (_, 0, 0, 0):
return (.utf32LittleEndian, 0)
case (0, _, 0, _):
return (.utf16BigEndian, 0)
case (_, 0, _, 0):
return (.utf16LittleEndian, 0)
default:
break
}
}
else if bytes.count >= 2 {
switch (bytes[0], bytes[1]) {
case (0, _):
return (.utf16BigEndian, 0)
case (_, 0):
return (.utf16LittleEndian, 0)
default:
break
}
}
return (.utf8, 0)
}
static func parseBOM(_ bytes: UnsafeRawBufferPointer) -> (encoding: String.Encoding, skipLength: Int)? {
return nil
}
// These static properties don't look very nice, but we need them to
// workaround: https://bugs.swift.org/browse/SR-14102
private static let utf8BOM: [UInt8] = [0xEF, 0xBB, 0xBF]
private static let utf32BigEndianBOM: [UInt8] = [0x00, 0x00, 0xFE, 0xFF]
private static let utf32LittleEndianBOM: [UInt8] = [0xFF, 0xFE, 0x00, 0x00]
private static let utf16BigEndianBOM: [UInt8] = [0xFF, 0xFE]
private static let utf16LittleEndianBOM: [UInt8] = [0xFE, 0xFF]
}
//MARK: - JSONSerializer
private struct JSONWriter {
var indent = 0
let pretty: Bool
let sortedKeys: Bool
let withoutEscapingSlashes: Bool
let writer: (String?) -> Void
init(options: JSONSerialization.WritingOptions, writer: @escaping (String?) -> Void) {
pretty = options.contains(.prettyPrinted)
sortedKeys = options.contains(.sortedKeys)
withoutEscapingSlashes = options.contains(.withoutEscapingSlashes)
self.writer = writer
}
mutating func serializeJSON(_ object: Any?) throws {
var toSerialize = object
if let number = toSerialize as? _NSNumberCastingWithoutBridging {
toSerialize = number._swiftValueOfOptimalType
}
guard let obj = toSerialize else {
try serializeNull()
return
}
// For better performance, the most expensive conditions to evaluate should be last.
switch (obj) {
case let str as String:
try serializeString(str)
case let boolValue as Bool:
writer(boolValue.description)
case let num as Int:
writer(num.description)
case let num as Int8:
writer(num.description)
case let num as Int16:
writer(num.description)
case let num as Int32:
writer(num.description)
case let num as Int64:
writer(num.description)
case let num as UInt:
writer(num.description)
case let num as UInt8:
writer(num.description)
case let num as UInt16:
writer(num.description)
case let num as UInt32:
writer(num.description)
case let num as UInt64:
writer(num.description)
case let array as Array<Any?>:
try serializeArray(array)
case let dict as Dictionary<AnyHashable, Any?>:
try serializeDictionary(dict)
case let num as Float:
try serializeFloat(num)
case let num as Double:
try serializeFloat(num)
case let num as Decimal:
writer(num.description)
case let num as NSDecimalNumber:
writer(num.description)
case is NSNull:
try serializeNull()
case _ where __SwiftValue.store(obj) is NSNumber:
let num = __SwiftValue.store(obj) as! NSNumber
writer(num.description)
default:
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [NSDebugDescriptionErrorKey : "Invalid object cannot be serialized"])
}
}
func serializeString(_ str: String) throws {
writer("\"")
for scalar in str.unicodeScalars {
switch scalar {
case "\"":
writer("\\\"") // U+0022 quotation mark
case "\\":
writer("\\\\") // U+005C reverse solidus
case "/":
if !withoutEscapingSlashes { writer("\\") }
writer("/") // U+002F solidus
case "\u{8}":
writer("\\b") // U+0008 backspace
case "\u{c}":
writer("\\f") // U+000C form feed
case "\n":
writer("\\n") // U+000A line feed
case "\r":
writer("\\r") // U+000D carriage return
case "\t":
writer("\\t") // U+0009 tab
case "\u{0}"..."\u{f}":
writer("\\u000\(String(scalar.value, radix: 16))") // U+0000 to U+000F
case "\u{10}"..."\u{1f}":
writer("\\u00\(String(scalar.value, radix: 16))") // U+0010 to U+001F
default:
writer(String(scalar))
}
}
writer("\"")
}
private func serializeFloat<T: FloatingPoint & LosslessStringConvertible>(_ num: T) throws {
guard num.isFinite else {
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [NSDebugDescriptionErrorKey : "Invalid number value (\(num)) in JSON write"])
}
var str = num.description
if str.hasSuffix(".0") {
str.removeLast(2)
}
writer(str)
}
mutating func serializeNumber(_ num: NSNumber) throws {
if CFNumberIsFloatType(num._cfObject) {
try serializeFloat(num.doubleValue)
} else {
switch num._cfTypeID {
case CFBooleanGetTypeID():
writer(num.boolValue.description)
default:
writer(num.stringValue)
}
}
}
mutating func serializeArray(_ array: [Any?]) throws {
writer("[")
if pretty {
writer("\n")
incIndent()
}
var first = true
for elem in array {
if first {
first = false
} else if pretty {
writer(",\n")
} else {
writer(",")
}
if pretty {
writeIndent()
}
try serializeJSON(elem)
}
if pretty {
writer("\n")
decAndWriteIndent()
}
writer("]")
}
mutating func serializeDictionary(_ dict: Dictionary<AnyHashable, Any?>) throws {
writer("{")
if pretty {
writer("\n")
incIndent()
if dict.count > 0 {
writeIndent()
}
}
var first = true
func serializeDictionaryElement(key: AnyHashable, value: Any?) throws {
if first {
first = false
} else if pretty {
writer(",\n")
writeIndent()
} else {
writer(",")
}
if let key = key as? String {
try serializeString(key)
} else {
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [NSDebugDescriptionErrorKey : "NSDictionary key must be NSString"])
}
pretty ? writer(" : ") : writer(":")
try serializeJSON(value)
}
if sortedKeys {
let elems = try dict.sorted(by: { a, b in
guard let a = a.key as? String,
let b = b.key as? String else {
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.propertyListReadCorrupt.rawValue, userInfo: [NSDebugDescriptionErrorKey : "NSDictionary key must be NSString"])
}
let options: NSString.CompareOptions = [.numeric, .caseInsensitive, .forcedOrdering]
let range: Range<String.Index> = a.startIndex..<a.endIndex
let locale = NSLocale.system
return a.compare(b, options: options, range: range, locale: locale) == .orderedAscending
})
for elem in elems {
try serializeDictionaryElement(key: elem.key, value: elem.value)
}
} else {
for (key, value) in dict {
try serializeDictionaryElement(key: key, value: value)
}
}
if pretty {
writer("\n")
decAndWriteIndent()
}
writer("}")
}
func serializeNull() throws {
writer("null")
}
let indentAmount = 2
mutating func incIndent() {
indent += indentAmount
}
mutating func incAndWriteIndent() {
indent += indentAmount
writeIndent()
}
mutating func decAndWriteIndent() {
indent -= indentAmount
writeIndent()
}
func writeIndent() {
for _ in 0..<indent {
writer(" ")
}
}
}
enum JSONValue: Equatable {
case string(String)
case number(String)
case bool(Bool)
case null
case array([JSONValue])
case object([String: JSONValue])
}
extension JSONValue {
var isValue: Bool {
switch self {
case .array, .object:
return false
case .null, .number, .string, .bool:
return true
}
}
var isContainer: Bool {
switch self {
case .array, .object:
return true
case .null, .number, .string, .bool:
return false
}
}
}
extension JSONValue {
var debugDataTypeDescription: String {
switch self {
case .array:
return "an array"
case .bool:
return "bool"
case .number:
return "a number"
case .string:
return "a string"
case .object:
return "a dictionary"
case .null:
return "null"
}
}
}
private extension JSONValue {
func toObjcRepresentation(options: JSONSerialization.ReadingOptions) throws -> Any {
switch self {
case .array(let values):
let array = try values.map { try $0.toObjcRepresentation(options: options) }
if !options.contains(.mutableContainers) {
return array
}
return NSMutableArray(array: array, copyItems: false)
case .object(let object):
let dictionary = try object.mapValues { try $0.toObjcRepresentation(options: options) }
if !options.contains(.mutableContainers) {
return dictionary
}
return NSMutableDictionary(dictionary: dictionary, copyItems: false)
case .bool(let bool):
return NSNumber(value: bool)
case .number(let string):
guard let number = NSNumber.fromJSONNumber(string) else {
throw JSONError.numberIsNotRepresentableInSwift(parsed: string)
}
return number
case .null:
return NSNull()
case .string(let string):
if options.contains(.mutableLeaves) {
return NSMutableString(string: string)
}
return string
}
}
}
extension NSNumber {
static func fromJSONNumber(_ string: String) -> NSNumber? {
let decIndex = string.firstIndex(of: ".")
let expIndex = string.firstIndex(of: "e")
let isInteger = decIndex == nil && expIndex == nil
let isNegative = string.utf8[string.utf8.startIndex] == UInt8(ascii: "-")
let digitCount = string[string.startIndex..<(expIndex ?? string.endIndex)].count
// Try Int64() or UInt64() first
if isInteger {
if isNegative {
if digitCount <= 19, let intValue = Int64(string) {
return NSNumber(value: intValue)
}
} else {
if digitCount <= 20, let uintValue = UInt64(string) {
return NSNumber(value: uintValue)
}
}
}
var exp = 0
if let expIndex = expIndex {
let expStartIndex = string.index(after: expIndex)
if let parsed = Int(string[expStartIndex...]) {
exp = parsed
}
}
// Decimal holds more digits of precision but a smaller exponent than Double
// so try that if the exponent fits and there are more digits than Double can hold
if digitCount > 17, exp >= -128, exp <= 127, let decimal = Decimal(string: string), decimal.isFinite {
return NSDecimalNumber(decimal: decimal)
}
// Fall back to Double() for everything else
if let doubleValue = Double(string), doubleValue.isFinite {
return NSNumber(value: doubleValue)
}
return nil
}
}
|