Swiftwater Logo

Writing A Robust Little Parser in Swift –Epilogue

This entry is part 17 of 17 in the series Swiftwater

ABSTRACT

Our wonderful, high-quality little parser has been chugging along for six months, parsing various data streams and delivering strings.

Then, one day, the boss walks in, and says those words that all software engineers dread:

“I have some feature requests for the parser.”

Our hearts sink. We break out in a cold sweat. Dread runs icy fingers down our spine…

THE FEATURE REQUEST

It turns out that our latest big account is using a pre-parser that delivers data in a slightly different format than those covered by our tests:

{
	"arrayElement": [{
		"1": "Element Value 01"
	}, {
		"2": "Element Value 02"
	}, {
		"3": "Element Value 03"
	}, {
		"4": "Element Value 04"
	}, {
		"5": "Element Value 05"
	}]
}

It looks like we’ll be able to make that work without any changes to the parser. We will just need to add a new test:

    // New Format, same set of tests.
    jsonInterpretations.append(convertJSONToDictionary("{\"arrayElement\": [{\"1\": \"Element Value 01\"}, {\"2\": \"Element Value 02\"}, {\"3\": \"Element Value 03\"}, {\"4\": \"Element Value 04\"}, {\"5\": \"Element Value 05\"}]}"))

We add it, run the playground, and…silence.

…and smugness; which is about to be wiped out…

The boss also adds that we now have to parse collections of integers and floats, returning them as Arrays of their respective types.

…and, our little parser has become so popular, that others want to use it, so the boss wants a “whitelist,” where you can filter for only certain elements.

Yeah…that won’t work with the current design.

The whitelist won’t be difficult. It pretty much jives with what we have, so a fairly basic change will give us what we need:

func recursiveDescentParser(_ inDictionaryToBeParsed: [AnyHashable: Any], blackList inBlacklist: [String] = [], whiteList inWhiteList: [String] = []) -> [String] {
    var ret = [String]()
    
    func isThisKeyValid(_ inKey: String) -> Bool {
        if inBlacklist.contains(inKey) {
            return false
        }
        
        if inWhiteList.isEmpty || inWhiteList.contains(inKey) {
            return true
        }
        
        return false
    }

    inDictionaryToBeParsed.forEach {
        if let asFloat = ($0.value as? Double ?? Double($0.value as? String ?? "ERROR")) {
            let formatter = NumberFormatter()
            formatter.minimumIntegerDigits = 1
            formatter.minimumFractionDigits = 0
            formatter.maximumFractionDigits = 20
            if let keyString = $0.key as? String, isThisKeyValid(keyString) {
                ret += [String(formatter.string(from: asFloat as NSNumber) ?? "")]
            }
        } else if let asString = $0.value as? String, let keyString = $0.key as? String, isThisKeyValid(keyString) {
            ret += [asString]
        } else if let asDictionary = $0.value as? [AnyHashable: Any] {
            ret += recursiveDescentParser(asDictionary, blackList: inBlacklist, whiteList: inWhiteList)
        } else if let asDictionaryArray = $0.value as? [[AnyHashable: Any]] {
            asDictionaryArray.forEach { elem in
                ret += recursiveDescentParser(elem, blackList: inBlacklist, whiteList: inWhiteList)
            }
        } else if let asArray = $0.value as? [String] {
            asArray.forEach { elem in
                ret += recursiveDescentParser(["": elem], blackList: inBlacklist, whiteList: inWhiteList)
            }
        }
    }
    
    return ret.sorted()
}

So what we did, there, was add an optional whitelist Array to the function signature, giving it a default of an empty Array, and created a short “isThisKeyValid()” function, as we are looking at both the blacklist and the whitelist. We also added the whitelist to the recursion calls.

If we run the playground now, we will still get silence. The current tests work fine, and we’ll need to add new tests for the whitelist. Realistically, we should have done that first, but this was such a quick and obvious patch, that we could just add it first.

Shouldn’t skimp on the tests, though:

let whatWeWantToSee3: [String] = ["0", "1", "2", "3", "4"]
// Test Set 3
jsonInterpretations.forEach {
    let parsedResult = recursiveDescentParser($0, whiteList: ["index", "row"])
    if !parsedResult.isEmpty, whatWeWantToSee3 != parsedResult {
        print("ERROR! Bad Result: \(parsedResult) for \($0)")
    } else {
//        print("\($0) Passes!")
    }
}

What we are doing, there, is filtering for the “index” and “row” fields. We ignore empty responses, but we should really add more tests. We’ll probably get to that after we deal with the next batch of changes.

Now…about those different node value types…

SWIFT TO THE RESCUE

This one won’t be so easy, but Swift has a few tricks up its sleeve that might help us out.

Non-Union

If you have any background in C, you might remember the concept of a C Union. This is a strange type that is a “Swiss Army knife” type. It holds a piece of data that can be expressed by any one of several different casts. You can define a data type that can be “any of” a set of types.

Unions are kind of an abomination. They aren’t defined that well, and their implementation can vary between compilers.

They can be useful, though, if you want to use one return to be any one of several different types. What the boss is asking, is that we define our parser three different ways:

func recursiveDescentParser(_ inDictionaryToBeParsed: [AnyHashable: Any], blackList inBlacklist: [String] = [], whiteList inWhiteList: [String] = []) -> [String]
func recursiveDescentParser(_ inDictionaryToBeParsed: [AnyHashable: Any], blackList inBlacklist: [String] = [], whiteList inWhiteList: [String] = []) -> [Int]
func recursiveDescentParser(_ inDictionaryToBeParsed: [AnyHashable: Any], blackList inBlacklist: [String] = [], whiteList inWhiteList: [String] = []) -> [Double]

Now, if you remember from our previous discussion, it’s perfectly legitimate to define three variants of a function, differentiated only by return type.

But that seems awkward. We’d have to pretty much do “copy-and-paste programming,” repeating a bunch of code three times, with only minor differences, or have a “breakout” function that is called by the three variants to do the common processing.

