FineJSON 提供了 FineJSONEncoder
和 FineJSONDecoder
,它们是比 Codable
更实用的编码器。它们替代了标准 Foundation
的 JSONEncoder
和 JSONDecoder
。这个库旨在帮助解决现实世界中一些奇怪的实际需求。
本节中所有示例代码的工作代码都在 FeaturesTests
中。
解码器允许不必要的末尾逗号。
struct A : Codable, Equatable {
var a: Int
var b: Int
}
func testAllowTrailingComma() throws {
let json = """
[
{
"a": 1,
"b": 2,
},
]
"""
let decoder = FineJSONDecoder()
let x = try decoder.decode([A].self, from: json.data(using: .utf8)!)
XCTAssertEqual(x, [A(a: 1, b: 2)])
}
解码器允许 JSON 中的注释。
struct A : Codable, Equatable {
var a: Int
var b: Int
}
func testComment() throws {
let json = """
[
// entry 1
{
"a": 10,
"b": 20
/*
"a": 1,
"b": 2,
*/
}
]
"""
let decoder = FineJSONDecoder()
let x = try decoder.decode([A].self, from: json.data(using: .utf8)!)
XCTAssertEqual(x, [A(a: 10, b: 20)])
}
解析器错误会告知 JSON 中的位置。行号、列号(以字节偏移量表示)、字节偏移量。
struct A : Codable, Equatable {
var a: Int
var b: Int
}
func testParseErrorLocation() throws {
let json = """
[
{
"a": 1,
"b": 2;
}
]
"""
let decoder = FineJSONDecoder()
do {
_ = try decoder.decode([A].self, from: json.data(using: .utf8)!)
XCTFail()
} catch {
let message = "\(error)"
// invalid character (";") at 4:11(28)
XCTAssertTrue(message.contains("4:11(28)"))
}
}
文件路径也可以传递给解码器。这提升了调试体验。
func testSourceLocationFilePath() throws {
let json = """
{ invalid syntax }
"""
do {
let decoder = FineJSONDecoder()
decoder.file = URL(fileURLWithPath: "resource/dir/info.json")
_ = try decoder.decode(Int.self, from: json.data(using: .utf8)!)
XCTFail("expect throw")
} catch {
let message = "\(error)"
XCTAssertTrue(message.contains("resource/dir/info.json"))
}
}
您可以从 Decoder
获取位置信息。
struct B : Decodable {
var location: SourceLocation?
var name: String
enum CodingKeys : String, CodingKey { case name }
init(from decoder: Decoder) throws {
self.location = decoder.sourceLocation
let c = try decoder.container(keyedBy: CodingKeys.self)
self.name = try c.decode(String.self, forKey: .name)
}
}
func testDecodeLocation() throws {
let json = """
// comment
{
"name": "b"
},
"""
let decoder = FineJSONDecoder()
let x = try decoder.decode(B.self, from: json.data(using: .utf8)!)
XCTAssertEqual(x.location, SourceLocation(offset: 11, line: 2, columnInByte: 1))
XCTAssertEqual(x.name, "b")
}
另请参阅 自动位置信息解码。
编码器保留 JSON 键顺序。
struct A : Codable {
var a: Int
var b: String
var c: Int?
var d: String?
}
func testKeyOrder() throws {
let a = A(a: 1, b: "b", c: 2, d: "d")
let e = FineJSONEncoder()
let json = String(data: try e.encode(a), encoding: .utf8)!
let expected = """
{
"a": 1,
"b": "b",
"c": 2,
"d": "d"
}
"""
XCTAssertEqual(json, expected)
}
Foundation.JSONEncoder
没有定义键顺序。所以您可能会遇到这种情况。
{
"d": "d",
"b": "b",
"c": 2,
"a": 1
}
您可以指定 Optional.none
编码。
默认是键缺失,这与 Foundation
相同。
func testNoneKeyAbsence() throws {
let a = A(a: 1, b: "b", c: nil, d: "d")
let e = FineJSONEncoder()
let json = String(data: try e.encode(a), encoding: .utf8)!
let expected = """
{
"a": 1,
"b": "b",
"d": "d"
}
"""
XCTAssertEqual(json, expected)
}
您可以指定为此类键发出显式的 null。
func testNoneExplicitNull() throws {
let a = A(a: 1, b: "b", c: nil, d: "d")
let e = FineJSONEncoder()
e.optionalEncodingStrategy = .explicitNull
let json = String(data: try e.encode(a), encoding: .utf8)!
let expected = """
{
"a": 1,
"b": "b",
"c": null,
"d": "d"
}
"""
XCTAssertEqual(json, expected)
}
您可以指定缩进宽度。
func testIndent4() throws {
let a = A(a: 1, b: "b", c: 2, d: "d")
let e = FineJSONEncoder()
e.jsonSerializeOptions = JSON.SerializeOptions(indentString: " ")
let json = String(data: try e.encode(a), encoding: .utf8)!
let expected = """
{
"a": 1,
"b": "b",
"c": 2,
"d": "d"
}
"""
XCTAssertEqual(json, expected)
}
也支持单行样式。
func testOnelineFormat() throws {
let a = A(a: 1, b: "b", c: 2, d: "d")
let e = FineJSONEncoder()
e.jsonSerializeOptions = JSON.SerializeOptions(isPrettyPrint: false)
let json = String(data: try e.encode(a), encoding: .utf8)!
let expected = """
{"a":1,"b":"b","c":2,"d":"d"}
"""
XCTAssertEqual(json, expected)
}
漂亮打印是默认设置。
JSON 最初支持任意位数的数字。您可以使用 JSONNumber
类型来处理这种情况。
struct B : Codable {
var x: JSONNumber
var y: JSONNumber
}
func testArbitraryNumber() throws {
let json1 = """
{
"x": 1234567890.1234567890,
"y": 0.01
}
"""
let d = FineJSONDecoder()
var b = try d.decode(B.self, from: json1.data(using: .utf8)!)
var y = Decimal(string: b.y.value)!
y += Decimal(string: "0.01")!
b.y = JSONNumber(y.description)
let e = FineJSONEncoder()
let json2 = String(data: try e.encode(b), encoding: .utf8)!
let expected = """
{
"x": 1234567890.1234567890,
"y": 0.02
}
"""
XCTAssertEqual(json2, expected)
}
Foundation.JSONEncoder
无法做到这一点。所以您可能会在使用 Float
时遇到这种情况。
{
"x": 1234567936,
"y": 0.019999999552965164
}
JSON 数字和字符串在解码期间彼此兼容。
struct C : Codable {
var id: Int
var name: String
}
func testWeakTypingDecoding() throws {
let json = """
{
"id": "123",
"name": 4869.57
}
"""
let d = FineJSONDecoder()
let c = try d.decode(C.self, from: json.data(using: .utf8)!)
XCTAssertEqual(c.id, 123)
XCTAssertEqual(c.name, "4869.57")
}
您可以通过注入符合 CodablePrimitiveJSONDecoder
的对象来定制此行为。
您可以使用 JSON
类型来处理复杂的结构。
struct F : Codable {
var name: String
var data: JSON
}
func testJSONTypeProperty() throws {
let json = """
{
"name": "john",
"data": [
"aaa",
{ "bbb": "ccc" }
]
}
"""
let d = FineJSONDecoder()
let f = try d.decode(F.self, from: json.data(using: .utf8)!)
XCTAssertEqual(f.name, "john")
XCTAssertEqual(f.data, JSON.array(JSONArray([
.string("aaa"),
.object(JSONObject([
"bbb": .string("ccc")
]))
])))
}
您可以为属性自定义 JSON 键,同时保持 Codable
方法的自动合成。
struct G : Codable, JSONAnnotatable {
static let keyAnnotations: JSONKeyAnnotations = [
"id": JSONKeyAnnotation(jsonKey: "no"),
"userName": JSONKeyAnnotation(jsonKey: "user_name")
]
var id: Int
var point: Int
var userName: String
}
func testAnnotateJSONKey() throws {
let json1 = """
{
"no": 1,
"point": 100,
"user_name": "john"
}
"""
let d = FineJSONDecoder()
var g = try d.decode(G.self, from: json1.data(using: .utf8)!)
XCTAssertEqual(g.id, 1)
XCTAssertEqual(g.point, 100)
XCTAssertEqual(g.userName, "john")
g.point += 3
let e = FineJSONEncoder()
let json2 = String(data: try e.encode(g), encoding: .utf8)!
let expect = """
{
"no": 1,
"point": 103,
"user_name": "john"
}
"""
XCTAssertEqual(json2, expect)
}
您可以为属性指定默认值,该默认值在 JSON 键不存在时使用。
struct H : Codable, JSONAnnotatable {
static let keyAnnotations: JSONKeyAnnotations = [
"language": JSONKeyAnnotation(defaultValue: JSON.string("Swift"))
]
var name: String
var language: String
}
func testDefaultValue() throws {
let json = """
{
"name": "john"
}
"""
let d = FineJSONDecoder()
let h = try d.decode(H.self, from: json.data(using: .utf8)!)
XCTAssertEqual(h.name, "john")
XCTAssertEqual(h.language, "Swift")
}
可以通过注解启用位置信息解码。
func testAutoLocationDecoding() throws {
let json = """
// comment
{
"name": "b"
},
"""
let decoder = FineJSONDecoder()
let x = try decoder.decode(C.self, from: json.data(using: .utf8)!)
XCTAssertEqual(x.location, SourceLocation(offset: 11, line: 2, columnInByte: 1))
XCTAssertEqual(x.name, "b")
let encoder = FineJSONEncoder()
let json2 = String(data: try encoder.encode(x), encoding: .utf8)!
XCTAssertEqual(json2, """
{
"name": "b"
}
""")
}
SwiftPM for mac, iOS。
Carthage for mac, iOS。
手动 xcworkspace for mac, iOS。这是我的最爱。 详情请点击这里
此库将 URL
序列化为 JSON 中的对象,而不是字符串。这与 Foundation.JSONEncoder
、`.JSONDecoder` 不同。因为此库对这些类型使用原生编码定义。Foundation
编码器通过遵循其内部自定义编码逻辑将它们序列化为字符串。
MIT。