Objective-C is a runtime, weakly typed language. The NSDictionary
class in Objective-C isn't a type-safe object.
Suppose we have a json dictionary:
NSDictionary *json = @{ @"username": @"tp", @"password": @"123456", @"scores": @{ @"programming": @70, @"language": @80 }, @"tels": @[ @"1367890876", @"02884435261" ] };
We may access the dictionary in this way:
NSString *username = json[@"username"];
if (!username || ![username isKindOfClass:NSString.class] || username.length == 0) {
return;
}
// Do something with username string
or:
NSDictionary *scores = json[@"scores"];
if (!scores || ![scores isKindOfClass:NSDictionary.class] || scores.count == 0) {
return;
}
NSNumber *score = scores[@"programming"];
if (!score || ![score isKindOfClass:NSNumber.class]) {
return;
}
// Do something with programming score number
The problem here is, we do much things with type & value check to insure type-safety, these make our code ugly.
With TypedJSON
, we use chain operators to solve this problem in a semantic way. These try to insure the elegance and readability of the code.
NSString *username = json.tj.string(@"username").without.empty.value;
if (!username) {
return;
}
// Do something with username string
and:
NSNumber *score = json.tj.dictionary(@"scores").number(@"programming").value;
if (!score) {
return;
}
// Do something with programming score number
The json data in this example:
{
"username": "tp",
"password": "123456",
"scores": {
"programming": 70,
"language": 80
},
"tels": [
"+8613612345678",
"028-84555555"
]
}
How we access this json dictionary with TypedJSON
:
NSDictionary *json = [self loadJSON];
// Get username from json exclued empty string value '', return 'anonymous' while nil.
NSString *username = json.tj.string(@"username").without.empty.defaults(@"anonymous").value;
// Get programming score number in scores dictionary from json.
NSNumber *programmingScore = json.tj.dictionary(@"scores").number(@"programming").value;
// Get politics score number in scores dictionary from json, return 0 while politics do not exists.
NSNumber *politicsScore = json.tj.dictionary(@"scores").number(@"politics").with.defaults(@0).value;
NSLog(@"The programming score of %@ is %@, and politics %@.", username, programmingScore, politicsScore);
TypedJSON
is available through CocoaPods. To install
it, simply add the following line to your Podfile:
pod 'TypedJSON'
Then run pod install
to integrate.
To import TypedJSON
, you just:
#import <TypedJSON/TypedJSON.h>
Read string in dictionary with a specified key:
json.tj.string(@"foo").value;
The value could be nil while it's not a
NSString
type.
Read number in dictionary with a specified key:
json.tj.number(@"foo").value;
The value could be
nil
while it's not aNSNumber
type.
Read array in dictionary with a specified key:
json.tj.array(@"foo").value;
The value could be
nil
while it's not aNSArray
type.
Read dictionary in dictionary with a specified key:
json.tj.dictionary(@"foo").value;
The value could be
nil
while it's not aNSDictionary
type.
Sometimes we get empty value in ditionary, such as:
- empty string:
""
- empty array:
@[]
- empty dictionary:
@{}
Use - empty
operator to ignore them:
json.tj.string(@"foo").without.empty.value;
Return
nil
if the value of"foo"
is a""
json.tj.array(@"foo").without.empty.value;
Return
nil
if the value of"foo"
is a@[]
json.tj.dictionary(@"foo").without.empty.value;
Return
nil
if the value of"foo"
is a@{}
Suppose we get nil
value in the chain, and we need to provide a default value:
json.tj.string(@"foo").with.defaults(@"bar").value;
The
- with
operator could be omitted, it just to insure semantics of the statment.
This operator just get value by a specified key, without type check:
json.tj.any(@"foo").value;
The return value is an
id
type.
Or you could use Valuable
extension to do the type check:
json.tj.any(@"foo").stringValue;
- stringValue
will make sure the return value matchesNSString
type, if not then returnnil
.
Supporse the value is wrapped in the inner dictionary:
{
"foo": {
"bar": {
"greeting": "Hello ~"
}
}
}
We can access the greeting
in a chain:
json.tj.dictionary(@"foo").dictionary(@"bar").string(@"greeting").value;
In the example above we access greeting
in an operator chain, with - find
operator we can change the code:
json.tj.find(@"greeting").value;
- find
operator enumerate the dictionary recursively to get the value mathes the specified key, but doesn't do the type check. In order to insure type-safety, we could use Valuable
extension method - stringValue
:
json.tj.find(@"greeting").stringValue;
This operator is similar to Valuable
extension methods. It make sure the value matches the specified class type. Usually we use this operator after - find
or - any
operators in the operator chain:
json.tj.find(@"greeting").as(NSString.class).value;
This make sure the result value of
- find
operator@"greeting"
matchesNSString
class.
It''s equal to:
json.tj.find(@"greeting").stringValue;
If the json
object is nil
or not a subcalss of NSDictionary
, use .tj
directly may cause crash. So we should make nullability & type check for json
object before access with tj. Or we can access with TJ(json)
macro to do this for you.
// The `json` object could be `nil`, or it's class is NOT a kind of NSDicionary.
NSString *username = TJ(json).string(@"username").without.empty.defaults(@"anonymous").value;
TypedJSON is available under the MIT license. See the LICENSE file for more info.