If we had a union, we could define and implement the function once, and the caller could work out the return type.

Swift has something a lot better than unions. It has enums. Swift enums are unlike any other enums out there. They are fully-qualified instances, with methods, protocols, and even stored (in a way) data. We have covered them (a bit), previously. It would take an entire book to cover the many cool things that you can do with Swift enums.

We’ll cover just one trick, here.

A Quick Peek Behind the Enum Curtain

When you associate a value with an enum case, you are adding a reference (or copy) of that value to the instance represented by that case.

Swift enums are instantiated as cases. When you do this:

enum SomeEnum {
    case someCase(String)
}
let enumInstance = SomeEnum.someCase("someString")

It is quite similar to this:

struct SomeStruct {
    struct someCase {
        var value: String

        init(_  inValue: String) {
            value = inValue
        }
    }
}
let fauxEnumInstance = SomeStruct.someCase("someString")

Where the “value” property is the value that was associated with the type defined by the “someStruct” embedded struct.

Multiple cases might look like this:

enum SomeOtherEnum {
    case someCase(String)
    case someOtherCase(Int)
}

struct SomeOtherStruct {
    struct someCase {
        var value: String

        init(_  inValue: String) {
            value = inValue
        }
    }

    struct someOtherCase {
        var value: Int

        init(_  inValue: Int) {
            value = inValue
        }
    }
}

This could also explain why enums with explicit types can’t have associated values, as they probably model more like this:

enum SomeFixedEnum: Int {
    case zero = 0
    case one = 1
}

struct SomeFixedStruct {
    struct zero {
        static var rawValue: Int {
            return 0
        }
    }
    
    struct one {
        static var rawValue: Int {
            return 1
        }
    }
}

Since they are basically static properties, and have a fixed type and storage, there’s no room for anything else. Here’s a relatively good discussion on the philosophical reasoning behind this, but I suspect that my more practical explanation is equally as valid. It’s quite a sensible approach, and language developers are extremely sensible.

If an enum has a rawValue, then think of it as a constant property. If not, then it is probably OK to model it as an embedded struct.

So What Does This Have to Do With Unions?

Good question. Glad you asked.

If you think of enum cases as being embedded structs, with each one having its own definition and var properties, you could define a struct like so:

struct FauxUnionStruct {
    struct stringVal {
        var value: String
        
        init(_  inValue: String) {
            value = inValue
        }
    }
    
    struct intVal {
        var value: Int
        
        init(_  inValue: Int) {
            value = inValue
        }
    }
    
    struct floatVal {
        var value: Double
        
        init(_  inValue: Double) {
            value = inValue
        }
    }
}

let aStringInstance = FauxUnionStruct.stringVal("someString")
let anIntInstance = FauxUnionStruct.intVal(123456789)
let aFloatInstance = FauxUnionStruct.floatVal(1234.56789)

print(aStringInstance.value)
print(anIntInstance.value)
print(aFloatInstance.value)

So that gives the appearance of the same property (“value”), having multiple data types. If we gussy up an enum to do something similar, we’d have:

enum FauxUnionEnum {
    case stringVal(String)
    case intVal(Int)
    case floatVal(Double)
}

let anotherStringInstance = FauxUnionEnum.stringVal("someString")
let anotherIntInstance = FauxUnionEnum.intVal(123456789)
let anotherFloatInstance = FauxUnionEnum.floatVal(1234.56789)

print(anotherStringInstance)
print(anotherIntInstance)
print(anotherFloatInstance)

Note the absence of the “.value” in the print statements. That doesn’t exist for enums. I’m relying on the built-in description property to emit a useful String.

You have to admit that’s a heck of a lot shorter than the struct version, though…

Now, let’s add some accessor variables to allow us to get at the associated values:

enum FauxUnionEnum {
    case stringVal(String)
    case intVal(Int)
    case floatVal(Double)
    
    var string: String {
        switch self {
        case .stringVal(let str):
            return str
        case .intVal(let int):
            return String(int)
        case .floatVal(let flt):
            return String(flt)
        }
    }
    
    var int: Int {
        switch self {
        case .stringVal(let str):
            return Int(str) ?? 0
        case .intVal(let int):
            return int
        case .floatVal(let flt):
            return Int(flt)
        }
    }
    
    var float: Double {
        switch self {
        case .stringVal(let str):
            return Double(str) ?? Double.nan
        case .intVal(let int):
            return Double(int)
        case .floatVal(let flt):
            return flt
        }
    }
}

And now, we can do this:

let anotherStringInstance = FauxUnionEnum.stringVal("someString")
let anotherIntInstance = FauxUnionEnum.intVal(123456789)
let anotherFloatInstance = FauxUnionEnum.floatVal(1234.56789)

print(anotherStringInstance.string)
print(anotherStringInstance.int)
print(anotherStringInstance.float)

print(anotherIntInstance.string)
print(anotherIntInstance.int)
print(anotherIntInstance.float)

print(anotherFloatInstance.string)
print(anotherFloatInstance.int)
print(anotherFloatInstance.float)

Which gives you this:

someString
0
nan
123456789
123456789
123456789.0
1234.56789
1234
1234.56789

We could even take advantage of this structure to make the String response a bit cleaner (like limiting the number of significant digits):

var string: String {
        switch self {
        case .stringVal(let str):
            return str
        case .intVal(let int):
            return String(int)
        case .floatVal(let flt):
            let formatter = NumberFormatter()
            formatter.minimumIntegerDigits = 1
            formatter.minimumFractionDigits = 0
            formatter.maximumFractionDigits = 4
            return formatter.string(from: flt as NSNumber) ?? ""
        }
    }

Which results in this:

someString
0
nan
123456789
123456789
123456789.0
1234.5679
1234
1234.56789

What makes this REALLY cool, however, is that –unlike unions– you have access to ALL THREE TYPES simultaneously. Unions have a type “set” when written to. This can change, but they don’t define access as the other defined types.

