Skip to content

Repository files navigation

FineJSON

FineJSON provides FineJSONEncoder and FineJSONDecoder which are more useful encoder of Codable. They alternates standard Foundation's JSONEncoder and JSONDecoder. This library helps practical requirements in real world which is weird sometime.

Index

Features

Working code of all example code in this section are in FeaturesTests.

Allowing unnecessary trailing commas

Decoder allows unnecessary trailing comma.

structA:Codable,Equatable{vara:Intvarb:Int}func testAllowTrailingComma()throws{letjson="""[ {"a": 1,"b": 2, },]"""letdecoder=FineJSONDecoder()letx=try decoder.decode([A].self, from: json.data(using:.utf8)!)XCTAssertEqual(x,[A(a:1, b:2)])}

Allowing comments

Decoder allows comments in JSON.

structA:Codable,Equatable{vara:Intvarb:Int}func testComment()throws{letjson="""[ // entry 1 {"a": 10,"b": 20/*"a": 1,"b": 2,*/ }]"""letdecoder=FineJSONDecoder()letx=try decoder.decode([A].self, from: json.data(using:.utf8)!)XCTAssertEqual(x,[A(a:10, b:20)])}

Line number information in parse error

Parser error tells location in JSON. line number, column number (in byte offset), byte offset.

structA:Codable,Equatable{vara:Intvarb:Int}func testParseErrorLocation()throws{letjson="""[ {"a": 1,"b": 2; }]"""letdecoder=FineJSONDecoder()do{
_ =try decoder.decode([A].self, from: json.data(using:.utf8)!)XCTFail()}catch{letmessage="\(error)"
// invalid character (";") at 4:11(28)
XCTAssertTrue(message.contains("4:11(28)"))}}

File path also can be passed to decoder. It improves debugging experience.

 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"))
}
}

Location information from decoder

You can get location information from Decoder.

structB:Decodable{varlocation:SourceLocation?varname:StringenumCodingKeys:String,CodingKey{case name }init(from decoder:Decoder)throws{self.location = decoder.sourceLocation
letc=try decoder.container(keyedBy:CodingKeys.self)self.name =try c.decode(String.self, forKey:.name)}}func testDecodeLocation()throws{letjson="""// comment{"name": "b"},"""letdecoder=FineJSONDecoder()letx=try decoder.decode(B.self, from: json.data(using:.utf8)!)XCTAssertEqual(x.location,SourceLocation(offset:11, line:2, columnInByte:1))XCTAssertEqual(x.name,"b")}

See also auto location information decoding.

Keeping JSON key order

Encoder keeps JSON key order.

structA:Codable{vara:Intvarb:Stringvarc:Int?vard:String?}func testKeyOrder()throws{leta=A(a:1, b:"b", c:2, d:"d")lete=FineJSONEncoder()letjson=String(data:try e.encode(a), encoding:.utf8)!
letexpected="""{"a": 1,"b": "b","c": 2,"d": "d"}"""XCTAssertEqual(json, expected)}

Foundation.JSONEncoder does not define key order. So you may get this.

{
"d": "d",
"b": "b",
"c": 2,
"a": 1
}

Control Optional.none encoding

You can specify Optional.none encoding.

Default is key absence which is same as Foundation.

func testNoneKeyAbsence()throws{leta=A(a:1, b:"b", c:nil, d:"d")lete=FineJSONEncoder()letjson=String(data:try e.encode(a), encoding:.utf8)!
letexpected="""{"a": 1,"b": "b","d": "d"}"""XCTAssertEqual(json, expected)}

You can specify to emit explicit null for such key.

func testNoneExplicitNull()throws{leta=A(a:1, b:"b", c:nil, d:"d")lete=FineJSONEncoder()
e.optionalEncodingStrategy =.explicitNull
letjson=String(data:try e.encode(a), encoding:.utf8)!
letexpected="""{"a": 1,"b": "b","c": null,"d": "d"}"""XCTAssertEqual(json, expected)}

Control indent width

You can specify indent width.

func testIndent4()throws{leta=A(a:1, b:"b", c:2, d:"d")lete=FineJSONEncoder()
e.jsonSerializeOptions =JSON.SerializeOptions(indentString:"")letjson=String(data:try e.encode(a), encoding:.utf8)!
letexpected="""{"a": 1,"b": "b","c": 2,"d": "d"}"""XCTAssertEqual(json, expected)}

Oneline style is also supported.

