Some checks failed
rules-freshness / rules (push) Failing after 12m24s
The JSON writer escapes the characters a YAML reader mangles. PureYAML.JSON.parse and parseLocated read JSON with those characters held back and given back, so no position moves, and Convert.convert takes the format of its source.
66 lines
3.4 KiB
Swift
66 lines
3.4 KiB
Swift
import PureYAML
|
|
|
|
public extension PureYAML.JSON {
|
|
/// Standalone YAML <-> JSON conversion. It depends only on the parsing/emitting
|
|
/// engine and the JSON writer; it knows nothing about any editor or UI, so it is
|
|
/// usable on its own (a CLI, a server, a build step, a test).
|
|
///
|
|
/// A conversion succeeds only when, in order:
|
|
/// 1. the source parses,
|
|
/// 2. the value is representable in the target format, and
|
|
/// 3. the emitted output parses back to a value identical to the source
|
|
/// (a lossless round-trip check that also guards against emitter defects).
|
|
///
|
|
/// JSON is a subset of YAML 1.2, so the same engine parses both the source and
|
|
/// the re-parsed output; only "to JSON" can be rejected for representability (a
|
|
/// value may use a non-string key or a non-finite number). "To YAML" still
|
|
/// rejects duplicate keys, which are legal in JSON but not in YAML.
|
|
enum Convert {
|
|
/// Convert source text to the target format, validated end to end. When the format of the source is known it is given as `from`, and a source that is JSON
|
|
/// is read as JSON (``PureYAML/JSON/parse(_:)``), which keeps every character of its strings; without it the source is read by the YAML reader as it is.
|
|
public static func convert(_ source: String, from sourceFormat: Format? = nil, to target: Format, pretty: Bool = true) throws -> String {
|
|
let value = try parse(source, as: sourceFormat)
|
|
let output = try emit(value, as: target, pretty: pretty)
|
|
guard try parse(output, as: target) == value else {
|
|
throw Failure.roundTripMismatch(target)
|
|
}
|
|
return output
|
|
}
|
|
|
|
/// Emit a value tree in the target format. The value is validated for
|
|
/// representability first; the round-trip check is not run here (use
|
|
/// ``convert(_:to:pretty:)`` for the fully validated path).
|
|
public static func emit(_ value: PureYAML.Model.Value, as target: Format, pretty: Bool = true) throws -> String {
|
|
let issues = validator(for: target).collect(value).values
|
|
guard issues.isEmpty else {
|
|
throw Failure.notRepresentable(issues)
|
|
}
|
|
switch target {
|
|
case .json:
|
|
return try PureYAML.JSON.Writer.string(from: value, options: .init(pretty: pretty, indent: 2))
|
|
case .yaml:
|
|
return PureYAML.dump(value, options: .init(scalarStyle: .plainWhenSafe, collectionStyle: .block))
|
|
}
|
|
}
|
|
|
|
/// The validator for a target format. JSON additionally requires string keys
|
|
/// and finite numbers; YAML allows complex keys but still forbids duplicates.
|
|
public static func validator(for target: Format) -> PureYAML.Validation.Validator<PureYAML.Model.Value, PureYAML.Model.Value> {
|
|
switch target {
|
|
case .json:
|
|
PureYAML.JSON.Validator.representability
|
|
case .yaml:
|
|
PureYAML.Validation.Validator()
|
|
}
|
|
}
|
|
|
|
private static func parse(_ text: String, as format: Format?) throws -> PureYAML.Model.Value {
|
|
do {
|
|
return try format == .json ? PureYAML.JSON.parse(text) : PureYAML.parse(text)
|
|
} catch {
|
|
throw Failure.sourceInvalid(String(describing: error))
|
|
}
|
|
}
|
|
}
|
|
}
|