You can ask any one of these for its integer value, string value or float value. In some cases, it may not make sense to return a certain value (you can get an “NaN” from the Double cast if the string value isn’t one that can be defined as a Double).

The enum case defines its “native” type, but you can treat it like any other type. You’ll see this in action when we fix up the tests, later. It will also affect the way that we sort.

ZZZZZZZZZZZzzzzzzz….

Okay, that was a really long-winded way of introducing this lil’ guy:

enum ParserResponse {
    case string(String)
    case integer(Int)
    case float(Double)
    
    var intValue: Int {
        switch self {
        case .integer(let intVal):
            return intVal
        case .float(let floatVal):
            return Int(floatVal)
        case .string(let strVal):
            return Int(strVal) ?? 0
        }
    }
    
    var floatValue: Double {
        switch self {
        case .integer(let intVal):
            return Double(intVal)
        case .float(let floatVal):
            return floatVal
        case .string(let strVal):
            return Double(strVal) ?? Double.nan
        }
    }
    
    var stringValue: String {
        switch self {
        case .integer(let intVal):
            return String(intVal)
        case .float(let floatVal):
            let formatter = NumberFormatter()
            formatter.minimumIntegerDigits = 1
            formatter.minimumFractionDigits = 0
            formatter.maximumFractionDigits = 20
            return formatter.string(from: floatVal as NSNumber) ?? ""
        case .string(let strVal):
            return strVal
        }
    }
}

Pretty much exactly the same as above, but with the word “Parser” in its name.

If we swap out the [String] for [ParserResponse], and load it up in the parser function properly, like so:

func recursiveDescentParser(_ inDictionaryToBeParsed: [AnyHashable: Any], blackList inBlacklist: [String] = [], whiteList inWhiteList: [String] = []) -> [ParserResponse] {
    var ret = [ParserResponse]()
    
    func isKeyValid(_ inKey: String) -> Bool {
        if inBlacklist.contains(inKey) {
            return false
        }
        
        if inWhiteList.isEmpty || inWhiteList.contains(inKey) {
            return true
        }
        
        return false
    }
    
    inDictionaryToBeParsed.forEach {
        if let asFloat = ($0.value as? Double ?? Double($0.value as? String ?? "ERROR")) {
            if let keyString = $0.key as? String, isKeyValid(keyString) {
                if floor(asFloat) == asFloat {
                    ret += [.integer(Int(asFloat))]
                } else {
                    ret += [.float(asFloat)]
                }
            }
        } else if let asString = $0.value as? String, let keyString = $0.key as? String, isKeyValid(keyString) {
            ret += [.string(asString)]
        } else if let asDictionary = $0.value as? [AnyHashable: Any] {
            ret += recursiveDescentParser(asDictionary, blackList: inBlacklist, whiteList: inWhiteList)
        } else if let asDictionaryArray = $0.value as? [[AnyHashable: Any]] {
            asDictionaryArray.forEach { elem in
                ret += recursiveDescentParser(elem, blackList: inBlacklist, whiteList: inWhiteList)
            }
        } else if let asArray = $0.value as? [String] {
            asArray.forEach { elem in
                ret += recursiveDescentParser(["": elem], blackList: inBlacklist, whiteList: inWhiteList)
            }
        }
    }
    
    return ret.sorted()
}

…we end up with compiler errors. Specifically, this:

return ret.sorted()

(There are a couple of others, as well, but we’ll get to them). Looks like we can’t sort anymore.

That’s because ParserResponse doesn’t cleave to the Comparable protocol. We should add a “less-than” handler to the enum:

enum ParserResponse: Comparable {
static func < (lhs: ParserResponse, rhs: ParserResponse) -> Bool {
    switch lhs {
    case .integer:
        if case .float = rhs {
            return lhs.floatValue < rhs.floatValue
        } else if case .string = rhs {
            return lhs.floatValue < rhs.floatValue
        } else {
            return lhs.intValue < rhs.intValue
        }
    case .float:
        return lhs.floatValue < rhs.floatValue
    case .string:
        return lhs.stringValue < rhs.stringValue
    }
}

Note that we look to see if one of the arguments is actually a floating-point number. We also assume that a String value could hold a floating-point number, so the comparator uses floating-point numbers in those cases, even though we are an integer.

That will take care of the sorted() issue. Since we are on the Comparable kick, we should also toss in an Equatable, so we can continue to use the parser results like we are kind of used to:

enum ParserResponse: Comparable, Equatable {
static func == (lhs: ParserResponse, rhs: ParserResponse) -> Bool {
    switch lhs {
    case .integer:
        if case .float = rhs {
            return lhs.floatValue == rhs.floatValue
        } else if case .string = rhs {
            return lhs.floatValue == rhs.floatValue
        } else {
            return lhs.intValue == rhs.intValue
        }
    case .float:
        return lhs.floatValue == rhs.floatValue
    case .string:
        return lhs.stringValue == rhs.stringValue
    }
}

And we should also add casting operators to the three data types that we are implementing:

extension String {
    init(_ inParserResponse: ParserResponse) {
        self.init(inParserResponse.stringValue)
    }
}

extension Int {
    init(_ inParserResponse: ParserResponse) {
        self.init(inParserResponse.intValue)
    }
}

extension Double {
    init(_ inParserResponse: ParserResponse) {
        self.init(inParserResponse.floatValue)
    }
}

As we are about to find out, though, this still introduces a breaking change to the consumers of the parsing function. We still have some compiler errors:

let whatWeWantToSee: [String] = ["Element Value 01", "Element Value 02", "Element Value 03", "Element Value 04", "Element Value 05"]
let whatWeWantToSee2: [String] = ["0123456789!@~*&^%$#.,;:?/'", "12345.6789", "1234567890", "3", "3.14"]
let whatWeWantToSee3: [String] = ["0", "1", "2", "3", "4"]

All barf. They won’t compare against Arrays of the new data type. They are all Array of String, and now we are dealing with Array of ParserResponse.

We’ll need to convert them to Array of ParserResponse:

//let whatWeWantToSee: [String] = ["Element Value 01", "Element Value 02", "Element Value 03", "Element Value 04", "Element Value 05"]
let whatWeWantToSee: [ParserResponse] = [.string("Element Value 01"), .string("Element Value 02"), .string("Element Value 03"), .string("Element Value 04"), .string("Element Value 05")]
//let whatWeWantToSee2: [String] = ["0123456789!@~*&^%$#.,;:?/'", "12345.6789", "1234567890", "3", "3.14"]
let whatWeWantToSee2: [ParserResponse] = [.string("0123456789!@~*&^%$#.,;:?/'"), .integer(3), .float(3.14), .float(12345.6789), .integer(1234567890)]
//let whatWeWantToSee3: [String] = ["0", "1", "2", "3", "4"]
let whatWeWantToSee3: [ParserResponse] = [.integer(0), .integer(1), .integer(2), .integer(3), .integer(4)]

Note also, that the sort order of the middle (“whatWeWantToSee2”) set has changed. Things will sort a bit differently, now. It’s actually a feature; not a bug.

We’re done, but we need to make sure that we put out an advisory when releasing this version, because it will break current implementations.

WHY DIDN’T YOU USE TDD ON THIS?

Actually, we did –a bit. We “rode the coattails” of the tests we put in place while developing the parser. That last set of fixes was exactly that. The legacy tests failed, and we need to examine why. It ended up that we actually needed to adjust the tests to meet the new reality –not the other way around.

We didn’t write the test first for two reasons:

