Это чисто артефакт того, как NSNumber
печатает себя.
JSONSerialization
реализован в Objective-C и использует объекты Objective-C (NSDictionary
, NSArray
, NSString
, NSNumber
и т. Д.) Для представления значений, которые он десериализует из вашего JSON. Поскольку JSON содержит пустое число с десятичной точкой в качестве значения для ключа "amount"
, JSONSerialization
анализирует его как double
и упаковывает его в NSNumber
.
Каждый из этих классов Objective C реализует метод description
для печати самого себя.
Объект, возвращаемый JSONSerialization
, является NSDictionary
. String(describing:)
преобразует NSDictionary
в String
, отправив ему метод description
. NSDictionary
реализует description
, отправляя description
каждому из его ключей и значений, включая значение NSNumber
для клавиши "amount"
.
Реализация NSNumber
description
форматирует значение double
с использованием спецификатора printf
%0.16g
. (Я проверил с помощью дизассемблера.) О спецификаторе g
в стандарте C написано
Наконец, если не используется флаг #, любые дробные нули удаляются из дробной части результата, а широкий символ десятичной запятой удаляется, если не осталось дробной части.
Ближайший двойной к 98,39 - это точно 98,3900000000000005684341886080801486968994140625. Таким образом, %0.16g
форматирует это как %0.14f
(см. Стандарт, почему 14, а не 16), что дает "98.39000000000000"
, а затем отбрасывает конечные нули, давая "98.39"
.
Ближайший двойной к 98.40 - это точно 98.400000000000005684341886080801486968994140625. Таким образом, %0.16g
форматирует это как %0.14f
, что дает "98.40000000000001"
(из-за округления), и нет никаких конечных нулей для отсечки.
Вот почему, когда вы печатаете результат JSONSerialization.jsonObject(with:options:)
, вы получаете много дробных цифр для 98.40, но только две цифры для 98.39.
Если вы извлекаете суммы из объекта JSON и конвертируете их в собственный тип Double
Swift, а затем печатаете эти Double
s, вы получаете намного более короткий вывод, потому что Double
реализует более умный алгоритм форматирования, который печатает самая короткая строка, которая при разборе выдает точно такой же Double
.
Попробуйте это:
import Foundation
struct Price: Encodable {
let amount: Decimal
}
func printJSON(from string: String) {
let decimal = Decimal(string: string)!
let price = Price(amount: decimal)
let data = try! JSONEncoder().encode(price)
let jsonString = String(data: data, encoding: .utf8)!
let jso = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
let nsNumber = jso["amount"] as! NSNumber
let double = jso["amount"] as! Double
print("""
Original string: \(string)
json: \(jsonString)
jso: \(jso)
amount as NSNumber: \(nsNumber)
amount as Double: \(double)
""")
}
printJSON(from: "98.39")
printJSON(from: "98.40")
printJSON(from: "98.99")
Результат:
Original string: 98.39
json: {"amount":98.39}
jso: ["amount": 98.39]
amount as NSNumber: 98.39
amount as Double: 98.39
Original string: 98.40
json: {"amount":98.4}
jso: ["amount": 98.40000000000001]
amount as NSNumber: 98.40000000000001
amount as Double: 98.4
Original string: 98.99
json: {"amount":98.99}
jso: ["amount": 98.98999999999999]
amount as NSNumber: 98.98999999999999
amount as Double: 98.99
Обратите внимание, что как фактическая версия JSON (в строках, помеченных json:
), так и версия Swift Double
используют наименьшее количество цифр во всех случаях. В строках, которые используют -[NSNumber description]
(помечены jso:
и amount as NSNumber:
), используются дополнительные цифры для некоторых значений.