JSONEncoder를 사용하여 null 값을 null로 인코딩합니다.
Swift 4를 사용하고 있습니다.JSONEncoder
저는...Codable
옵션 속성을 가진 구조입니다.이 속성을 표시해 주세요.null
생성된 JSON 데이터의 값은 다음과 같습니다.nil
.하지만,JSONEncoder
는 속성을 폐기하고 JSON 출력에 추가하지 않습니다.를 설정하는 방법이 있습니까?JSONEncoder
키를 보존해, 로 설정합니다.null
이 경우?
예
다음 코드 조각은 다음을 생성합니다.{"number":1}
하지만, 저는 차라리 제가 이 기회를 줬으면 합니다.{"string":null,"number":1}
:
struct Foo: Codable {
var string: String? = nil
var number: Int = 1
}
let encoder = JSONEncoder()
let data = try! encoder.encode(Foo())
print(String(data: data, encoding: .utf8)!)
네, 하지만 직접 쓰셔야 해요encode(to:)
구현, 자동 생성된 구현은 사용할 수 없습니다.
struct Foo: Codable {
var string: String? = nil
var number: Int = 1
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(number, forKey: .number)
try container.encode(string, forKey: .string)
}
}
옵션을 직접 인코딩하면 원하는 대로 늘이 인코딩됩니다.
이것이 중요한 사용 사례인 경우 bugs.swift.org에서 결함을 열어 새로운 기능을 요청할 수 있습니다.OptionalEncodingStrategy
기존과 일치하도록 JSONEncoder에 추가할 플래그DateEncodingStrategy
(오늘날 Swift에서는 실장이 불가능할 가능성이 높지만, Swift가 진화함에 따라 추적 시스템에 들어가는 것은 여전히 유용합니다.)
편집: 아래 파울로의 질문에 대해, 이 문서는 일반 문서로 발송됩니다.encode<T: Encodable>
버전:Optional
준거하다Encodable
이는 Codable.swift에서 다음과 같이 구현됩니다.
extension Optional : Encodable /* where Wrapped : Encodable */ {
@_inlineable // FIXME(sil-serialize-all)
public func encode(to encoder: Encoder) throws {
assertTypeIsEncodable(Wrapped.self, in: type(of: self))
var container = encoder.singleValueContainer()
switch self {
case .none: try container.encodeNil()
case .some(let wrapped): try (wrapped as! Encodable).__encode(to: &container)
}
}
}
그러면 콜이 로 랩됩니다.encodeNil
stdlib가 옵션들을 단지 다른 Encodable로 취급하는 것이 우리의 인코더 및 콜에서 특별한 케이스로 취급하는 것보다 낫다고 생각합니다.encodeNil
우리자신.
또 다른 분명한 질문은 애초에 왜 이런 방식으로 작동하느냐는 것이다.Optional은 Encodable이고 Encodable 준거로 생성된 Encodable은 모든 속성을 인코딩하므로 "모든 속성을 수동으로 인코딩"이 다르게 작동하는 이유는 무엇입니까?정답은 적합성 생성기에 옵션의 특수한 케이스가 포함되어 있다는 것입니다.
// Now need to generate `try container.encode(x, forKey: .x)` for all
// existing properties. Optional properties get `encodeIfPresent`.
...
if (varType->getAnyNominal() == C.getOptionalDecl() ||
varType->getAnyNominal() == C.getImplicitlyUnwrappedOptionalDecl()) {
methodName = C.Id_encodeIfPresent;
}
즉, 이 동작을 변경하려면 자동 생성된 적합성을 변경해야 합니다.JSONEncoder
(즉, 오늘날의 Swift에서는 설정 가능한 것을 만드는 것은 매우 어려운 일이라는 것입니다.
속성 래퍼를 사용하는 방법은 다음과 같습니다(Swift v5.1 필요).
@propertyWrapper
struct NullEncodable<T>: Encodable where T: Encodable {
var wrappedValue: T?
init(wrappedValue: T?) {
self.wrappedValue = wrappedValue
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch wrappedValue {
case .some(let value): try container.encode(value)
case .none: try container.encodeNil()
}
}
}
사용 예:
struct Tuplet: Encodable {
let a: String
let b: Int
@NullEncodable var c: String? = nil
}
struct Test: Encodable {
@NullEncodable var name: String? = nil
@NullEncodable var description: String? = nil
@NullEncodable var tuplet: Tuplet? = nil
}
var test = Test()
test.tuplet = Tuplet(a: "whee", b: 42)
test.description = "A test"
let data = try JSONEncoder().encode(test)
print(String(data: data, encoding: .utf8) ?? "")
출력:
{
"name": null,
"description": "A test",
"tuplet": {
"a": "whee",
"b": 42,
"c": null
}
}
완전 실장 : https://github.com/g-mark/NullCodable
다음은 프로젝트에서 사용한 접근법입니다.도움이 됐으면 좋겠다.
struct CustomBody: Codable {
let method: String
let params: [Param]
enum CodingKeys: String, CodingKey {
case method = "method"
case params = "params"
}
}
enum Param: Codable {
case bool(Bool)
case integer(Int)
case string(String)
case stringArray([String])
case valueNil
case unsignedInteger(UInt)
case optionalString(String?)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Bool.self) {
self = .bool(x)
return
}
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode([String].self) {
self = .stringArray(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(UInt.self) {
self = .unsignedInteger(x)
return
}
throw DecodingError.typeMismatch(Param.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Param"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let x):
try container.encode(x)
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
case .stringArray(let x):
try container.encode(x)
case .valueNil:
try container.encodeNil()
case .unsignedInteger(let x):
try container.encode(x)
case .optionalString(let x):
x?.isEmpty == true ? try container.encodeNil() : try container.encode(x)
}
}
}
그리고 용도는 이렇습니다.
RequestBody.CustomBody(method: "WSDocMgmt.getDocumentsInContentCategoryBySearchSource", params: [.string(legacyToken), .string(shelfId), .bool(true), .valueNil, .stringArray(queryFrom(filters: filters ?? [])), .optionalString(sortMethodParameters()), .bool(sortMethodAscending()), .unsignedInteger(segment ?? 0), .unsignedInteger(segmentSize ?? 0), .string("NO_PATRON_STATUS")])
동작을 제어하기 위해 이 열거형을 사용합니다.백엔드에서 필요한 작업입니다.
public enum Tristate<Wrapped> : ExpressibleByNilLiteral, Encodable {
/// Null
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
/// Pending value, not none, not some
case pending
/// Creates an instance initialized with .pending.
public init() {
self = .pending
}
/// Creates an instance initialized with .none.
public init(nilLiteral: ()) {
self = .none
}
/// Creates an instance that stores the given value.
public init(_ some: Wrapped) {
self = .some(some)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .none:
try container.encodeNil()
case .some(let wrapped):
try (wrapped as! Encodable).encode(to: encoder)
case .pending: break // do nothing
}
}
}
typealias TriStateString = Tristate<String>
typealias TriStateInt = Tristate<Int>
typealias TriStateBool = Tristate<Bool>
/// 테스트
struct TestStruct: Encodable {
var variablePending: TriStateString?
var variableSome: TriStateString?
var variableNil: TriStateString?
}
/// Structure with tristate strings:
let testStruc = TestStruct(/*variablePending: TriStateString(),*/ // pending, unresolved
variableSome: TriStateString("test"), // some, resolved
variableNil: TriStateString(nil)) // nil, resolved
/// Make the structure also tristate
let tsStruct = Tristate<TestStruct>(testStruc)
/// Make a json from the structure
do {
let jsonData = try JSONEncoder().encode(tsStruct)
print( String(data: jsonData, encoding: .utf8)! )
} catch(let e) {
print(e)
}
/// 출력
{"variableNil":null,"variableSome":"test"}
// variablePending is missing, which is a correct behaviour
저도 같은 문제에 부딪혔어요.JSONEncoder를 사용하지 않고 구조에서 사전을 만들어 해결했습니다.이 작업은 비교적 보편적인 방법으로 수행할 수 있습니다.제 코드는 다음과 같습니다.
struct MyStruct: Codable {
let id: String
let regionsID: Int?
let created: Int
let modified: Int
let removed: Int?
enum CodingKeys: String, CodingKey, CaseIterable {
case id = "id"
case regionsID = "regions_id"
case created = "created"
case modified = "modified"
case removed = "removed"
}
var jsonDictionary: [String : Any] {
let mirror = Mirror(reflecting: self)
var dic = [String: Any]()
var counter = 0
for (name, value) in mirror.children {
let key = CodingKeys.allCases[counter]
dic[key.stringValue] = value
counter += 1
}
return dic
}
}
extension Array where Element == MyStruct {
func jsonArray() -> [[String: Any]] {
var array = [[String:Any]]()
for element in self {
array.append(element.jsonDictionary)
}
return array
}
}
이것은 CodingKeys 없이 실행할 수 있습니다(서버측의 테이블 속성명이 구조 속성명과 같은 경우).이 경우 mirror.children의 '이름'을 사용합니다.
CodingKeys가 필요한 경우 CaseItable 프로토콜을 추가하는 것을 잊지 마십시오.이를 통해 allCases 변수를 사용할 수 있습니다.
중첩된 구조에 주의하십시오. 예를 들어, 사용자 지정 구조를 유형으로 하는 속성이 있는 경우 해당 구조도 사전으로 변환해야 합니다.이것은 for 루프에서 실행할 수 있습니다.
MyStruct 사전 배열을 생성하려면 배열 확장이 필요합니다.
@Peterdk에서 설명한 바와 같이 이 문제에 대한 오류 보고서가 작성되었습니다.
https://bugs.swift.org/browse/SR-9232
향후 출시에서 이 기능이 어떻게 공식 API의 일부가 되어야 하는지를 고집하고 싶다면 언제든지 업 투표해 주십시오.
그리고 (Johan Nordberg에 의해) 이 버그리포트에 기재되어 있듯이, 각각을 고쳐 쓸 필요 없이 이 문제를 처리할 수 있는 라이브러리가 있습니다.encode(to:)
가능한 코드 가능한 구조 구현 ^^
에서는 이 줍니다.NULL
의 값:
import Foundation
import FineJSON
extension URLRequest {
init<T: APIRequest>(apiRequest: T, settings: APISettings) {
// early return in case of main conf failure
guard let finalUrl = URL(string: apiRequest.path, relativeTo: settings.baseURL) else {
fatalError("Bad resourceName: \(apiRequest.path)")
}
// call designated init
self.init(url: finalUrl)
var parametersData: Data? = nil
if let postParams = apiRequest.postParams {
do {
// old code using standard JSONSerializer :/
// parametersData = try JSONSerializer.encode(postParams)
// new code using FineJSON Encoder
let encoder = FineJSONEncoder.init()
// with custom 'optionalEncodingStrategy' ^^
encoder.optionalEncodingStrategy = .explicitNull
parametersData = try encoder.encode(postParams)
// set post params
self.httpBody = parametersData
} catch {
fatalError("Encoding Error: \(error)")
}
}
// set http method
self.httpMethod = apiRequest.httpMethod.rawValue
// set http headers if needed
if let httpHeaders = settings.httpHeaders {
for (key, value) in httpHeaders {
self.setValue(value, forHTTPHeaderField: key)
}
}
}
}
이 문제에 대처하기 위해 필요한 변경은 이것뿐입니다.
훌륭한 lib를 해주신 Omochi씨 감사합니다.
도움이 됐으면 좋겠는데...
언급URL : https://stackoverflow.com/questions/47266862/encode-nil-value-as-null-with-jsonencoder
'sourcetip' 카테고리의 다른 글
온라인 코드 색칠 서비스가 있나요? (0) | 2023.02.13 |
---|---|
jQuery getJ 관련 문제Chrome에서 로컬 파일을 사용하는 SON (0) | 2023.02.13 |
Wordpress 용어 ID로 분류법 이름 가져오기 (0) | 2023.02.13 |
캔 각도JS ng-Keyup 패스는 어떤 키를 눌렀습니까? (0) | 2023.02.09 |
Tomcat이 스프링 부트 응용 프로그램 속성을 읽지 않음 (0) | 2023.02.09 |