Skip to content

v2.4.0

Choose a tag to compare

@trasch trasch released this 10 Sep 11:25
· 62 commits to main since this release
46748e6
  • JSONValue — a Hashable/Sendable/Codable JSON value enum (string/number/int/bool/array/object/null), created from any property-style Sendable value. Numbers are normalized (integral 3.0 → .int(3)), so values compare and hash consistently across producers. Integers beyond Int.max fall back to .number (documented 2^53 precision caveat). Prior art: turf-swift's JSONValue, GEOSwift's JSON.
  • Feature.properties(as:decoder:) / GeoJson.foreignMembers(as:decoder:) — decode properties into any Decodable type. Values are converted to JSONValue, encoded to JSON, and decoded with the given JSONDecoder, so CodingKeys, dateDecodingStrategy, keyDecodingStrategy, … work and real DecodingErrors are thrown instead of failing silently. The normalization fixes the 3.0 → Int sharp edge from the issue.
  • Feature(encodedProperties:) — create a Feature from Encodable properties; values are stored as native Sendable values (Int stays Int). Non-object roots throw.
  • jsonValue(for:) / jsonForeignMember(for:) — JSONValue accessors for exhaustive pattern matching.
  • jsonProperties() / jsonForeignMembers() — bulk conversion of all properties / foreign members into [String: JSONValue] (direct conversion, no JSON round-trip, numbers normalized along the way).
  • JSONValue extensions — typed accessors (stringValue, intValue (.number(3.0) reads as 3), doubleValue (.int(3) reads as 3.0), boolValue, arrayValue, objectValue, isNull), chainable subscripts (value["a"]?["b"]?[0]), coercion accessors (coercedIntValue etc., also accepting "3"-style strings), ExpressibleBy*Literal conformances (let v: JSONValue = ["a": [1, 2.5, true]]), asJsonData/asJsonString(prettyPrinted:), init?(jsonData:)/init?(jsonString:), and description rendering compact JSON.
  • JSONValue custom == / hash(into:) — an integer and a number compare equal when the number is exactly that integer (.int(3) == .number(3.0), hashing equal, deduplicating in sets); same-case comparisons remain exact. Mirrors the parsing normalization but applies to directly-constructed values, too.
  • intValue/doubleValue/boolValue/stringValue(ForeignMember) — coercing scalar accessors (feature.intValue(for: "priority") succeeds on a stored 3.0; booleans are never converted to numbers, using the same objCType discriminator as JsonCoercion).
  • Hashable for Point, MultiPoint, LineString, MultiLineString, GeometryCollection, Feature, and FeatureCollection — explicit hash(into:) implementations consistent with each type's == (never synthesized, since cached boundingBox/foreignMembers are not part of ==).

Compatibility

100% backwards compatible:

  • New API only; the stored properties type, existing accessors (property(for:), subscript, foreignMember(for:)), and all == implementations are unchanged.