从 JSON 文件生成数据模型代码和 JSON 解析器代码。目前你可以生成 Swift 和 Objective-C 代码。
swift build --configuration release
默认情况下,你会在 .build/release/ReverseJson
中找到可执行文件
swift test
Usage: ReverseJson (swift|objc|export) NAME FILE <options>
e.g. ReverseJson swift User testModel.json <options>
Options:
-v, --verbose Print result instead of creating files
-o, --out <dir> Output directory (default is current directory)
-c, --class (Swift) Use classes instead of structs for objects
-ca, --contiguousarray (Swift) Use ContiguousArray for lists
-pt, --publictypes (Swift) Make type declarations public instead of internal
-pf, --publicfields (Swift) Make field declarations public instead of internal
-n, --nullable (Swift and Objective-C) Make all field declarations optional (nullable in Objective-C)
-m, --mutable (Swift and Objective-C) All object fields are mutable (var instead of
let in Swift and 'readwrite' instead of 'readonly' in Objective-C)
-a, --atomic (Objective-C) Make properties 'atomic'
-p, --prefix <prefix> (Objective-C) Class-prefix to use for type declarations
-r, --reversemapping (Objective-C) Create method for reverse mapping (toJson)
./ReverseJson swift User testModel.json
./ReverseJson objc User testModel.json
./ReverseJson export User testModel.json
将此
{
"name": "Tom",
"is_private": false,
"mixed": [10, "Some text"],
"numbers": [10, 11, 12],
"locations": [
{"lat": 10.5, "lon": 49},
{
"lat": -74.1184284,
"lon": 40.7055647,
"address": {
"street": "Some Street",
"city": "New York"
}
}
],
"internal": false
}
...转换为此
// JsonModel.swift
struct User {
enum MixedItem {
case number(Int)
case text(String)
}
struct LocationsItem {
struct Address {
let city: String
let street: String
}
let address: Address?
let lat: Double
let lon: Double
}
let `internal`: Bool
let isPrivate: Bool
let locations: [LocationsItem]
let mixed: [MixedItem]
let name: String
let numbers: [Int]
}
// JsonModelMapping.swift
enum JsonParsingError: Error {
case unsupportedTypeError
}
extension Array {
init(jsonValue: Any?, map: (Any) throws -> Element) throws {
guard let items = jsonValue as? [Any] else {
throw JsonParsingError.unsupportedTypeError
}
self = try items.map(map)
}
}
extension Bool {
init(jsonValue: Any?) throws {
if let number = jsonValue as? NSNumber {
guard String(cString: number.objCType) == String(cString: NSNumber(value: true).objCType) else {
throw JsonParsingError.unsupportedTypeError
}
self = number.boolValue
} else if let number = jsonValue as? Bool {
self = number
} else {
throw JsonParsingError.unsupportedTypeError
}
}
}
extension Double {
init(jsonValue: Any?) throws {
if let number = jsonValue as? NSNumber {
self = number.doubleValue
} else if let number = jsonValue as? Int {
self = Double(number)
} else if let number = jsonValue as? Double {
self = number
} else if let number = jsonValue as? Float {
self = Double(number)
} else {
throw JsonParsingError.unsupportedTypeError
}
}
}
extension Int {
init(jsonValue: Any?) throws {
if let number = jsonValue as? NSNumber {
self = number.intValue
} else if let number = jsonValue as? Int {
self = number
} else if let number = jsonValue as? Double {
self = Int(number)
} else if let number = jsonValue as? Float {
self = Int(number)
} else {
throw JsonParsingError.unsupportedTypeError
}
}
}
extension Optional {
init(jsonValue: Any?, map: (Any) throws -> Wrapped) throws {
if let jsonValue = jsonValue, !(jsonValue is NSNull) {
self = try map(jsonValue)
} else {
self = nil
}
}
}
extension String {
init(jsonValue: Any?) throws {
guard let string = jsonValue as? String else {
throw JsonParsingError.unsupportedTypeError
}
self = string
}
}
extension User {
init(jsonValue: Any?) throws {
guard let dict = jsonValue as? [String: Any] else {
throw JsonParsingError.unsupportedTypeError
}
self.mixed = try Array(jsonValue: dict["mixed"]) { try User.MixedItem(jsonValue: $0) }
self.numbers = try Array(jsonValue: dict["numbers"]) { try Int(jsonValue: $0) }
self.`internal` = try Bool(jsonValue: dict["internal"])
self.isPrivate = try Bool(jsonValue: dict["is_private"])
self.locations = try Array(jsonValue: dict["locations"]) { try User.LocationsItem(jsonValue: $0) }
self.name = try String(jsonValue: dict["name"])
}
}
extension User.LocationsItem {
init(jsonValue: Any?) throws {
guard let dict = jsonValue as? [String: Any] else {
throw JsonParsingError.unsupportedTypeError
}
self.lat = try Double(jsonValue: dict["lat"])
self.lon = try Double(jsonValue: dict["lon"])
self.address = try Optional(jsonValue: dict["address"]) { try User.LocationsItem.Address(jsonValue: $0) }
}
}
extension User.LocationsItem.Address {
init(jsonValue: Any?) throws {
guard let dict = jsonValue as? [String: Any] else {
throw JsonParsingError.unsupportedTypeError
}
self.street = try String(jsonValue: dict["street"])
self.city = try String(jsonValue: dict["city"])
}
}
extension User.MixedItem {
init(jsonValue: Any?) throws {
if let value = try? Int(jsonValue: jsonValue) {
self = .number(value)
} else if let value = try? String(jsonValue: jsonValue) {
self = .text(value)
} else {
throw JsonParsingError.unsupportedTypeError
}
}
}
func parseUser(jsonValue: Any?) throws -> User {
return try User(jsonValue: jsonValue)
}
...或者转换为此...
// User.h
#import <Foundation/Foundation.h>
@class UserLocationsItem, UserMixedItem;
NS_ASSUME_NONNULL_BEGIN
@interface User : NSObject
- (instancetype)initWithJsonDictionary:(NSDictionary<NSString *, id<NSObject>> *)dictionary;
- (nullable instancetype)initWithJsonValue:(nullable id<NSObject>)jsonValue;
- (NSDictionary<NSString *, id<NSObject>> *)toJson;
@property (nonatomic, assign, readonly) BOOL internal;
@property (nonatomic, assign, readonly) BOOL isPrivate;
@property (nonatomic, strong, readonly) NSArray<NSNumber/*NSInteger*/ *> *numbers;
@property (nonatomic, strong, readonly) NSArray<UserLocationsItem *> *locations;
@property (nonatomic, strong, readonly) NSArray<UserMixedItem *> *mixed;
@property (nonatomic, copy, readonly) NSString *name;
@end
NS_ASSUME_NONNULL_END
// UserLocationsItem.h
#import <Foundation/Foundation.h>
@class UserLocationsItemAddress;
NS_ASSUME_NONNULL_BEGIN
@interface UserLocationsItem : NSObject
- (instancetype)initWithJsonDictionary:(NSDictionary<NSString *, id<NSObject>> *)dictionary;
- (nullable instancetype)initWithJsonValue:(nullable id<NSObject>)jsonValue;
- (NSDictionary<NSString *, id<NSObject>> *)toJson;
@property (nonatomic, strong, readonly, nullable) UserLocationsItemAddress *address;
@property (nonatomic, assign, readonly) double lat;
@property (nonatomic, assign, readonly) double lon;
@end
NS_ASSUME_NONNULL_END
// UserLocationsItemAddress.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface UserLocationsItemAddress : NSObject
- (instancetype)initWithJsonDictionary:(NSDictionary<NSString *, id<NSObject>> *)dictionary;
- (nullable instancetype)initWithJsonValue:(nullable id<NSObject>)jsonValue;
- (NSDictionary<NSString *, id<NSObject>> *)toJson;
@property (nonatomic, copy, readonly) NSString *city;
@property (nonatomic, copy, readonly) NSString *street;
@end
NS_ASSUME_NONNULL_END
// UserMixedItem.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface UserMixedItem : NSObject
- (instancetype)initWithJsonValue:(nullable id<NSObject>)jsonValue;
- (id<NSObject>)toJson;
@property (nonatomic, copy, readonly, nullable) NSNumber/*NSInteger*/ *number;
@property (nonatomic, strong, readonly, nullable) NSString *text;
@end
NS_ASSUME_NONNULL_END
// User.m
#import "User.h"
#import "UserLocationsItem.h"
#import "UserMixedItem.h"
@implementation User
- (instancetype)initWithJsonDictionary:(NSDictionary<NSString *, id> *)dict {
self = [super init];
if (self) {
_internal = [dict[@"internal"] isKindOfClass:[NSNumber class]] ? [dict[@"internal"] boolValue] : 0;
_isPrivate = [dict[@"is_private"] isKindOfClass:[NSNumber class]] ? [dict[@"is_private"] boolValue] : 0;
_numbers = ({
id value = dict[@"numbers"];
NSMutableArray<NSNumber/*NSInteger*/ *> *values = nil;
if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
values = [NSMutableArray arrayWithCapacity:array.count];
for (id item in array) {
NSNumber/*NSInteger*/ *parsedItem = [item isKindOfClass:[NSNumber class]] ? item : nil;
[values addObject:parsedItem ?: (id)[NSNull null]];
}
}
[values copy] ?: @[];
});
_locations = ({
id value = dict[@"locations"];
NSMutableArray<UserLocationsItem *> *values = nil;
if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
values = [NSMutableArray arrayWithCapacity:array.count];
for (id item in array) {
UserLocationsItem *parsedItem = ([[UserLocationsItem alloc] initWithJsonValue:item] ?: [[UserLocationsItem alloc] initWithJsonDictionary:@{}]);
[values addObject:parsedItem ?: (id)[NSNull null]];
}
}
[values copy] ?: @[];
});
_mixed = ({
id value = dict[@"mixed"];
NSMutableArray<UserMixedItem *> *values = nil;
if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
values = [NSMutableArray arrayWithCapacity:array.count];
for (id item in array) {
UserMixedItem *parsedItem = [[UserMixedItem alloc] initWithJsonValue:item];
[values addObject:parsedItem ?: (id)[NSNull null]];
}
}
[values copy] ?: @[];
});
_name = [dict[@"name"] isKindOfClass:[NSString class]] ? dict[@"name"] : @"";
}
return self;
}
- (instancetype)initWithJsonValue:(id)jsonValue {
if ([jsonValue isKindOfClass:[NSDictionary class]]) {
self = [self initWithJsonDictionary:jsonValue];
} else {
self = nil;
}
return self;
}
- (NSDictionary<NSString *, id<NSObject>> *)toJson {
NSMutableDictionary *ret = [NSMutableDictionary dictionaryWithCapacity:6];
ret[@"internal"] = @(_internal);
ret[@"is_private"] = @(_isPrivate);
ret[@"numbers"] = ({
NSMutableArray<id<NSObject>> *values = nil;
NSArray *array = _numbers;
if (array) {
values = [NSMutableArray arrayWithCapacity:array.count];
for (id item in array) {
if (item == [NSNull null]) {
[values addObject:item];
} else {
id json = item;
[values addObject:json ?: (id)[NSNull null]];
}
}
}
[values copy] ?: @[];
});
ret[@"locations"] = ({
NSMutableArray<id<NSObject>> *values = nil;
NSArray *array = _locations;
if (array) {
values = [NSMutableArray arrayWithCapacity:array.count];
for (id item in array) {
if (item == [NSNull null]) {
[values addObject:item];
} else {
id json = [item toJson];
[values addObject:json ?: (id)[NSNull null]];
}
}
}
[values copy] ?: @[];
});
ret[@"mixed"] = ({
NSMutableArray<id<NSObject>> *values = nil;
NSArray *array = _mixed;
if (array) {
values = [NSMutableArray arrayWithCapacity:array.count];
for (id item in array) {
if (item == [NSNull null]) {
[values addObject:item];
} else {
id json = [item toJson];
[values addObject:json ?: (id)[NSNull null]];
}
}
}
[values copy] ?: @[];
});
ret[@"name"] = _name;
return [ret copy];
}
@end
// UserLocationsItem.m
#import "UserLocationsItem.h"
#import "UserLocationsItemAddress.h"
@implementation UserLocationsItem
- (instancetype)initWithJsonDictionary:(NSDictionary<NSString *, id> *)dict {
self = [super init];
if (self) {
_address = [[UserLocationsItemAddress alloc] initWithJsonValue:dict[@"address"]];
_lat = [dict[@"lat"] isKindOfClass:[NSNumber class]] ? [dict[@"lat"] doubleValue] : 0;
_lon = [dict[@"lon"] isKindOfClass:[NSNumber class]] ? [dict[@"lon"] doubleValue] : 0;
}
return self;
}
- (instancetype)initWithJsonValue:(id)jsonValue {
if ([jsonValue isKindOfClass:[NSDictionary class]]) {
self = [self initWithJsonDictionary:jsonValue];
} else {
self = nil;
}
return self;
}
- (NSDictionary<NSString *, id<NSObject>> *)toJson {
NSMutableDictionary *ret = [NSMutableDictionary dictionaryWithCapacity:3];
ret[@"address"] = [_address toJson];
ret[@"lat"] = @(_lat);
ret[@"lon"] = @(_lon);
return [ret copy];
}
@end
// UserLocationsItemAddress.m
#import "UserLocationsItemAddress.h"
@implementation UserLocationsItemAddress
- (instancetype)initWithJsonDictionary:(NSDictionary<NSString *, id> *)dict {
self = [super init];
if (self) {
_city = [dict[@"city"] isKindOfClass:[NSString class]] ? dict[@"city"] : @"";
_street = [dict[@"street"] isKindOfClass:[NSString class]] ? dict[@"street"] : @"";
}
return self;
}
- (instancetype)initWithJsonValue:(id)jsonValue {
if ([jsonValue isKindOfClass:[NSDictionary class]]) {
self = [self initWithJsonDictionary:jsonValue];
} else {
self = nil;
}
return self;
}
- (NSDictionary<NSString *, id<NSObject>> *)toJson {
NSMutableDictionary *ret = [NSMutableDictionary dictionaryWithCapacity:2];
ret[@"city"] = _city;
ret[@"street"] = _street;
return [ret copy];
}
@end
// UserMixedItem.m
#import "UserMixedItem.h"
@implementation UserMixedItem
- (instancetype)initWithJsonValue:(id)jsonValue {
self = [super init];
if (self) {
_number = [jsonValue isKindOfClass:[NSNumber class]] ? jsonValue : nil;
_text = [jsonValue isKindOfClass:[NSString class]] ? jsonValue : nil;
}
return self;
}
- (id<NSObject>)toJson {
if (_number) {
return _number;
} else if (_text) {
return _text;
}
return nil;
}
@end
有时,推断的模型需要进行一些小的调整,例如,已被推断为非可选的字段应该变为可选。因此,ReverseJson 允许导出简单的模式,然后可以调整该模式,并用于生成 Swift 或 Objective-C 代码
./ReverseJson export User testModel.json
上面的示例生成以下模式
{
"type" : "object",
"$schema" : "https:\/\/github.com\/tomquist\/ReverseJson\/tree\/1.2.0",
"properties" : {
"mixed" : {
"type" : "list",
"content" : {
"type" : "any",
"of" : [
"int",
"text"
]
}
},
"numbers" : {
"type" : "list",
"content" : "int"
},
"name" : "text",
"internal" : "bool",
"locations" : {
"type" : "list",
"content" : {
"type" : "object",
"properties" : {
"lat" : "double",
"lon" : "double",
"address" : {
"type" : "object",
"isOptional" : true,
"properties" : {
"street" : "text",
"city" : "text"
}
}
}
}
},
"is_private" : "bool"
}
}
./ReverseJson swift User mySchema.json
模式是一个简单的类型描述,它始终具有以下结构
{
"type": "<object|list|string|int|float|double|bool|any>",
"isOptional": true
}
如果只有一个“type”属性,也可以简单地使用类型标识符,例如,以下表达式
"string"
等同于
{
"type": "string"
}
如果缺少“isOptional”属性,则假定该类型为非可选类型。 除了使用“isOptional”属性外,也可以在类型名称后添加“?”后缀。 例如,以下表达式
"string?"
等同于
{
"type": "string",
"isOptional": true
}
对象有一个额外的属性“properties”,它是一个包含所有属性的 JSON 对象,其中键是属性名称,值是模式。你可以选择添加“name”属性来覆盖将模型转换为代码时自动生成的名称。例如:
{
"type": "object",
"name": "MyObject",
"properties": {
"property1": {
"type": "string"
},
"property2": "int?"
}
}
列表使用“content”属性描述其内容类型。例如:
{
"type": "list",
"content": {
"type": "object",
"properties": {
"id": "int",
"name": "string",
"description": "string?"
}
}
}
“any”类型允许在同一位置支持多种值类型。要描述允许的类型,请使用“of”属性。你可以选择添加“name”属性来覆盖将模型转换为代码时自动生成的名称。例如:
{
"type": "any",
"name": "MyObject",
"of": [
{
"type": "object",
"properties": {
"id": "text",
"name": "text",
"description": "text?"
}
},
"int"
]
}