  1. In the first thing we did (the whitelist), the implementation was so small and safe, that it would have taken twice as long to write new tests, as it took to implement, and the current set of tests actually completely applied to it. We still, however, need to write more tests that address the specific features of the whitelist.
  2. In the second thing that we did (the differing data types), the requirements were totally vague. It would have taken weeks to fetch exact requirements, then go through all the meetings and negotiations that were necessary. It was far more reasonable to analyze the problem, and come up with a solution –developed by the system authors– than it would have been to hammer out a “camel is a horse designed by committee” chimera that almost certainly would have taken just as much time after release, and would have had a far lower quality level.

It’s really, REALLY important to factor in human elements. Meetings are EXPENSIVE. They can also result in highly sub-optimal designs.

In many cases, they can’t be avoided, but in this case, we were confident enough that the whitelist requirement, and the vague “parse integers” requirement was enough to develop a very solid response.

This will not always be the case. We were fortunate, here.

All that said, there’s still more to go. We need to write up a bunch more tests, formulate the advisory, and also, add some damn comments to the code. It’s bald as an egg.

CONCLUSION

…and we won’t be covering that in this series. I’ve tortured you guys long enough. This is not actually a shipping product, and it will dilute the quality of the exercise to get bogged down with those details.

However, IF THIS WERE A SHIPPING PRODUCT, you would be A BLITHERING IDIOT to skip that stuff.

We added flexibility.

Flexibility is great! It helps to “future-proof” the code. It allows its use in unforeseen circumstances.

Flexibility is awful! It introduces a gazillion little “trouble nodes.” Places where unintended behavior can crop up.

Testing flexible code is a huge task. It requires a fairly organized, scientific approach. You can’t just toss a bunch of random tests at it. You need to figure out its stress points, and bang on those. Look for rarely-followed code paths, and design tests for them, even if you don’t see them ever being used.

An important aspect of bug-fixing/testing/quality control, is that it’s important to find and fix bugs as quickly as possible after introducing them. That’s why you look for unexecuted code paths, and test stuff that might not get used for a while (and remove stuff that doesn’t apply or may not add any value).

It really, really stinks to have a “zombie bug,” come lurching out of a grave, because you never tested it, and some programmer just found that code path.

SAMPLE PLAYGROUND

/*
    <containerElement>
        <arrayElement>Element Value 01</arrayElement>
        <arrayElement>Element Value 02</arrayElement>
        <arrayElement>Element Value 03</arrayElement>
        <arrayElement>Element Value 04</arrayElement>
        <arrayElement>Element Value 05</arrayElement>
    </containerElement>

    <!--
        {"containerElement": [  {"arrayElement": "Element Value 01"},
                                {"arrayElement": "Element Value 02"},
                                {"arrayElement": "Element Value 03"},
                                {"arrayElement": "Element Value 04"},
                                {"arrayElement": "Element Value 05"}
                                ]
        }

 
        {"containerElement": {"arrayElement": [ "Element Value 01",
                                                "Element Value 02",
                                                "Element Value 03",
                                                "Element Value 04",
                                                "Element Value 05"
                                                ]
                            }
        }

        {"containerElement": [  {"arrayElement": {"value": "Element Value 01"}},
                                {"arrayElement": {"value": "Element Value 02"}},
                                {"arrayElement": {"value": "Element Value 03"}},
                                {"arrayElement": {"value": "Element Value 04"}},
                                {"arrayElement": {"value": "Element Value 05"}}
                                ]
        }
    -->

    <containerElement>
        <arrayElement>
            <value>Element Value 01</value>
            <value>Element Value 02</value>
            <value>Element Value 03</value>
            <value>Element Value 04</value>
            <value>Element Value 05</value>
        </arrayElement>
    </containerElement>

    <!--
        {"containerElement": {"arrayElement": [ {"value": "Element Value 01"},
                                                {"value": "Element Value 02"},
                                                {"value": "Element Value 03"},
                                                {"value": "Element Value 04"},
                                                {"value": "Element Value 05"}
                                                ]
                            }
        }
 
        {"containerElement": {"arrayElement": [ "Element Value 01",
                                                "Element Value 02",
                                                "Element Value 03",
                                                "Element Value 04",
                                                "Element Value 05"
                                                ]
                            }
        }
    -->

    <containerElement>
        <arrayElement value="Element Value 01"/>
        <arrayElement value="Element Value 02"/>
        <arrayElement value="Element Value 03"/>
        <arrayElement value="Element Value 04"/>
        <arrayElement value="Element Value 05"/>
    </containerElement>

    <!--
        {"containerElement": [  {"arrayElement": {"attributes": [{"value": "Element Value 01"}]}},
                                {"arrayElement": {"attributes": [{"value": "Element Value 02"}]}},
                                {"arrayElement": {"attributes": [{"value": "Element Value 03"}]}},
                                {"arrayElement": {"attributes": [{"value": "Element Value 04"}]}},
                                {"arrayElement": {"attributes": [{"value": "Element Value 05"}]}}
                                ]
        }

        {"containerElement": [  {"arrayElement": {"attributes": {"value": "Element Value 01"}}},
                                {"arrayElement": {"attributes": {"value": "Element Value 02"}}},
                                {"arrayElement": {"attributes": {"value": "Element Value 03"}}},
                                {"arrayElement": {"attributes": {"value": "Element Value 04"}}},
                                {"arrayElement": {"attributes": {"value": "Element Value 05"}}}
                                ]
        }
    -->

    <containerElement>
        <arrayElement index="0">Element Value 01</arrayElement>
        <arrayElement index="1">Element Value 02</arrayElement>
        <arrayElement index="2">Element Value 03</arrayElement>
        <arrayElement index="3">Element Value 04</arrayElement>
        <arrayElement index="4">Element Value 05</arrayElement>
    </containerElement>

    <!--
        {"containerElement": [  {"arrayElement": [{"attributes": [{"index": 0}]}, {"value": "Element Value 01"}]},
                                {"arrayElement": [{"attributes": [{"index": 1}]}, {"value": "Element Value 02"}]},
                                {"arrayElement": [{"attributes": [{"index": 2}]}, {"value": "Element Value 03"}]},
                                {"arrayElement": [{"attributes": [{"index": 3}]}, {"value": "Element Value 04"}]},
                                {"arrayElement": [{"attributes": [{"index": 4}]}, {"value": "Element Value 05"}]}
                                ]
        }

        {"containerElement": [  {"arrayElement": [{"attributes": [{"index": "0"}]}, {"value": "Element Value 01"}]},
                                {"arrayElement": [{"attributes": [{"index": "1"}]}, {"value": "Element Value 02"}]},
                                {"arrayElement": [{"attributes": [{"index": "2"}]}, {"value": "Element Value 03"}]},
                                {"arrayElement": [{"attributes": [{"index": "3"}]}, {"value": "Element Value 04"}]},
                                {"arrayElement": [{"attributes": [{"index": "4"}]}, {"value": "Element Value 05"}]}
                                ]
        }

        {"containerElement": [  {"arrayElement": [{"attributes": {"index": "0"}}, {"value": "Element Value 01"}]},
                                {"arrayElement": [{"attributes": {"index": "1"}}, {"value": "Element Value 02"}]},
                                {"arrayElement": [{"attributes": {"index": "2"}}, {"value": "Element Value 03"}]},
                                {"arrayElement": [{"attributes": {"index": "3"}}, {"value": "Element Value 04"}]},
                                {"arrayElement": [{"attributes": {"index": "4"}}, {"value": "Element Value 05"}]}
                                ]
        }

        {"containerElement": [  {"arrayElement": [{"attributes": [{"index": "0"}, {"value": "Element Value 01"}]}]},
                                {"arrayElement": [{"attributes": [{"index": "1"}, {"value": "Element Value 02"}]}]},
                                {"arrayElement": [{"attributes": [{"index": "2"}, {"value": "Element Value 03"}]}]},
                                {"arrayElement": [{"attributes": [{"index": "3"}, {"value": "Element Value 04"}]}]},
                                {"arrayElement": [{"attributes": [{"index": "4"}, {"value": "Element Value 05"}]}]}
                                ]
        }
    -->

    <containerElement>
        <arrayElement row="0" value="Element Value 01"/>
        <arrayElement row="1" value="Element Value 02"/>
        <arrayElement row="2" value="Element Value 03"/>
        <arrayElement row="3" value="Element Value 04"/>
        <arrayElement row="4" value="Element Value 05"/>
    </containerElement>

    <!--
        {"containerElement": [  {"arrayElement": {"attributes": [{"row": 0}, {"value": "Element Value 01"}]}},
                                {"arrayElement": {"attributes": [{"row": 1}, {"value": "Element Value 02"}]}},
                                {"arrayElement": {"attributes": [{"row": 2}, {"value": "Element Value 03"}]}},
                                {"arrayElement": {"attributes": [{"row": 3}, {"value": "Element Value 04"}]}},
                                {"arrayElement": {"attributes": [{"row": 4}, {"value": "Element Value 05"}]}}
                                ]
        }

        {"containerElement": [  {"arrayElement": [{"attributes": [{"row": 0}, {"value": "Element Value 01"}]}]},
                                {"arrayElement": [{"attributes": [{"row": 1}, {"value": "Element Value 02"}]}]},
                                {"arrayElement": [{"attributes": [{"row": 2}, {"value": "Element Value 03"}]}]},
                                {"arrayElement": [{"attributes": [{"row": 3}, {"value": "Element Value 04"}]}]},
                                {"arrayElement": [{"attributes": [{"row": 4}, {"value": "Element Value 05"}]}]}
                                ]
        }

        {"containerElement": [  {"arrayElement": {"attributes": [{"row": "0"}, {"value": "Element Value 01"}]}},
                                {"arrayElement": {"attributes": [{"row": "1"}, {"value": "Element Value 02"}]}},
                                {"arrayElement": {"attributes": [{"row": "2"}, {"value": "Element Value 03"}]}},
                                {"arrayElement": {"attributes": [{"row": "3"}, {"value": "Element Value 04"}]}},
                                {"arrayElement": {"attributes": [{"row": "4"}, {"value": "Element Value 05"}]}}
                                ]
        }

        {"containerElement": [  {"arrayElement": [{"attributes": [{"row": "0"}, {"value": "Element Value 01"}]}]},
                                {"arrayElement": [{"attributes": [{"row": "1"}, {"value": "Element Value 02"}]}]},
                                {"arrayElement": [{"attributes": [{"row": "2"}, {"value": "Element Value 03"}]}]},
                                {"arrayElement": [{"attributes": [{"row": "3"}, {"value": "Element Value 04"}]}]},
                                {"arrayElement": [{"attributes": [{"row": "4"}, {"value": "Element Value 05"}]}]}
                                ]
        }
    -->
 */

import Foundation

// Exercises
enum SomeEnum {
    case someCase(String)
}
let enumInstance = SomeEnum.someCase("someString")

struct SomeStruct {
    struct someCase {
        var value: String
        
