Files
purchases-ios/Sources/RulesEngine/Value+JSON.swift
T
Rick 12f3893db0 feat(checkpoints): public API surface (#7361)
* Add checkpoint public API surface

* Rename checkpoint paywall result to outcome

* Add checkpoint API compile tests

* Add checkpoint sources to Xcode project

* Hide checkpoint Objective-C API behind flag

* Align checkpoint API models with Android

* Move checkpoint public API to RevenueCatUI

* Export flagged checkpoint Objective-C API

* Type checkpoint parameters in core

* Fix checkpoint project membership and lint

* Own checkpoint API models in core

* Simplify checkpoint completion listener

* Allow core to construct checkpoint results

* Fix checkpoint test project membership

* Centralize Foundation number classification

* Share checkpoint models with Objective-C

* Preserve JSON number classification behavior

* Organize checkpoint API sources

* Simplify checkpoint API storage

* Restrict checkpoint outcome construction
2026-08-07 10:02:32 +02:00

69 lines
2.5 KiB
Swift

//
// Value+JSON.swift
//
// Created by Antonio Pallares.
//
import Foundation
/// Production JSON → `Value` parser. Converts the predicate JSON extracted
/// from the SDK artifact into the engine's typed `Value` tree. Used by
/// `RulesEngine.evaluate`; failures surface as `RulesEngine.EvaluationError.parse`.
extension RulesEngine.Value {
/// Parse a JSON string into a `Value`. `JSONSerialization` returns
/// numbers as `NSNumber`, so we use `CFNumber` type metadata to
/// distinguish booleans from ints from doubles — without that, a JSON
/// `true` and a JSON `1` both round-trip to `NSNumber` and lose their
/// type intent.
static func fromJSONString(_ input: String) throws -> RulesEngine.Value {
guard let data = input.data(using: .utf8) else {
throw RulesEngine.EvaluationError.parse(message: "non-UTF8 input")
}
let json: Any
do {
json = try JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed])
} catch {
throw RulesEngine.EvaluationError.parse(message: error.localizedDescription)
}
return try Self.fromJSONObject(json)
}
/// Recursively convert a value produced by `JSONSerialization` (the
/// `Any` is one of `NSNull`, `NSNumber`, `String`, `[Any]`, or
/// `[String: Any]`). Throws `RulesEngine.EvaluationError.parse` if it
/// encounters anything else.
static func fromJSONObject(_ object: Any) throws -> RulesEngine.Value {
if object is NSNull {
return .null
}
if let number = object as? NSNumber {
switch number.jsonNumberKind {
case .boolean:
return .bool(number.boolValue)
case .integer:
return .int(number.int64Value)
case .floatingPoint:
return .float(number.doubleValue)
}
}
if let string = object as? String {
return .string(string)
}
if let array = object as? [Any] {
return .array(try array.map(Self.fromJSONObject))
}
if let dict = object as? [String: Any] {
var result: [String: RulesEngine.Value] = [:]
result.reserveCapacity(dict.count)
for (key, value) in dict {
result[key] = try Self.fromJSONObject(value)
}
return .object(result)
}
throw RulesEngine.EvaluationError.parse(
message: "unexpected JSONSerialization output of type \(type(of: object))"
)
}
}