Permalink
Find file
ebdea73 Nov 6, 2016
105 lines (90 sloc) 5.61 KB
//
// json-attribute-merging.swift
// json2swift
//
// Created by Joshua Smith on 10/11/16.
// Copyright © 2016 iJoshSmith. All rights reserved.
//
// MARK: - JSONElementSchema extension
extension JSONElementSchema {
static func inferredByMergingAttributes(of schemas: [JSONElementSchema], named name: String) -> JSONElementSchema {
switch schemas.count {
case 0: return JSONElementSchema(name: name)
case 1: return schemas[0]
default: return schemas.dropFirst().reduce(schemas[0], merge(schema:withSchema:))
}
}
private static func merge(schema: JSONElementSchema, withSchema otherSchema: JSONElementSchema) -> JSONElementSchema {
return schema.merged(with: otherSchema)
}
fileprivate func merged(with schema: JSONElementSchema) -> JSONElementSchema {
let mergedAttributes = JSONType.merge(attributes: attributes, with: schema.attributes)
return JSONElementSchema(name: name, attributes: mergedAttributes)
}
}
// MARK: - JSONType extension
extension JSONType {
fileprivate static func merge(attributes: JSONAttributeMap, with otherAttributes: JSONAttributeMap) -> JSONAttributeMap {
let attributeNames = Set(Array(attributes.keys) + Array(otherAttributes.keys))
let mergedAttributes = attributeNames.map { name -> (String, JSONType) in
let type = attributes[name] ?? .nullable
let otherType = otherAttributes[name] ?? .nullable
let compatibleType = type.findCompatibleType(with: otherType)
return (name, compatibleType)
}
return JSONAttributeMap(entries: mergedAttributes)
}
internal func findCompatibleType(with type: JSONType) -> JSONType {
return JSONType.compatible(with: self, and: type)
}
private static func compatible(with type1: JSONType, and type2: JSONType) -> JSONType {
switch (type1, type2) {
// Element
case let (.element(r1, s1), .element(r2, s2)): return .element(isRequired: r1 && r2, schema: s1.merged(with: s2))
case let (.element(_, s), .nullable): return .element(isRequired: false, schema: s)
case let (.nullable, .element(_, s)): return .element(isRequired: false, schema: s)
// Element Array
case let (.elementArray(r1, s1, n1), .elementArray(r2, s2, n2)): return .elementArray(isRequired: r1 && r2, elementSchema: s1.merged(with: s2), hasNullableElements: n1 || n2)
case let (.elementArray(_, s, n), .nullable): return .elementArray(isRequired: false, elementSchema: s, hasNullableElements: n)
case let (.nullable, .elementArray(_, s, n)): return .elementArray(isRequired: false, elementSchema: s, hasNullableElements: n)
// Value Array
case let (.valueArray(r1, t1), .valueArray(r2, t2)): return .valueArray(isRequired: r1 && r2, valueType: t1.findCompatibleType(with: t2))
case let (.valueArray(_, t), .nullable): return .valueArray(isRequired: false, valueType: t)
case let (.nullable, .valueArray(_, t)): return .valueArray(isRequired: false, valueType: t)
// Numeric
case let (.number(r1, fp1), .number(r2, fp2)): return .number(isRequired: r1 && r2, isFloatingPoint: fp1 || fp2)
case let (.number(_, fp), .nullable): return .number(isRequired: false, isFloatingPoint: fp)
case let (.nullable, .number(_, fp)): return .number(isRequired: false, isFloatingPoint: fp)
// Text
case let (.date(r1, f), .date(r2, _)): return .date( isRequired: r1 && r2, format: f)
case let (.date(r1, _), .url(r2)): return .string(isRequired: r1 && r2)
case let (.date(r1, f), .string(r2)): return .date( isRequired: r1 && r2, format: f)
case let (.date(_, f), .nullable): return .date( isRequired: false, format: f)
case let (.nullable, .date(_, f)): return .date( isRequired: false, format: f)
case let (.url(r1), .date(r2, _)): return .string(isRequired: r1 && r2)
case let (.url(r1), .url(r2)): return .url( isRequired: r1 && r2)
case let (.url(r1), .string(r2)): return .string(isRequired: r1 && r2)
case (.url, .nullable): return .url( isRequired: false)
case (.nullable, .url): return .url( isRequired: false)
case let (.string(r1), .date(r2, f)): return .date( isRequired: r1 && r2, format: f)
case let (.string(r1), .url(r2)): return .string(isRequired: r1 && r2)
case let (.string(r1), .string(r2)): return .string(isRequired: r1 && r2)
case (.string, .nullable): return .string(isRequired: false)
case (.nullable, .string): return .string(isRequired: false)
// Boolean
case let (.bool(r1), .bool(r2)): return .bool(isRequired: r1 && r2)
case (.bool, .nullable): return .bool(isRequired: false)
case (.nullable, .bool): return .bool(isRequired: false)
// Nullable (e.g. both attribute values were null)
case (.nullable, .nullable): return .nullable
// Empty array
case (.emptyArray, .emptyArray): return .emptyArray
case (.elementArray, .emptyArray): return type1
case (.valueArray, .emptyArray): return type1
case (.emptyArray, .elementArray): return type2
case (.emptyArray, .valueArray): return type2
// Unrelated types
default: return .anything
}
}
}