        init(_  inValue: String) {
            value = inValue
        }
    }
}
let fauxEnumInstance = SomeStruct.someCase("someString")

enum SomeOtherEnum {
    case someCase(String)
    case someOtherCase(Int)
}

struct SomeOtherStruct {
    struct someCase {
        var value: String
        
        init(_  inValue: String) {
            value = inValue
        }
    }
    
    struct someOtherCase {
        var value: Int
        
        init(_  inValue: Int) {
            value = inValue
        }
    }
}

enum SomeFixedEnum: Int {
    case zero = 0
    case one = 1
}

struct SomeFixedStruct {
    struct zero {
        static var rawValue: Int {
            return 0
        }
    }
    
    struct one {
        static var rawValue: Int {
            return 1
        }
    }
}

struct FauxUnionStruct {
    struct stringVal {
        var value: String
        
        init(_  inValue: String) {
            value = inValue
        }
    }
    
    struct intVal {
        var value: Int
        
        init(_  inValue: Int) {
            value = inValue
        }
    }
    
    struct floatVal {
        var value: Double
        
        init(_  inValue: Double) {
            value = inValue
        }
    }
}

let aStringInstance = FauxUnionStruct.stringVal("someString")
let anIntInstance = FauxUnionStruct.intVal(123456789)
let aFloatInstance = FauxUnionStruct.floatVal(1234.56789)

print(aStringInstance.value)
print(anIntInstance.value)
print(aFloatInstance.value)

enum FauxUnionEnum {
    case stringVal(String)
    case intVal(Int)
    case floatVal(Double)
    
    var string: String {
        switch self {
        case .stringVal(let str):
            return str
        case .intVal(let int):
            return String(int)
        case .floatVal(let flt):
            let formatter = NumberFormatter()
            formatter.minimumIntegerDigits = 1
            formatter.minimumFractionDigits = 0
            formatter.maximumFractionDigits = 4
            return formatter.string(from: flt as NSNumber) ?? ""
        }
    }
    
    var int: Int {
        switch self {
        case .stringVal(let str):
            return Int(str) ?? 0
        case .intVal(let int):
            return int
        case .floatVal(let flt):
            return Int(flt)
        }
    }
    
    var float: Double {
        switch self {
        case .stringVal(let str):
            return Double(str) ?? Double.nan
        case .intVal(let int):
            return Double(int)
        case .floatVal(let flt):
            return flt
        }
    }
}

let anotherStringInstance = FauxUnionEnum.stringVal("someString")
let anotherIntInstance = FauxUnionEnum.intVal(123456789)
let anotherFloatInstance = FauxUnionEnum.floatVal(1234.56789)

print(anotherStringInstance)
print(anotherIntInstance)
print(anotherFloatInstance)

print(anotherStringInstance.string)
print(anotherStringInstance.int)
print(anotherStringInstance.float)

print(anotherIntInstance.string)
print(anotherIntInstance.int)
print(anotherIntInstance.float)

print(anotherFloatInstance.string)
print(anotherFloatInstance.int)
print(anotherFloatInstance.float)

// Back to the Parser

func convertJSONToDictionary(_ inJSON: String) -> [String: Any] {
    do {
        if let stringData = inJSON.data(using: .utf8), let jsonDict = try JSONSerialization.jsonObject(with: stringData) as? [String: Any] {
            return jsonDict
        }
    } catch(let error) {
        print(String(describing: error))
    }
    return [:]
}

var jsonInterpretations: [[String: Any]] = []

var jsonInterpretationsSecondaryTests: [[String: Any]] = []

func loadJSONIntoDictionaries() {
    // Initial test set
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": \"Element Value 01\"}, {\"arrayElement\": \"Element Value 02\"}, {\"arrayElement\": \"Element Value 03\"}, {\"arrayElement\": \"Element Value 04\"}, {\"arrayElement\": \"Element Value 05\"}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": {\"arrayElement\": [\"Element Value 01\", \"Element Value 02\", \"Element Value 03\", \"Element Value 04\", \"Element Value 05\"]}}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": {\"value\": \"Element Value 01\"}}, {\"arrayElement\": {\"value\": \"Element Value 02\"}}, {\"arrayElement\": {\"value\": \"Element Value 03\"}}, {\"arrayElement\": {\"value\": \"Element Value 04\"}}, {\"arrayElement\": {\"value\": \"Element Value 05\"}}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": {\"arrayElement\": [{\"value\": \"Element Value 01\"}, {\"value\": \"Element Value 02\"}, {\"value\": \"Element Value 03\"}, {\"value\": \"Element Value 04\"}, {\"value\": \"Element Value 05\"}]}}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": {\"arrayElement\": [\"Element Value 01\", \"Element Value 02\", \"Element Value 03\", \"Element Value 04\", \"Element Value 05\"]}}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": {\"attributes\": [{\"value\": \"Element Value 01\"}]}}, {\"arrayElement\": {\"attributes\": [{\"value\": \"Element Value 02\"}]}}, {\"arrayElement\": {\"attributes\": [{\"value\": \"Element Value 03\"}]}}, {\"arrayElement\": {\"attributes\": [{\"value\": \"Element Value 04\"}]}}, {\"arrayElement\": {\"attributes\": [{\"value\": \"Element Value 05\"}]}}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": {\"attributes\": {\"value\": \"Element Value 01\"}}}, {\"arrayElement\": {\"attributes\": {\"value\": \"Element Value 02\"}}}, {\"arrayElement\": {\"attributes\": {\"value\": \"Element Value 03\"}}}, {\"arrayElement\": {\"attributes\": {\"value\": \"Element Value 04\"}}}, {\"arrayElement\": {\"attributes\": {\"value\": \"Element Value 05\"}}}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": [{\"attributes\": [{\"index\": 0}]}, {\"value\": \"Element Value 01\"}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": 1}]}, {\"value\": \"Element Value 02\"}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": 2}]}, {\"value\": \"Element Value 03\"}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": 3}]}, {\"value\": \"Element Value 04\"}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": 4}]}, {\"value\": \"Element Value 05\"}]}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": [{\"attributes\": [{\"index\": \"0\"}]}, {\"value\": \"Element Value 01\"}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": \"1\"}]}, {\"value\": \"Element Value 02\"}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": \"2\"}]}, {\"value\": \"Element Value 03\"}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": \"3\"}]}, {\"value\": \"Element Value 04\"}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": \"4\"}]}, {\"value\": \"Element Value 05\"}]}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": [{\"attributes\": {\"index\": \"0\"}}, {\"value\": \"Element Value 01\"}]}, {\"arrayElement\": [{\"attributes\": {\"index\": \"1\"}}, {\"value\": \"Element Value 02\"}]}, {\"arrayElement\": [{\"attributes\": {\"index\": \"2\"}}, {\"value\": \"Element Value 03\"}]}, {\"arrayElement\": [{\"attributes\": {\"index\": \"3\"}}, {\"value\": \"Element Value 04\"}]}, {\"arrayElement\": [{\"attributes\": {\"index\": \"4\"}}, {\"value\": \"Element Value 05\"}]}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": [{\"attributes\": [{\"index\": \"0\"}, {\"value\": \"Element Value 01\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": \"1\"}, {\"value\": \"Element Value 02\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": \"2\"}, {\"value\": \"Element Value 03\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": \"3\"}, {\"value\": \"Element Value 04\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"index\": \"4\"}, {\"value\": \"Element Value 05\"}]}]}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": {\"attributes\": [{\"row\": 0}, {\"value\": \"Element Value 01\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": 1}, {\"value\": \"Element Value 02\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": 2}, {\"value\": \"Element Value 03\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": 3}, {\"value\": \"Element Value 04\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": 4}, {\"value\": \"Element Value 05\"}]}}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": [{\"attributes\": [{\"row\": 0}, {\"value\": \"Element Value 01\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"row\": 1}, {\"value\": \"Element Value 02\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"row\": 2}, {\"value\": \"Element Value 03\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"row\": 3}, {\"value\": \"Element Value 04\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"row\": 4}, {\"value\": \"Element Value 05\"}]}]}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": {\"attributes\": [{\"row\": \"0\"}, {\"value\": \"Element Value 01\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": \"1\"}, {\"value\": \"Element Value 02\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": \"2\"}, {\"value\": \"Element Value 03\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": \"3\"}, {\"value\": \"Element Value 04\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": \"4\"}, {\"value\": \"Element Value 05\"}]}}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": [{\"attributes\": [{\"row\": \"0\"}, {\"value\": \"Element Value 01\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"row\": \"1\"}, {\"value\": \"Element Value 02\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"row\": \"2\"}, {\"value\": \"Element Value 03\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"row\": \"3\"}, {\"value\": \"Element Value 04\"}]}]}, {\"arrayElement\": [{\"attributes\": [{\"row\": \"4\"}, {\"value\": \"Element Value 05\"}]}]}]}"))

    // Extra Credit
    jsonInterpretations.append(convertJSONToDictionary("{\"arrayElement\": [{\"value\": \"Element Value 01\"}, {\"value\": \"Element Value 02\"}, {\"value\": \"Element Value 03\"}, {\"value\": \"Element Value 04\"}, {\"value\": \"Element Value 05\"}]}"))
    jsonInterpretations.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": {\"attributes\": [{\"row\": 3}, {\"value\": \"Element Value 04\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": 2}, {\"value\": \"Element Value 03\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": 1}, {\"value\": \"Element Value 02\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": 4}, {\"value\": \"Element Value 05\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": 0}, {\"value\": \"Element Value 01\"}]}}]}"))
    
    // New Format, same set of tests.
    jsonInterpretations.append(convertJSONToDictionary("{\"arrayElement\": [{\"1\": \"Element Value 01\"}, {\"2\": \"Element Value 02\"}, {\"3\": \"Element Value 03\"}, {\"4\": \"Element Value 04\"}, {\"5\": \"Element Value 05\"}]}"))

    // Differing Data
    jsonInterpretationsSecondaryTests.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": {\"attributes\": [{\"row\": 3}, {\"value\": 3}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": 2}, {\"value\": \"3.14\"}]}}, {\"arrayElement\": {\"attributes\": [{\"index\": 1}, {\"value\": \"0123456789!@~*&^%$#.,;:?/'\"}]}}, {\"arrayElement\": {\"attributes\": [{\"row\": 4}, {\"value\": 1234567890}]}}, {\"arrayElement\": {\"attributes\": [{\"index\": 0}, {\"value\": \"12345.6789\"}]}}]}"))
    jsonInterpretationsSecondaryTests.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": \"3\"}, {\"arrayElement\": \"3.14\"}, {\"arrayElement\": \"0123456789!@~*&^%$#.,;:?/'\"}, {\"arrayElement\": \"1234567890\"}, {\"arrayElement\": \"12345.6789\"}]}"))
    jsonInterpretationsSecondaryTests.append(convertJSONToDictionary("{\"containerElement\": [{\"arrayElement\": 3}, {\"arrayElement\": 3.14}, {\"arrayElement\": \"0123456789!@~*&^%$#.,;:?/'\"}, {\"arrayElement\": 1234567890}, {\"arrayElement\": 12345.6789}]}"))
}

enum ParserResponse: Comparable, Equatable {
    case string(String)
    case integer(Int)
    case float(Double)
    
    var intValue: Int {
        switch self {
        case .integer(let intVal):
            return intVal
        case .float(let floatVal):
            return Int(floatVal)
        case .string(let strVal):
            return Int(strVal) ?? 0
        }
    }
    
    var floatValue: Double {
        switch self {
        case .integer(let intVal):
            return Double(intVal)
        case .float(let floatVal):
            return floatVal
        case .string(let strVal):
            return Double(strVal) ?? Double.nan
        }
    }
    
    var stringValue: String {
        switch self {
        case .integer(let intVal):
            return String(intVal)
        case .float(let floatVal):
            let formatter = NumberFormatter()
            formatter.minimumIntegerDigits = 1
            formatter.minimumFractionDigits = 0
            formatter.maximumFractionDigits = 20
            return formatter.string(from: floatVal as NSNumber) ?? ""
        case .string(let strVal):
            return strVal
        }
    }
    
    static func == (lhs: ParserResponse, rhs: ParserResponse) -> Bool {
        switch lhs {
        case .integer:
            if case .float = rhs {
                return lhs.floatValue == rhs.floatValue
            } else if case .string = rhs {
                return lhs.floatValue == rhs.floatValue
            } else {
                return lhs.intValue == rhs.intValue
            }
        case .float:
            return lhs.floatValue == rhs.floatValue
        case .string:
            return lhs.stringValue == rhs.stringValue
        }
    }
    
    static func < (lhs: ParserResponse, rhs: ParserResponse) -> Bool {
        switch lhs {
        case .integer:
            if case .float = rhs {
                return lhs.floatValue < rhs.floatValue
            } else if case .string = rhs {
                return lhs.floatValue < rhs.floatValue
            } else {
                return lhs.intValue < rhs.intValue
            }
        case .float:
            return lhs.floatValue < rhs.floatValue
        case .string:
            return lhs.stringValue < rhs.stringValue
        }
    }
}

extension String {
    init(_ inParserResponse: ParserResponse) {
        self.init(inParserResponse.stringValue)
    }
}

extension Int {
    init(_ inParserResponse: ParserResponse) {
        self.init(inParserResponse.intValue)
    }
}

extension Double {
    init(_ inParserResponse: ParserResponse) {
        self.init(inParserResponse.floatValue)
    }
}

func recursiveDescentParser(_ inDictionaryToBeParsed: [AnyHashable: Any], blackList inBlacklist: [String] = [], whiteList inWhiteList: [String] = []) -> [ParserResponse] {
    var ret = [ParserResponse]()
    
    func isKeyValid(_ inKey: String) -> Bool {
        if inBlacklist.contains(inKey) {
            return false
        }
        
        if inWhiteList.isEmpty || inWhiteList.contains(inKey) {
            return true
        }
        
        return false
    }
    
    inDictionaryToBeParsed.forEach {
        if let asFloat = ($0.value as? Double ?? Double($0.value as? String ?? "ERROR")) {
            if let keyString = $0.key as? String, isKeyValid(keyString) {
                if floor(asFloat) == asFloat {
                    ret += [.integer(Int(asFloat))]
                } else {
                    ret += [.float(asFloat)]
                }
            }
        } else if let asString = $0.value as? String, let keyString = $0.key as? String, isKeyValid(keyString) {
            ret += [.string(asString)]
        } else if let asDictionary = $0.value as? [AnyHashable: Any] {
            ret += recursiveDescentParser(asDictionary, blackList: inBlacklist, whiteList: inWhiteList)
        } else if let asDictionaryArray = $0.value as? [[AnyHashable: Any]] {
            asDictionaryArray.forEach { elem in
                ret += recursiveDescentParser(elem, blackList: inBlacklist, whiteList: inWhiteList)
            }
        } else if let asArray = $0.value as? [String] {
            asArray.forEach { elem in
                ret += recursiveDescentParser(["": elem], blackList: inBlacklist, whiteList: inWhiteList)
            }
        }
    }
    
    return ret.sorted()
}

loadJSONIntoDictionaries()

//let whatWeWantToSee: [String] = ["Element Value 01", "Element Value 02", "Element Value 03", "Element Value 04", "Element Value 05"]
let whatWeWantToSee: [ParserResponse] = [.string("Element Value 01"), .string("Element Value 02"), .string("Element Value 03"), .string("Element Value 04"), .string("Element Value 05")]
//let whatWeWantToSee2: [String] = ["0123456789!@~*&^%$#.,;:?/'", "12345.6789", "1234567890", "3", "3.14"]
let whatWeWantToSee2: [ParserResponse] = [.string("0123456789!@~*&^%$#.,;:?/'"), .integer(3), .float(3.14), .float(12345.6789), .integer(1234567890)]
//let whatWeWantToSee3: [String] = ["0", "1", "2", "3", "4"]
let whatWeWantToSee3: [ParserResponse] = [.integer(0), .integer(1), .integer(2), .integer(3), .integer(4)]

// Test Set 1
jsonInterpretations.forEach {
    let parsedResult = recursiveDescentParser($0, blackList: ["index", "row"])
    if whatWeWantToSee != parsedResult {
        print("ERROR! Bad Result: \(parsedResult) for \($0)")
    } else {
//        print("\($0) Passes!")
    }
}

// Test Set 2
jsonInterpretationsSecondaryTests.forEach {
    let parsedResult = recursiveDescentParser($0, blackList: ["index", "row"])
    if whatWeWantToSee2 != parsedResult {
        print("ERROR! Bad Result: \(parsedResult) for \($0)")
    } else {
//        print("\($0) Passes!")
    }
}

// Test Set 3
jsonInterpretations.forEach {
    let parsedResult = recursiveDescentParser($0, whiteList: ["index", "row"])
    if !parsedResult.isEmpty, whatWeWantToSee3 != parsedResult {
        print("ERROR! Bad Result: \(parsedResult) for \($0)")
    } else {
//        print("\($0) Passes!")
    }
}