func testOnelineFormat()throws{leta=A(a:1, b:"b", c:2, d:"d")lete=FineJSONEncoder()
e.jsonSerializeOptions =JSON.SerializeOptions(isPrettyPrint:false)letjson=String(data:try e.encode(a), encoding:.utf8)!
letexpected="""{"a":1,"b":"b","c":2,"d":"d"}"""XCTAssertEqual(json, expected)}

And prettyprint is default.

Handling arbitrary digits number

JSON supports arbitrary digits originally. You can handle this by JSONNumber type.

structB:Codable{varx:JSONNumbervary:JSONNumber}func testArbitraryNumber()throws{letjson1="""{"x": 1234567890.1234567890,"y": 0.01}"""letd=FineJSONDecoder()varb=try d.decode(B.self, from: json1.data(using:.utf8)!)vary=Decimal(string: b.y.value)!
y +=Decimal(string:"0.01")!
b.y =JSONNumber(y.description)lete=FineJSONEncoder()letjson2=String(data:try e.encode(b), encoding:.utf8)!
letexpected="""{"x": 1234567890.1234567890,"y": 0.02}"""XCTAssertEqual(json2, expected)}

Foundation.JSONEncoder can not do this. So you may get this with Float.

{
"x": 1234567936,
"y": 0.019999999552965164
}

Weak typing primitive decoding

JSON number and string are each compatible during decoding.

structC:Codable{varid:Intvarname:String}func testWeakTypingDecoding()throws{letjson="""{"id": "123","name": 4869.57}"""letd=FineJSONDecoder()letc=try d.decode(C.self, from: json.data(using:.utf8)!)XCTAssertEqual(c.id,123)XCTAssertEqual(c.name,"4869.57")}

You can customize this behavior by inject your object which conforms to CodablePrimitiveJSONDecoder.

Handling complex JSON structure directly

You can use JSON type to handle complex structure.

structF:Codable{varname:Stringvardata:JSON}func testJSONTypeProperty()throws{letjson="""{"name": "john","data": ["aaa", { "bbb": "ccc" } ]}"""letd=FineJSONDecoder()letf=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")]))])))}

Customizing JSON key with keeping Codable methods auto synthesis

You can customize JSON key for property with Codable methods auto synthesis.

structG:Codable,JSONAnnotatable{staticletkeyAnnotations:JSONKeyAnnotations=["id":JSONKeyAnnotation(jsonKey:"no"),"userName":JSONKeyAnnotation(jsonKey:"user_name")]varid:Intvarpoint:IntvaruserName:String}func testAnnotateJSONKey()throws{letjson1="""{"no": 1,"point": 100,"user_name": "john"}"""letd=FineJSONDecoder()varg=try d.decode(G.self, from: json1.data(using:.utf8)!)XCTAssertEqual(g.id,1)XCTAssertEqual(g.point,100)XCTAssertEqual(g.userName,"john")
g.point +=3lete=FineJSONEncoder()letjson2=String(data:try e.encode(g), encoding:.utf8)!
letexpect="""{"no": 1,"point": 103,"user_name": "john"}"""XCTAssertEqual(json2, expect)}

Default value for absent key

You can specify default value for property which is used when JSON key is absent.

structH:Codable,JSONAnnotatable{staticletkeyAnnotations:JSONKeyAnnotations=["language":JSONKeyAnnotation(defaultValue:JSON.string("Swift"))]varname:Stringvarlanguage:String}func testDefaultValue()throws{letjson="""{"name": "john"}"""letd=FineJSONDecoder()leth=try d.decode(H.self, from: json.data(using:.utf8)!)XCTAssertEqual(h.name,"john")XCTAssertEqual(h.language,"Swift")}

Auto location information decoding

Location information decoding can be enabled from annotation.

func testAutoLocationDecoding()throws{letjson="""// comment{"name": "b"},"""letdecoder=FineJSONDecoder()letx=try decoder.decode(C.self, from: json.data(using:.utf8)!)XCTAssertEqual(x.location,SourceLocation(offset:11, line:2, columnInByte:1))XCTAssertEqual(x.name,"b")letencoder=FineJSONEncoder()letjson2=String(data:try encoder.encode(x), encoding:.utf8)!
XCTAssertEqual(json2,"""{"name": "b"}""")}

Supported building environment

  • SwiftPM for mac, iOS.

  • Carthage for mac, iOS.

  • Manual xcworkspace for mac, iOS. This is my favorite. Detail is here

Cautions

Coding for URL, Date

This library serializes URL as not string but object in JSON. It differ from Foundation.JSONEncoder, .JSONDecoder. Bacause this library uses native coding definition for these types. Foundation coders serialize them as string by following their internal custom coding logics.

License

MIT.

About

More useful JSONEncoder, Decoder

Resources

Stars

53 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages