Skip to content

Instantly share code, notes, and snippets.

@PhilCai1993
Created February 25, 2016 09:48
Show Gist options
  • Save PhilCai1993/5821a168cd3ea11168f2 to your computer and use it in GitHub Desktop.
Save PhilCai1993/5821a168cd3ea11168f2 to your computer and use it in GitHub Desktop.
CoolieForObjectMapper.playground
//
// Coolie.swift
// Coolie
//
// Created by NIX on 16/1/23.
// Copyright © 2016年 nixWork. All rights reserved.
//
import Foundation
public class Coolie: NSObject {
private let scanner: NSScanner
public init(JSONString: String) {
scanner = NSScanner(string: JSONString)
}
public func printModelWithName(modelName: String) {
if let value = parse() {
value.printAtLevel(0, modelName: modelName)
} else {
print("Parse failed!")
}
}
private enum Token {
case BeginObject(Swift.String) // {
case EndObject(Swift.String) // }
case BeginArray(Swift.String) // [
case EndArray(Swift.String) // ]
case Colon(Swift.String) // ;
case Comma(Swift.String) // ,
case Bool(Swift.Bool) // true or false
enum NumberType {
case Int(Swift.Int)
case Double(Swift.Double)
}
case Number(NumberType) // 42, 99.99
case String(Swift.String) // "nix", ...
case Null
}
private enum Value {
case Bool(Swift.Bool)
enum NumberType {
case Int(Swift.Int)
case Double(Swift.Double)
}
case Number(NumberType)
case String(Swift.String)
case Null
indirect case Dictionary([Swift.String: Value])
indirect case Array(name: Swift.String?, values: [Value])
}
lazy var numberScanningSet: NSCharacterSet = {
let symbolSet = NSMutableCharacterSet.decimalDigitCharacterSet()
symbolSet.addCharactersInString(".-")
return symbolSet
}()
lazy var stringScanningSet: NSCharacterSet = {
let symbolSet = NSMutableCharacterSet.alphanumericCharacterSet()
symbolSet.formUnionWithCharacterSet(NSCharacterSet.punctuationCharacterSet())
symbolSet.formUnionWithCharacterSet(NSCharacterSet.symbolCharacterSet())
symbolSet.formUnionWithCharacterSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
symbolSet.removeCharactersInString("\"")
return symbolSet
}()
private func generateTokens() -> [Token] {
func scanBeginObject() -> Token? {
if scanner.scanString("{", intoString: nil) {
return .BeginObject("{")
}
return nil
}
func scanEndObject() -> Token? {
if scanner.scanString("}", intoString: nil) {
return .EndObject("}")
}
return nil
}
func scanBeginArray() -> Token? {
if scanner.scanString("[", intoString: nil) {
return .BeginArray("[")
}
return nil
}
func scanEndArray() -> Token? {
if scanner.scanString("]", intoString: nil) {
return .EndArray("]")
}
return nil
}
func scanColon() -> Token? {
if scanner.scanString(":", intoString: nil) {
return .Colon(":")
}
return nil
}
func scanComma() -> Token? {
if scanner.scanString(",", intoString: nil) {
return .Comma(",")
}
return nil
}
func scanBool() -> Token? {
if scanner.scanString("true", intoString: nil) {
return .Bool(true)
}
if scanner.scanString("false", intoString: nil) {
return .Bool(false)
}
return nil
}
func scanNumber() -> Token? {
var string: NSString?
if scanner.scanCharactersFromSet(numberScanningSet, intoString: &string) {
if let string = string as? String {
if let number = Int(string) {
return .Number(.Int(number))
} else if let number = Double(string) {
return .Number(.Double(number))
}
}
}
return nil
}
func scanString() -> Token? {
var string: NSString?
if scanner.scanString("\"", intoString: nil) &&
scanner.scanCharactersFromSet(stringScanningSet, intoString: &string) &&
scanner.scanString("\"", intoString: nil) {
if let string = string as? String {
return .String(string)
}
}
return nil
}
func scanNull() -> Token? {
if scanner.scanString("null", intoString: nil) {
return .Null
}
return nil
}
var tokens = [Token]()
while !scanner.atEnd {
let previousScanLocation = scanner.scanLocation
if let token = scanBeginObject() {
tokens.append(token)
}
if let token = scanEndObject() {
tokens.append(token)
}
if let token = scanBeginArray() {
tokens.append(token)
}
if let token = scanEndArray() {
tokens.append(token)
}
if let token = scanColon() {
tokens.append(token)
}
if let token = scanComma() {
tokens.append(token)
}
if let token = scanBool() {
tokens.append(token)
}
if let token = scanNumber() {
tokens.append(token)
}
if let token = scanString() {
tokens.append(token)
}
if let token = scanNull() {
tokens.append(token)
}
let currentScanLocation = scanner.scanLocation
guard currentScanLocation > previousScanLocation else {
print("Not found valid token")
break
}
}
return tokens
}
private func parse() -> Value? {
let tokens = generateTokens()
guard !tokens.isEmpty else {
print("No tokens")
return nil
}
var next = 0
func parseValue() -> Value? {
guard let token = tokens[safe: next] else {
print("No token for parseValue")
return nil
}
switch token {
case .BeginArray:
var arrayName: String?
let nameIndex = next - 2
if nameIndex >= 0 {
if let nameToken = tokens[safe: nameIndex] {
if case .String(let name) = nameToken {
arrayName = name.capitalizedString
}
}
}
next++
return parseArray(name: arrayName)
case .BeginObject:
next++
return parseObject()
case .Bool:
return parseBool()
case .Number:
return parseNumber()
case .String:
return parseString()
case .Null:
return parseNull()
default:
return nil
}
}
func parseArray(name name: String? = nil) -> Value? {
guard let token = tokens[safe: next] else {
print("No token for parseArray")
return nil
}
var array = [Value]()
if case .EndArray = token {
next++
return .Array(name: name, values: array)
} else {
while true {
guard let value = parseValue() else {
break
}
array.append(value)
if let token = tokens[safe: next] {
if case .EndArray = token {
next++
return .Array(name: name, values: array)
} else {
guard let _ = parseComma() else {
print("Expect comma")
break
}
guard let nextToken = tokens[safe: next] where nextToken.isNotEndArray else {
print("Invalid JSON, comma at end of array")
break
}
}
}
}
return nil
}
}
func parseObject() -> Value? {
guard let token = tokens[safe: next] else {
print("No token for parseObject")
return nil
}
var dictionary = [String: Value]()
if case .EndObject = token {
next++
return .Dictionary(dictionary)
} else {
while true {
guard let key = parseString(), _ = parseColon(), value = parseValue() else {
print("Expect key : value")
break
}
if case .String(let key) = key {
dictionary[key] = value
}
if let token = tokens[safe: next] {
if case .EndObject = token {
next++
return .Dictionary(dictionary)
} else {
guard let _ = parseComma() else {
print("Expect comma")
break
}
guard let nextToken = tokens[safe: next] where nextToken.isNotEndObject else {
print("Invalid JSON, comma at end of object")
break
}
}
}
}
}
return nil
}
func parseColon() -> Value? {
defer {
next += 1
}
guard let token = tokens[safe: next] else {
print("No token for parseColon")
return nil
}
if case .Colon(let string) = token {
return .String(string)
}
return nil
}
func parseComma() -> Value? {
defer {
next += 1
}
guard let token = tokens[safe: next] else {
print("No token for parseComma")
return nil
}
if case .Comma(let string) = token {
return .String(string)
}
return nil
}
func parseBool() -> Value? {
defer {
next += 1
}
guard let token = tokens[safe: next] else {
print("No token for parseBool")
return nil
}
if case .Bool(let bool) = token {
return .Bool(bool)
}
return nil
}
func parseNumber() -> Value? {
defer {
next += 1
}
guard let token = tokens[safe: next] else {
print("No token for parseNumber")
return nil
}
if case .Number(let number) = token {
switch number {
case .Int(let int):
return .Number(.Int(int))
case .Double(let double):
return .Number(.Double(double))
}
}
return nil
}
func parseString() -> Value? {
defer {
next += 1
}
guard let token = tokens[safe: next] else {
print("No token for parseString")
return nil
}
if case .String(let string) = token {
return .String(string)
}
return nil
}
func parseNull() -> Value? {
defer {
next += 1
}
guard let token = tokens[safe: next] else {
print("No token for parseNull")
return nil
}
if case .Null = token {
return .Null
}
return nil
}
return parseValue()
}
}
private extension Coolie.Value {
var type: Swift.String {
switch self {
case .Bool:
return "Bool"
case .Number(let number):
switch number {
case .Int:
return "Int"
case .Double:
return "Double"
}
case .String:
return "String"
case .Null:
return "UnknownType"
default:
fatalError("Unknown type")
}
}
var isDictionaryOrArray: Swift.Bool {
switch self {
case .Dictionary:
return true
case .Array:
return true
default:
return false
}
}
var isDictionary: Swift.Bool {
switch self {
case .Dictionary:
return true
default:
return false
}
}
var isArray: Swift.Bool {
switch self {
case .Array:
return true
default:
return false
}
}
var isNull: Swift.Bool {
switch self {
case .Null:
return true
default:
return false
}
}
}
private extension Coolie.Token {
var isNotEndObject: Swift.Bool {
switch self {
case .EndObject:
return false
default:
return true
}
}
var isNotEndArray: Swift.Bool {
switch self {
case .EndArray:
return false
default:
return true
}
}
}
private extension Coolie.Value {
func printAtLevel(level: Int, modelName: Swift.String? = nil) {
func indentLevel(level: Int) {
for _ in 0..<level {
print("\t", terminator: "")
}
}
switch self {
case .Bool, .Number, .String, .Null:
print("\(type)?")
case .Dictionary(let info):
// struct name
indentLevel(level)
print("class \(modelName ?? "Model"): Mappable {")
// properties
for key in info.keys.sort() {
if let value = info[key] {
if value.isDictionaryOrArray {
value.printAtLevel(level + 1, modelName: key.capitalizedString)
indentLevel(level + 1)
if value.isArray {
if case .Array(_, let values) = value, let first = values.first where !first.isDictionaryOrArray {
print("var \(key.coolie_lowerCamelCase): [\(first.type)]?", terminator: "\n")
} else {
print("var \(key.coolie_lowerCamelCase): [\(key.capitalizedString.coolie_dropLastCharacter)]?", terminator: "\n")
}
} else {
print("var \(key.coolie_lowerCamelCase): \(key.capitalizedString)?", terminator: "\n")
}
} else {
indentLevel(level + 1)
print("var \(key.coolie_lowerCamelCase): ", terminator: "")
value.printAtLevel(level)
}
}
}
indentLevel(level + 1)
print("init(){ }")
indentLevel(level + 1)
print("required init?(_ map: Map){ }")
// generate method
indentLevel(level + 1)
print("func mapping(map: Map) {")
for key in info.keys.sort() {
indentLevel(level + 2)
print(key.coolie_lowerCamelCase + " <- " + "map[\"" + key + "\"]")
}
indentLevel(level + 1)
print("}")
indentLevel(level)
print("}")
case .Array(let name, let values):
if let first = values.first {
if first.isDictionaryOrArray {
first.printAtLevel(level, modelName: name?.coolie_dropLastCharacter)
}
}
}
}
}
private extension String {
var coolie_dropLastCharacter: String {
if characters.count > 0 {
return String(characters.dropLast())
}
return self
}
var coolie_lowerCamelCase: String {
let symbolSet = NSMutableCharacterSet.alphanumericCharacterSet()
symbolSet.addCharactersInString("_")
symbolSet.invert()
let validString = self.componentsSeparatedByCharactersInSet(symbolSet).joinWithSeparator("_")
let parts = validString.componentsSeparatedByString("_")
return parts.enumerate().map({ index, part in
return index == 0 ? part : part.capitalizedString
}).joinWithSeparator("")
}
}
private extension Array {
subscript (safe index: Int) -> Element? {
return index >= 0 && index < count ? self[index] : nil
}
}
let jsonPath = NSBundle.mainBundle().pathForResource("test", ofType: "json")!
let jsonString = try! NSString(contentsOfFile: jsonPath, encoding: NSUTF8StringEncoding) as String!
let coolie = Coolie(JSONString: jsonString)
coolie.printModelWithName("MyModel")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment