From c921dc7e3d864ed20b0b20989a9181115aedce5a Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 13 Jun 2025 09:33:54 -0700 Subject: [PATCH 1/4] Use custom codable implementation for encoding and decoding properties --- Sources/JSONSchema/JSONSchema.swift | 56 ++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/Sources/JSONSchema/JSONSchema.swift b/Sources/JSONSchema/JSONSchema.swift index 52cf9e7..7db19b6 100644 --- a/Sources/JSONSchema/JSONSchema.swift +++ b/Sources/JSONSchema/JSONSchema.swift @@ -461,7 +461,10 @@ extension JSONSchema: Codable { try encodeIfPresent(`enum`, forKey: .enum, into: &container) try encodeIfPresent(const, forKey: .const, into: &container) - try encodeIfNotEmpty(properties, forKey: .properties, into: &container) + // Custom encoding for properties to preserve order but encode as object + if !properties.isEmpty { + try container.encode(PropertyDictionary(properties), forKey: .properties) + } try encodeIfNotEmpty(required, forKey: .required, into: &container) if let additionalProperties = additionalProperties { @@ -679,9 +682,9 @@ extension JSONSchema: Codable { switch type { case "object": - let properties = - try container.decodeIfPresent( - OrderedDictionary.self, forKey: .properties) ?? [:] + let propertiesWrapper = try container.decodeIfPresent( + PropertyDictionary.self, forKey: .properties) + let properties = propertiesWrapper?.orderedDictionary ?? [:] let required = try container.decodeIfPresent([String].self, forKey: .required) ?? [] let additionalProperties = try container.decodeIfPresent( AdditionalProperties.self, forKey: .additionalProperties) @@ -861,6 +864,51 @@ extension JSONSchema: ExpressibleByNilLiteral { } } +// MARK: - Property Dictionary Wrapper + +/// A wrapper around OrderedDictionary that encodes as a JSON object while preserving key order. +private struct PropertyDictionary: Codable { + let orderedDictionary: OrderedDictionary + + init(_ orderedDictionary: OrderedDictionary) { + self.orderedDictionary = orderedDictionary + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DynamicKey.self) + + for (key, value) in orderedDictionary { + try container.encode(value, forKey: DynamicKey(stringValue: key)!) + } + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DynamicKey.self) + var result = OrderedDictionary() + + // Preserve order by using container.allKeys + for key in container.allKeys { + let value = try container.decode(JSONSchema.self, forKey: key) + result[key.stringValue] = value + } + + self.orderedDictionary = result + } + + private struct DynamicKey: CodingKey { + let stringValue: String + let intValue: Int? = nil + + init?(stringValue: String) { + self.stringValue = stringValue + } + + init?(intValue: Int) { + return nil + } + } +} + // MARK: - /// Standard format options for string values in JSON Schema. From 94ddc7931d3265ba18925dcef9387efa61214020 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 13 Jun 2025 09:55:44 -0700 Subject: [PATCH 2/4] Add property order user info key --- Sources/JSONSchema/JSONSchema.swift | 41 +++++- Tests/JSONSchemaTests/JSONSchemaTests.swift | 138 +++++++++++++++++++- 2 files changed, 173 insertions(+), 6 deletions(-) diff --git a/Sources/JSONSchema/JSONSchema.swift b/Sources/JSONSchema/JSONSchema.swift index 7db19b6..ad166c2 100644 --- a/Sources/JSONSchema/JSONSchema.swift +++ b/Sources/JSONSchema/JSONSchema.swift @@ -254,6 +254,22 @@ import OrderedCollections } extension JSONSchema { + /// The user info key for specifying property order during JSON decoding. + /// + /// When decoding JSON Schema objects, the standard `JSONDecoder` does not preserve + /// the order of properties from the original JSON. To maintain property order, you + /// can provide an array of property names in the decoder's `userInfo` dictionary + /// using this key. + /// + /// ## Example + /// ```swift + /// let decoder = JSONDecoder() + /// decoder.userInfo[JSONSchema.propertyOrderUserInfoKey] = ["name", "age", "email"] + /// let schema = try decoder.decode(JSONSchema.self, from: jsonData) + /// ``` + public static let propertyOrderUserInfoKey = CodingUserInfoKey( + rawValue: "JSONSchemaPropertyOrder")! + /// The title of the schema, if present. public var title: String? { switch self { @@ -886,10 +902,27 @@ private struct PropertyDictionary: Codable { let container = try decoder.container(keyedBy: DynamicKey.self) var result = OrderedDictionary() - // Preserve order by using container.allKeys - for key in container.allKeys { - let value = try container.decode(JSONSchema.self, forKey: key) - result[key.stringValue] = value + // Check if property order was provided in userInfo + if let keyOrder = decoder.userInfo[JSONSchema.propertyOrderUserInfoKey] as? [String] { + // Use the provided order + for key in keyOrder { + guard let codingKey = DynamicKey(stringValue: key) else { continue } + if container.contains(codingKey) { + let value = try container.decode(JSONSchema.self, forKey: codingKey) + result[key] = value + } + } + // Add any keys that weren't in the provided order + for key in container.allKeys where !keyOrder.contains(key.stringValue) { + let value = try container.decode(JSONSchema.self, forKey: key) + result[key.stringValue] = value + } + } else { + // Fallback to default behavior - order not preserved + for key in container.allKeys { + let value = try container.decode(JSONSchema.self, forKey: key) + result[key.stringValue] = value + } } self.orderedDictionary = result diff --git a/Tests/JSONSchemaTests/JSONSchemaTests.swift b/Tests/JSONSchemaTests/JSONSchemaTests.swift index 9edc1f7..875fed5 100644 --- a/Tests/JSONSchemaTests/JSONSchemaTests.swift +++ b/Tests/JSONSchemaTests/JSONSchemaTests.swift @@ -1196,10 +1196,13 @@ struct JSONSchemaTests { } let encoder = JSONEncoder() - encoder.outputFormatting = [.prettyPrinted] let data = try encoder.encode(schema) + // Extract property order from the encoded JSON for preservation + let expectedPropertyOrder = ["one", "two", "three", "four", "alpha", "zero", "beta"] + let decoder = JSONDecoder() + decoder.userInfo[JSONSchema.propertyOrderUserInfoKey] = expectedPropertyOrder let decodedSchema = try decoder.decode(JSONSchema.self, from: data) // Verify schemas are equivalent using sorted JSON comparison @@ -1227,7 +1230,12 @@ struct JSONSchemaTests { ] let literalData = try encoder.encode(literalSchema) - let decodedLiteralSchema = try decoder.decode(JSONSchema.self, from: literalData) + + let literalDecoder = JSONDecoder() + literalDecoder.userInfo[JSONSchema.propertyOrderUserInfoKey] = [ + "one", "two", "three", "four", + ] + let decodedLiteralSchema = try literalDecoder.decode(JSONSchema.self, from: literalData) if case let .object(_, _, _, _, _, _, properties, _, _) = decodedLiteralSchema { #expect(properties.count == 4) @@ -1236,4 +1244,130 @@ struct JSONSchemaTests { Issue.record("Decoded literal schema should be an object schema") } } + + @Test("Properties encode as JSON object, not array") + func testPropertiesEncodeAsObject() throws { + let schema: JSONSchema = .object( + properties: [ + "name": .string(), + "age": .integer(), + "email": .string(), + ] + ) + + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let data = try encoder.encode(schema) + let json = String(data: data, encoding: .utf8)! + + // Verify that properties are encoded as a JSON object, not an array + #expect(json.contains("\"properties\" : {")) + #expect(!json.contains("\"properties\" : [")) + + // Verify it contains the expected structure + #expect(json.contains("\"name\" : {")) + #expect(json.contains("\"age\" : {")) + #expect(json.contains("\"email\" : {")) + } + + @Test("Round-trip encoding/decoding preserves all properties") + func testRoundTripPreservesProperties() throws { + let originalSchema: JSONSchema = .object( + title: "Person", + description: "A person object", + properties: [ + "name": .string(minLength: 1), + "age": .integer(minimum: 0, maximum: 120), + "email": .string(format: .email), + "address": .object( + properties: [ + "street": .string(), + "city": .string(), + ] + ), + ], + required: ["name", "email"] + ) + + // Encode + let encoder = JSONEncoder() + let data = try encoder.encode(originalSchema) + + // Decode + let decoder = JSONDecoder() + let decodedSchema = try decoder.decode(JSONSchema.self, from: data) + + // Verify structure is preserved + guard + case let .object(title, description, _, _, _, _, properties, required, _) = + decodedSchema + else { + Issue.record("Decoded schema should be an object") + return + } + + #expect(title == "Person") + #expect(description == "A person object") + #expect(properties.count == 4) + #expect(Set(properties.keys) == Set(["name", "age", "email", "address"])) + #expect(Set(required) == Set(["name", "email"])) + + // Verify nested object properties + guard case let .object(_, _, _, _, _, _, addressProps, _, _) = properties["address"] else { + Issue.record("Address should be an object schema") + return + } + + #expect(addressProps.count == 2) + #expect(Set(addressProps.keys) == Set(["street", "city"])) + } + + @Test("Can decode JSON schema from external source") + func testDecodeExternalJSONSchema() throws { + let jsonString = """ + { + "type": "object", + "title": "User", + "properties": { + "id": {"type": "integer"}, + "username": {"type": "string", "minLength": 3}, + "profile": { + "type": "object", + "properties": { + "firstName": {"type": "string"}, + "lastName": {"type": "string"} + }, + "required": ["firstName", "lastName"] + } + }, + "required": ["id", "username"] + } + """ + + let data = jsonString.data(using: .utf8)! + let schema = try JSONDecoder().decode(JSONSchema.self, from: data) + + guard case let .object(title, _, _, _, _, _, properties, required, _) = schema else { + Issue.record("Should decode as object schema") + return + } + + #expect(title == "User") + #expect(properties.count == 3) + #expect(Set(properties.keys) == Set(["id", "username", "profile"])) + #expect(Set(required) == Set(["id", "username"])) + + // Verify nested object + guard + case let .object(_, _, _, _, _, _, profileProps, profileRequired, _) = properties[ + "profile"] + else { + Issue.record("Profile should be an object schema") + return + } + + #expect(profileProps.count == 2) + #expect(Set(profileProps.keys) == Set(["firstName", "lastName"])) + #expect(Set(profileRequired) == Set(["firstName", "lastName"])) + } } From a33cf526f7345db7aeb47c698ce65c2bf8d1457c Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Sat, 14 Jun 2025 05:20:03 -0700 Subject: [PATCH 3/4] Add affordances to decode properties in declared order --- README.md | 89 ++++- Sources/JSONSchema/JSONSchema.swift | 368 ++++++++++++++++++++ Tests/JSONSchemaTests/JSONSchemaTests.swift | 350 +++++++++++++++++++ 3 files changed, 801 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e8eb42a..10fa69c 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,15 @@ A Swift library for working with JSON Schema definitions — especially for declaring schemas for AI tool use. -This library implements core features of the -[JSON Schema](https://json-schema.org/) standard, +This library implements core features of the +[JSON Schema](https://json-schema.org/) standard, targeting the **draft-2020-12** version. 🙅‍♀️ This library specifically **does not** support the following features: - Document validation - Reference resolution -- Conditional validation keywords, like +- Conditional validation keywords, like `dependentRequired`, `dependentSchemas`, and `if`/`then`/`else` - Custom vocabularies and meta-schemas @@ -195,16 +195,93 @@ let decoder = JSONDecoder() let decodedSchema = try decoder.decode(JSONSchema.self, from: jsonData) ``` +### Preserving Property Order + +According to [the JSON spec](https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf) (emphasis added): + +> ## 6. Objects +> [...] The JSON syntax does not impose any restrictions on the strings used as names, +> does not require that name strings be unique, +> and **does not assign any significance to the ordering of name/value pairs**. [...] + +And yet, +JSON Schema documents often _do_ assign significance to the order of properties. +In such cases, it may be desireable to preserve this ordering. +For example, +ensuring that an auto-generated form for a `createEvent` tool +lists `start` before `end`. +For this reason, +the associated value for `JSONSchema.object` properties use +[the `OrderedDictionary` type from `apple/swift-collections`](https://github.com/apple/swift-collections) + +By default, +`JSONDecoder` doesn't guarantee stable ordering of keys. +However, this package provides the following affordances +to decode `JSONSchema` objects with properties +in order they appear in the JSON string: + +- A static `extractSchemaPropertyOrder` method + that extracts property order from the top-level `"properties"` field + of a JSON Schema object. +- A static `extractPropertyOrder` method + that extracts property order from any JSON object at a specified keypath. +- A static `propertyOrderUserInfoKey` constant + that you can pass to `JSONDecoder` + (determined with either extraction method or some other means) + to guide the ordering of JSON Schema object properties. + +```swift +let json = """ +{ + "type": "object", + "properties": { + "firstName": {"type": "string"}, + "lastName": {"type": "string"}, + "age": {"type": "integer"}, + "email": {"type": "string", "format": "email"} + } +} +""".data(using: .utf8)! + +// Extract property order from a JSON Schema object's "properties" field +if let propertyOrder = JSONSchema.extractSchemaPropertyOrder(from: jsonData) { + // Configure decoder to preserve order + let decoder = JSONDecoder() + decoder.userInfo[JSONSchema.propertyOrderUserInfoKey] = propertyOrder + + // Decode with preserved property order + let schema = try decoder.decode(JSONSchema.self, from: data) + + // Properties will maintain their original order: `firstName`, `lastName`, `age`, `email` +} + +// Or extract from a nested object using a keypath +let nestedJSONData = """ +{ + "definitions": { + "person": { + "firstName": "John", + "lastName": "Doe" + } + } +} +""".data(using: .utf8)! + +let keyOrder = JSONSchema.extractPropertyOrder(from: nestedJSONData, + at: ["definitions", "person"]) +// keyOrder will be ["firstName", "lastName"] +``` + ## Motivation -There are a few other packages out there for working with JSON Schema, +There are a few other packages out there for working with JSON Schema, but they did more than I needed. -This library focuses solely on defining and serializing JSON Schema values +This library focuses solely on defining and serializing JSON Schema values with a clean, ergonomic API.
_That's it_. -The [implementation](/Sources/JSONSchema/) is deliberately minimal: +The [implementation](/Sources/JSONSchema/) is deliberately minimal: two files, ~1,000 lines of code total. At its core is one big `JSONSchema` enumeration with associated values for most of the JSON Schema keywords you might want. diff --git a/Sources/JSONSchema/JSONSchema.swift b/Sources/JSONSchema/JSONSchema.swift index ad166c2..da18af5 100644 --- a/Sources/JSONSchema/JSONSchema.swift +++ b/Sources/JSONSchema/JSONSchema.swift @@ -1,6 +1,10 @@ import Foundation import OrderedCollections +#if canImport(RegexBuilder) + import RegexBuilder +#endif + /// A type that represents a JSON Schema definition. /// /// Use JSONSchema to create, manipulate, and encode/decode JSON Schema documents. @@ -270,6 +274,95 @@ extension JSONSchema { public static let propertyOrderUserInfoKey = CodingUserInfoKey( rawValue: "JSONSchemaPropertyOrder")! + /// Extracts the order of property keys from a JSON Schema object's "properties" field. + /// + /// This method is specifically designed for JSON Schema objects and automatically + /// extracts property keys from the "properties" field in the order they appear. + /// + /// - Parameter jsonData: The JSON Schema data to extract property order from. + /// - Returns: An array of property keys in the order they appear in the JSON Schema's properties field, or nil if extraction fails. + /// + /// ## Example + /// ```swift + /// let jsonString = """ + /// { + /// "type": "object", + /// "properties": { + /// "name": {"type": "string"}, + /// "age": {"type": "integer"}, + /// "email": {"type": "string"} + /// } + /// } + /// """ + /// + /// if let data = jsonString.data(using: .utf8), + /// let keyOrder = JSONSchema.extractSchemaPropertyOrder(from: data) { + /// let decoder = JSONDecoder() + /// decoder.userInfo[JSONSchema.propertyOrderUserInfoKey] = keyOrder + /// let schema = try decoder.decode(JSONSchema.self, from: data) + /// } + /// ``` + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) + public static func extractSchemaPropertyOrder(from jsonData: Data) -> [String]? { + return extractPropertyOrder(from: jsonData, at: ["properties"]) + } + + /// Extracts the order of property keys from a JSON object at a specified keypath. + /// + /// This general-purpose method can extract property key order from any JSON object + /// by navigating through the provided keypath. + /// + /// - Parameters: + /// - jsonData: The JSON data to extract keys from. + /// - keypath: An array of string keys representing the path to the target object. + /// An empty array extracts keys from the root object. + /// - Returns: An array of property keys in the order they appear in the JSON, or nil if extraction fails. + /// + /// ## Example + /// ```swift + /// let jsonString = """ + /// { + /// "definitions": { + /// "person": { + /// "name": "John", + /// "age": 30, + /// "email": "john@example.com" + /// } + /// } + /// } + /// """ + /// + /// if let data = jsonString.data(using: .utf8), + /// let keyOrder = JSONSchema.extractPropertyOrder(from: data, at: ["definitions", "person"]) { + /// // keyOrder will be ["name", "age", "email"] + /// } + /// ``` + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) + public static func extractPropertyOrder(from jsonData: Data, at keypath: [String] = []) + -> [String]? + { + guard let jsonString = String(data: jsonData, encoding: .utf8) else { return nil } + + // First validate it's valid JSON by trying to decode + do { + let decoded = try JSONDecoder().decode(JSONValue.self, from: jsonData) + + // Verify root is an object + guard case .object = decoded else { return nil } + + // Use the parser to extract keys + let parser = JSONKeyOrderParser(json: jsonString) + + if keypath.isEmpty { + return parser.extractRootKeys() + } else { + return parser.extractKeys(at: keypath) + } + } catch { + return nil + } + } + /// The title of the schema, if present. public var title: String? { switch self { @@ -1163,3 +1256,278 @@ extension AdditionalProperties: ExpressibleByDictionaryLiteral { self = .schema(.object(properties: .init(uniqueKeysWithValues: elements))) } } + +// MARK: - + +/// A simple JSON parser that extracts property keys in order. +@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) +private struct JSONKeyOrderParser { + private let json: String + + init(json: String) { + self.json = json + } + + /// Extract keys from the root object + func extractRootKeys() -> [String]? { + return extractKeysFromObject(json) + } + + /// Extract keys from an object at a specific keypath + func extractKeys(at keypath: [String]) -> [String]? { + guard !keypath.isEmpty else { + return extractRootKeys() + } + + // Navigate through the keypath to find the target object + var currentObject = json + + for key in keypath { + guard let nextObject = findObjectAtPath(key, in: currentObject) else { + return nil + } + currentObject = nextObject + } + + return extractKeysFromObject(currentObject) + } + + /// Find an object value for a given key within a JSON string + private func findObjectAtPath(_ targetKey: String, in jsonString: String) -> String? { + var index = jsonString.startIndex + var inString = false + var escapeNext = false + var braceDepth = 0 + + // Skip to opening brace + while index < jsonString.endIndex { + if jsonString[index] == "{" { + braceDepth = 1 + index = jsonString.index(after: index) + break + } + index = jsonString.index(after: index) + } + + guard braceDepth == 1 else { return nil } + + // Look for the key at root level + while index < jsonString.endIndex && braceDepth > 0 { + let char = jsonString[index] + + if escapeNext { + escapeNext = false + } else if inString { + if char == "\\" { + escapeNext = true + } else if char == "\"" { + inString = false + } + } else { + switch char { + case "\"": + if braceDepth == 1 { + // Potential key at root level + if let (key, keyEnd) = extractString(startingAt: index, in: jsonString) { + if key == targetKey { + // Found the key, now extract its value + if let objectStart = skipToValue(from: keyEnd, in: jsonString) { + return extractObject(startingAt: objectStart, in: jsonString) + } + } + index = keyEnd + continue + } + } + inString = true + + case "{": + braceDepth += 1 + + case "}": + braceDepth -= 1 + + default: + break + } + } + + index = jsonString.index(after: index) + } + + return nil + } + + /// Extract all top-level keys from a JSON object string + private func extractKeysFromObject(_ objectJson: String) -> [String]? { + var keys: [String] = [] + var index = objectJson.startIndex + var inString = false + var escapeNext = false + var braceDepth = 0 + + // Skip to opening brace + while index < objectJson.endIndex { + if objectJson[index] == "{" { + braceDepth = 1 + index = objectJson.index(after: index) + break + } + index = objectJson.index(after: index) + } + + guard braceDepth == 1 else { return nil } + + while index < objectJson.endIndex && braceDepth > 0 { + let char = objectJson[index] + + if escapeNext { + escapeNext = false + } else if inString { + if char == "\\" { + escapeNext = true + } else if char == "\"" { + inString = false + } + } else { + switch char { + case "\"": + if braceDepth == 1 { + // Extract key at top level + if let (key, keyEnd) = extractString(startingAt: index, in: objectJson) { + // Verify it's followed by a colon + var colonIndex = keyEnd + while colonIndex < objectJson.endIndex + && objectJson[colonIndex].isWhitespace + { + colonIndex = objectJson.index(after: colonIndex) + } + + if colonIndex < objectJson.endIndex && objectJson[colonIndex] == ":" { + keys.append(key) + } + + index = keyEnd + continue + } + } + inString = true + + case "{": + braceDepth += 1 + + case "}": + braceDepth -= 1 + + default: + break + } + } + + index = objectJson.index(after: index) + } + + return braceDepth == 0 ? keys : nil + } + + /// Extract a quoted string starting at the given index + private func extractString(startingAt startIndex: String.Index, in str: String? = nil) -> ( + String, String.Index + )? { + let targetString = str ?? json + guard startIndex < targetString.endIndex && targetString[startIndex] == "\"" else { + return nil + } + + var index = targetString.index(after: startIndex) + var result = "" + var escapeNext = false + + while index < targetString.endIndex { + let char = targetString[index] + + if escapeNext { + result.append("\\") + result.append(char) + escapeNext = false + } else if char == "\\" { + escapeNext = true + } else if char == "\"" { + return (result, targetString.index(after: index)) + } else { + result.append(char) + } + + index = targetString.index(after: index) + } + + return nil + } + + /// Skip whitespace and colon to get to the value + private func skipToValue(from index: String.Index, in jsonString: String) -> String.Index? { + var current = index + + // Skip whitespace + while current < jsonString.endIndex && jsonString[current].isWhitespace { + current = jsonString.index(after: current) + } + + // Expect colon + guard current < jsonString.endIndex && jsonString[current] == ":" else { + return nil + } + + current = jsonString.index(after: current) + + // Skip whitespace after colon + while current < jsonString.endIndex && jsonString[current].isWhitespace { + current = jsonString.index(after: current) + } + + return current < jsonString.endIndex ? current : nil + } + + /// Extract a complete object starting at the given index + private func extractObject(startingAt startIndex: String.Index, in jsonString: String) + -> String? + { + guard startIndex < jsonString.endIndex && jsonString[startIndex] == "{" else { + return nil + } + + var index = jsonString.index(after: startIndex) + var braceDepth = 1 + var inString = false + var escapeNext = false + + while index < jsonString.endIndex && braceDepth > 0 { + let char = jsonString[index] + + if escapeNext { + escapeNext = false + } else if inString { + if char == "\\" { + escapeNext = true + } else if char == "\"" { + inString = false + } + } else { + switch char { + case "\"": + inString = true + case "{": + braceDepth += 1 + case "}": + braceDepth -= 1 + default: + break + } + } + + index = jsonString.index(after: index) + } + + return braceDepth == 0 ? String(jsonString[startIndex.. Date: Sat, 14 Jun 2025 05:35:11 -0700 Subject: [PATCH 4/4] Amend motivation section --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 10fa69c..eacbecf 100644 --- a/README.md +++ b/README.md @@ -281,8 +281,7 @@ This library focuses solely on defining and serializing JSON Schema values with a clean, ergonomic API.
_That's it_. -The [implementation](/Sources/JSONSchema/) is deliberately minimal: -two files, ~1,000 lines of code total. +The [implementation](/Sources/JSONSchema/) is deliberately minimal. At its core is one big `JSONSchema` enumeration with associated values for most of the JSON Schema keywords you might want. No result builders, property wrappers, macros, or dynamic member lookup —