草庐IT

关于可编码:如何在 Swift 4 中使用 JSONEncoder 对字典进行编码

codeneng 2023-03-28 原文

How to encode Dictionary with JSONEncoder in Swift 4

我想用 JSONEncoder 将 Dictionary 编码为 json。
它看起来像一个请求,接收一个字典作为参数并将其编码为 json 作为 http 正文。
代码如下所示:

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
let dict = ["name":"abcde"]

protocol Request {
    var params: [String: Encodable] { get set }
    func encode< T >(_ value: T) throws -> Data where T : Encodable
}

extension Request {
    func encode< T >(_ value: T) throws -> Data where T : Encodable {
        return try JSONEncoder().encode(value)
    }

    var body: Data? {
        if let encoded = try? self.encode(self.params) {
            return encoded
        }
        return nil
    }
}

struct BaseRequest: Request {
    var params: [String : Encodable]
}

let req = BaseRequest(params: dict)
let body = req.body

但是这段代码出现错误

Fatal error: Dictionary does not conform to Encodable because Encodable does not conform to itself. You must use a concrete type to encode or decode.

我怎样才能使它可编码?

  • 为什么不使用 JSONSerializer 呢?你想防止 Any 依赖吗?
  • 是的,我需要参数为 [String: Any]
  • 哪些类型最终可以作为字典中的值?这就像不是真正的 Any 而是几种已知类型之一,对吧?通常,最好的解决方案是使用这些类型作为关联值进行枚举,以确认 Encodable
  • 最后我在 Request 中添加了 associatetype


你必须按如下方式引入类型擦除:

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
struct AnyEncodable: Encodable {

    let value: Encodable
    init(value: Encodable) {
        self.value = value
    }

    func encode(to encoder: Encoder) throws {
        try value.encode(to: encoder)
    }

}

struct Model: Encodable {

    var params: [String: AnyEncodable]

}

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let json = try! encoder.encode(
    Model(
        params: [
           "hello" : AnyEncodable.init(value:"world")
        ]
    ).params
)
print(String(data: json, encoding: .utf8))

  • 不幸的是,像这样的类型擦除会阻止编码器在编码之前拦截类型。这意味着如果您尝试对可能具有编码策略的类型(例如 Dates 或 Data)进行编码,它将不会被应用。
  • @ItaiFerber AnyEncodable 装饰它收到的 Encodable 的值。它使用 Encodable 本身定义的实现(每个具体实现的定义不同)。我看不出它是如何破坏不同的编码策略的,除非您尝试输入强制类型转换,这是一个非常糟糕的主意。
  • 此外,如果您需要打破封装,您可以键入 AnyEncodable 的 cast value 参数。
  • 当您通过 JSONEncoder 的容器之一 encode(...) 一个值时,它最终会将该值装箱以进行编码。由于所有的编码方法都是通用的,它知道被编码的内容的类型,并且可以拦截它以应用编码策略。您可以在 box_ 的实现中看到这一点:它检查要应用的特定类型。但是,当您执行 value.encode(to: encoder) 时,您会反转关系,并直接调用底层类型实现。
  • 例如,对于 Date,您最终总是将日期编码为 Double,因为默认情况下 Date 就是这样编码的(实际上,代码会询问"日期,请将自己编码到编码器中",而不是"编码器,请编码这个日期")。 encoder 从来没有看到有一个 Date 因为 Date.encode 是直接调用的,它只是对时间间隔值进行编码。
  • 您可以在此要点中看到此行为:将日期package在 AnyEncodable 中会丢失类型上下文,因此编码器无法应用 DateEncodingStrategy。如果您不使用策略,那么这当然完全可以。它只是一个需要注意的警告,因此您最终不会在同一编码的有效负载中出现冲突的值格式。
  • @ItaiFerber 感谢您指出这一点!我什至不知道编码器存在这种行为。但是无论如何,变异编码器是一个坏主意。为什么不引入两个概念装饰器,如 StandardDateFormattedDate 以在编码器上以不同的方式打印日期。他们可以在 encode 方法中更改编码器的状态,应用自己并将编码器返回到其原始状态。
  • 通常在正确性(即不破坏封装)和有用性之间进行权衡。 JSON 经常被发送到对日期格式有严格要求的服务器(因为 JSON 没有指定日期必须如何编码,每个服务器都是不同的),而且很多时候,您需要对您不拥有且无法影响的类型进行编码— 如果这些类型编码 Dates 而不是 StandardDateFormattedDate,那么您无能为力。由于这种权衡,我们为非常有限的一组类型(目前只有 DateData)提供这些策略。
  • @ItaiFerber 我明白你的推理,但是通过引入打破封装的概念,我们创建了一个积极的反馈循环,我们让开发人员偷工减料,而不是尝试设计方便的 OO 概念来解决严格封装设计的限制。


如果你想定义你的结构符合Codable,你可以这样做:

1
2
3
4
5
6
7
8
struct Model: Codable {
    var param1: String
    var param2: Int
}

let model = Model(param1:"test", param2: 0)
let encoded = try? JSONEncoder().encode(model)
let decoded = try? JSONDecoder().decode(Model.self, from: encoded!)

如果您设置 params: [String: Any],它实际上不会起作用,因为编码器/解码器不知道如何编码/解码 Any,但他们可以为原始类型做到这一点。

如果您需要更多帮助,您应该阅读更多关于新的 Codable 协议的信息。我推荐这个:https://hackernoon.com/everything-about-codable-in-swift-4-97d0e18a2999

  • Timofey Solonins 的回答显示了一种封装 Encodable Any 的好方法,以防您想使用它。但是,您最好准确定义模型使用的类型,这样您就不需要封装它们。

有关关于可编码:如何在 Swift 4 中使用 JSONEncoder 对字典进行编码的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  9. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  10. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

随机推荐