首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Swift JSONEncoder数舍入

Swift JSONEncoder数舍入
EN

Stack Overflow用户
提问于 2020-07-20 12:52:20
回答 1查看 2K关注 0票数 7

与所有IEEE7540系统一样,像4.7这样的Swift中的数字被视为像4.7000000000000002一样的值。所以毫不奇怪:

代码语言:javascript
复制
% swift
Welcome to Apple Swift version 5.2.4 (swiftlang-1103.0.32.9 clang-1103.0.32.53).
Type :help for assistance.
  1> 4.7
$R0: Double = 4.7000000000000002
  2> 4.7 == 4.7000000000000002
$R1: Bool = true

这是一个被充分理解的世界现实,因此不需要用包含关于浮点精度损失的背景文章的链接的评论来处理。

当使用内置JSONEncoder对这个数字进行编码时,我们看到:

代码语言:javascript
复制
  4> String(data: JSONEncoder().encode([4.7]), encoding: .utf8) 
$R2: String? = "[4.7000000000000002]"

这并不是不正确的,因为维基百科说关于JSON &浮点数:

JSON标准不要求实现细节,如溢出、下溢、丢失精度、舍入或签名的零,但它建议不要超过IEEE 754 binary64的精度,以达到“良好的互操作性”。在将浮点数(如binary64)的机器级二进制表示序列化为人类可读的十进制表示(如JSON中的数字)和返回时,不会造成固有的精度损失,因为已经发布的算法可以准确和优化地完成这一任务。

然而,其他JavaScript环境倾向于舍入这些数字。例如用JavaScriptCore:

代码语言:javascript
复制
% /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Helpers/jsc

>>> 4.7 == 4.7000000000000002
true
>>> JSON.stringify([4.7000000000000002])
[4.7]

对于节点:

代码语言:javascript
复制
% node
Welcome to Node.js v13.13.0.
Type ".help" for more information.
> 4.7 == 4.7000000000000002
true
> JSON.stringify([4.7000000000000002])
'[4.7]'

对我来说,问题在于我有大量的Swift加倍集合,当序列化到JSON以进行存储和/或传输时,包含大量不必要的箔条("4.7000000000000002“比”4.7“多6倍),从而大大增大了序列化数据的大小。

有没有人能想到一种很好的方法来覆盖Swift的数字编码,将双数序列化为它们的圆形等价物,而不是放弃自动合成可编码性和手动重新实现整个类型图的编码?

EN

回答 1

Stack Overflow用户

发布于 2020-07-20 14:45:29

您可以扩展KeyedEncodingContainer和KeyedDecodingContainer,并实现自定义编码和解码方法,以将Decimal作为普通数据发送。您只需将编解码器dataEncodingStrategy设置为deferredToData即可。另一种可能是对其base64Data进行编码和解码,或者将其编码/解码为普通字符串。

代码语言:javascript
复制
extension Numeric {
    var data: Data {
        var bytes = self
        return .init(bytes: &bytes, count: MemoryLayout<Self>.size)
    }
}
代码语言:javascript
复制
extension DataProtocol {
    func decode<T: Numeric>(_ codingPath: [CodingKey], key: CodingKey) throws -> T {
        var value: T = .zero
        guard withUnsafeMutableBytes(of: &value, copyBytes) == MemoryLayout.size(ofValue: value) else {
            throw DecodingError.dataCorrupted(.init(codingPath: codingPath, debugDescription: "The key \(key) could not be converted to a numeric value: \(Array(self))"))
        }
        return value
    }
}
代码语言:javascript
复制
extension KeyedEncodingContainer {
    mutating func encode(_ value: Decimal, forKey key: K) throws {
        try encode(value.data, forKey: key)
    }
    mutating func encodeIfPresent(_ value: Decimal?, forKey key: K) throws {
        guard let value = value else { return }
        try encode(value, forKey: key)
    }
}
代码语言:javascript
复制
extension KeyedDecodingContainer {
    func decode(_ type: Decimal.Type, forKey key: K) throws -> Decimal {
        try decode(Data.self, forKey: key).decode(codingPath, key: key)
    }
    func decodeIfPresent(_ type: Decimal.Type, forKey key: K) throws -> Decimal? {
        try decodeIfPresent(Data.self, forKey: key)?.decode(codingPath, key: key)
    }
}

操场测试:

代码语言:javascript
复制
struct Root: Codable {
    let decimal: Decimal
}

// using the string initializer for decimal is required to maintain precision
let root = Root(decimal: Decimal(string: "0.007")!)

do {
    let encoder = JSONEncoder()
    encoder.dataEncodingStrategy = .deferredToData
    let rootData = try encoder.encode(root)
    let decoder = JSONDecoder()
    decoder.dataDecodingStrategy = .deferredToData
    let root = try decoder.decode(Root.self, from: rootData)
    print(root.decimal) // prints "0.007\n" instead of "0.007000000000000001024\n" without the custom encoding and decoding methods
} catch {
    print(error)
}

为了尽可能地保持数据大小,可以将十进制编码和解码为字符串:

代码语言:javascript
复制
extension String {
    func decimal(_ codingPath: [CodingKey], key: CodingKey) throws -> Decimal {
        guard let decimal = Decimal(string: self) else {
            throw DecodingError.dataCorrupted(.init(codingPath: codingPath, debugDescription: "The key \(key) could not be converted to decimal: \(self)"))
        }
        return decimal
    }

}
代码语言:javascript
复制
extension KeyedEncodingContainer {
    mutating func encode(_ value: Decimal, forKey key: K) throws {
        try encode(String(describing: value), forKey: key)
    }
    mutating func encodeIfPresent(_ value: Decimal?, forKey key: K) throws {
        guard let value = value else { return }
        try encode(value, forKey: key)
    }
}
代码语言:javascript
复制
extension KeyedDecodingContainer {
    func decode(_ type: Decimal.Type, forKey key: K) throws -> Decimal {
        try decode(String.self, forKey: key).decimal(codingPath, key: key)
    }
    func decodeIfPresent(_ type: Decimal.Type, forKey key: K) throws -> Decimal? {
        try decodeIfPresent(String.self, forKey: key)?.decimal(codingPath, key: key)
    }
}

操场测试:

代码语言:javascript
复制
struct StringDecimal: Codable {
    let decimal: Decimal
}

let root = StringDecimal(decimal: Decimal(string: "0.007")!)
do {
    let stringDecimalData = try JSONEncoder().encode(root)
    print(String(data: stringDecimalData, encoding: .utf8)!)
    let stringDecimal = try JSONDecoder().decode(StringDecimal.self, from: stringDecimalData)
    print(stringDecimal.decimal) // "0.007\n"
} catch {
    print(error)
}

这个会打印出来

{“十进制”:“0.007”} 0.007

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62995804

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档