Skip to content

Commit 99ad71b

Browse files
committed
feat: support path argument when decoding
1 parent 8439914 commit 99ad71b

File tree

2 files changed

+67
-25
lines changed

2 files changed

+67
-25
lines changed

Sources/ReerJSON/ReerJSONDecoder.swift

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,11 @@ open class ReerJSONDecoder {
143143
///
144144
/// - parameter type: The type of the value to decode.
145145
/// - parameter data: The data to decode from.
146+
/// - parameter path: The decoding container path.
146147
/// - returns: A value of the requested type.
147148
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
148149
/// - throws: An error if any value throws an error during decoding.
149-
open func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
150+
open func decode<T: Decodable>(_ type: T.Type, from data: Data, path: [String] = []) throws -> T {
150151
let doc = data.withUnsafeBytes {
151152
yyjson_read($0.bindMemory(to: CChar.self).baseAddress, data.count, YYJSON_READ_NUMBER_AS_RAW)
152153
}
@@ -158,7 +159,42 @@ open class ReerJSONDecoder {
158159
yyjson_doc_free(doc)
159160
}
160161

161-
let json = JSON(pointer: yyjson_doc_get_root(doc))
162+
var pointer = yyjson_doc_get_root(doc)
163+
for key in path {
164+
pointer = yyjson_obj_get(pointer, key)
165+
}
166+
167+
let json = JSON(pointer: pointer)
168+
let impl = JSONDecoderImpl(json: json, userInfo: userInfo, codingPathNode: .root, options: options)
169+
return try impl.unbox(json, as: type, for: .root, _CodingKey?.none)
170+
}
171+
172+
/// Decodes a top-level value of the given type from the given JSON representation.
173+
///
174+
/// - parameter type: The type of the value to decode.
175+
/// - parameter data: The data to decode from.
176+
/// - parameter keyPath: The decoding container path, "user.info".
177+
/// - returns: A value of the requested type.
178+
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
179+
/// - throws: An error if any value throws an error during decoding.
180+
open func decode<T: Decodable>(_ type: T.Type, from data: Data, keyPath: String) throws -> T {
181+
let doc = data.withUnsafeBytes {
182+
yyjson_read($0.bindMemory(to: CChar.self).baseAddress, data.count, YYJSON_READ_NUMBER_AS_RAW)
183+
}
184+
guard let doc else {
185+
return try decodeWithFoundationDecoder(type, from: data)
186+
}
187+
188+
defer {
189+
yyjson_doc_free(doc)
190+
}
191+
192+
var pointer = yyjson_doc_get_root(doc)
193+
for key in keyPath.components(separatedBy: CharacterSet(charactersIn: ".")) {
194+
pointer = yyjson_obj_get(pointer, key)
195+
}
196+
197+
let json = JSON(pointer: pointer)
162198
let impl = JSONDecoderImpl(json: json, userInfo: userInfo, codingPathNode: .root, options: options)
163199
return try impl.unbox(json, as: type, for: .root, _CodingKey?.none)
164200
}

Tests/ReerJSONTests/ReerJSONDecoderTests.swift

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -668,11 +668,11 @@ class ReerJSONTests: XCTestCase {
668668
struct Test: Codable, Equatable {
669669
let a: Bool
670670
}
671-
//testRoundTrip(of: Test.self, json: #"{"a": true}"#)
672-
//testRoundTrip(of: TopLevelWrapper<Test>.self, json: #"{"value": {"a": true}}"#)
673-
//_testFailure(of: Test.self, json: #"{"b": true}"#, expectedError: DecodingError.keyNotFound(_CodingKey(stringValue: "a")!, DecodingError.Context(codingPath: [], debugDescription: "No value associated with a.")))
674-
//_testFailure(of: Test.self, json: #"{}"#, expectedError: DecodingError.keyNotFound(_CodingKey(stringValue: "a")!, DecodingError.Context(codingPath: [], debugDescription: "No value associated with a.")))
675-
//_testFailure(of: TopLevelWrapper<Test>.self, json: #"{"value": {}}"#, expectedError: DecodingError.keyNotFound(_CodingKey(stringValue: "a")!, DecodingError.Context(codingPath: [], debugDescription: "No value associated with a.")))
671+
testRoundTrip(of: Test.self, json: #"{"a": true}"#)
672+
testRoundTrip(of: TopLevelWrapper<Test>.self, json: #"{"value": {"a": true}}"#)
673+
_testFailure(of: Test.self, json: #"{"b": true}"#, expectedError: DecodingError.keyNotFound(_CodingKey(stringValue: "a")!, DecodingError.Context(codingPath: [], debugDescription: "No value associated with a.")))
674+
_testFailure(of: Test.self, json: #"{}"#, expectedError: DecodingError.keyNotFound(_CodingKey(stringValue: "a")!, DecodingError.Context(codingPath: [], debugDescription: "No value associated with a.")))
675+
_testFailure(of: TopLevelWrapper<Test>.self, json: #"{"value": {}}"#, expectedError: DecodingError.keyNotFound(_CodingKey(stringValue: "a")!, DecodingError.Context(codingPath: [], debugDescription: "No value associated with a.")))
676676
_testFailure(of: TopLevelWrapper<Test>.self, json: #"{"value": {"b": true}}"#, expectedError: nil) //DecodingError.keyNotFound(_CodingKey(stringValue: "a")!, DecodingError.Context(codingPath: [_CodingKey(stringValue: "value")!], debugDescription: "No value associated with a.")))
677677
}
678678

@@ -864,24 +864,6 @@ class ReerJSONTests: XCTestCase {
864864
return Decimal(string: numberString)!
865865
}
866866
testRoundTrip(decimals)
867-
// NSDecimalNumber doesn't conform to Decodable
868-
//let nsDecimals: [NSDecimalNumber] = [1.2, 1]
869-
//testRoundTrip(nsDecimals)
870-
871-
/*struct Aa: Equatable & Codable {
872-
init(from decoder: Decoder) throws {
873-
//let value = (decoder as! __JSONDecoder)
874-
var outLength: Int32 = 0
875-
let string = "{}"
876-
string.withCString { (cString) -> Void in
877-
let context = JNTCreateContext(cString, UInt32(string.count), "".utf8CString, "".utf8CString, "".utf8CString)
878-
let value = JNTDocumentFromJSON(context, UnsafeRawPointer(cString), string.count, false, nil, true)
879-
JNTDocumentDecode__DecimalString(value, &outLength)
880-
}
881-
}
882-
}*/
883-
884-
//_testFailure(of: Aa.self, json: "{}", expectedError: DecodingError.dataCorrupted(DecodingError.Context(codingPath: [_CodingKey(index: 0)], debugDescription: "Invalid Decimal")))
885867

886868
_testFailure(of: [Decimal].self, json: "[true]", expectedError: DecodingError.dataCorrupted(DecodingError.Context(codingPath: [_CodingKey(index: 0)], debugDescription: "Invalid Decimal")))
887869
}
@@ -941,4 +923,28 @@ class ReerJSONTests: XCTestCase {
941923
run("twitter", Twitter.self, keyDecoding: .convertFromSnakeCase)
942924
run("twitterescaped", Twitter.self)
943925
}
926+
927+
func testPath() throws {
928+
struct Test: Decodable {
929+
let c: String
930+
}
931+
let data = """
932+
{"a": {"b": {"c": "ddd"}}}
933+
""".data(using: .utf8)!
934+
let model = try ReerJSONDecoder().decode(Test.self, from: data, path: ["a", "b"])
935+
XCTAssert(model.c == "ddd")
936+
let model3 = try ReerJSONDecoder().decode(Test.self, from: data, keyPath: "a.b")
937+
XCTAssert(model3.c == "ddd")
938+
939+
struct Test2: Decodable {
940+
let c: [Int]
941+
}
942+
let data2 = """
943+
{"a": {"b": {"c": [1,2,3]}}}
944+
""".data(using: .utf8)!
945+
let model2 = try ReerJSONDecoder().decode(Test2.self, from: data2, path: ["a", "b"])
946+
XCTAssert(model2.c == [1, 2, 3])
947+
let model4 = try ReerJSONDecoder().decode(Test2.self, from: data2, keyPath: "a.b")
948+
XCTAssert(model4.c == [1, 2, 3])
949+
}
944950
}

0 commit comments

Comments
 (0)