JSON-Pointer-Pfade für iOS auf dem Cloud-Mac prüfen

JSON-Pointer-Pfade für iOS auf dem Cloud-Mac prüfen

JSON-Pointer-Pfade für iOS auf dem Cloud-Mac prüfen

Der Server verortet einen Validierungsfehler unter /items/0/name, doch der iOS-Client hebt das falsche Feld hervor. Häufig liegt das nicht an der UI-Anbindung, sondern daran, dass der Pfadauflöser leere Schlüssel verwirft, 01 als Array-Index interpretiert oder fehlende Felder und gültige null-Werte gleich behandelt. Auf einem Cloud-Mac lassen sich diese Regeln zunächst mit einem eigenständigen Swift-Skript festschreiben. Anschließend können die Testfälle in das Test-Target der App übernommen werden, ohne den gesamten UI-Ablauf zu starten.

1. Zuerst den Vertrag für Lesezugriffe festlegen

Die hier gezeigte Implementierung unterstützt die JSON-String-Darstellung nach RFC 6901: Ein leerer String verweist auf das gesamte Dokument, alle anderen Pfade müssen mit / beginnen. Die mit # beginnende URI-Fragment-Darstellung wird nicht akzeptiert. Die Implementierung verändert keine Daten und ist auch keine JSONPath-Abfragesprache.

Beim Aufteilen müssen leere Segmente erhalten bleiben. Der Pfad / greift auf den leeren Schlüssel zu, /a/ auf den leeren Schlüssel im Objekt a. Der abschließende Schrägstrich darf daher nicht einfach entfernt werden. Die Namen von Objektmitgliedern werden immer als Strings verglichen. Nur wenn der aktuelle Knoten tatsächlich ein Array ist, wird ein numerischer Index ausgewertet.

Ein vorhandener Pfad mit dem Wert null ist ein erfolgreicher Lesezugriff. Ein nicht vorhandener Pfad ist ein Fehler. Verwenden Sie nicht denselben optionalen Wert, um beide Ergebnisse darzustellen.

Die angezeigte Fehlermeldung kann letztlich „Feld nicht gefunden“ lauten. Auf Testebene sollten Syntaxfehler, fehlende Mitglieder, Indexfehler und falsche Knotentypen jedoch unterscheidbar bleiben, damit sich Änderungen am API-Vertrag nachvollziehen lassen.

2. Strikte Escape-Dekodierung und Knotennavigation implementieren

Speichern Sie den folgenden Code als PointerCheck.swift. Er benötigt nur Foundation und läuft mit der aktuell ausgewählten Toolchain, ohne zusätzliche Pakete zu installieren. Die Escape-Dekodierung akzeptiert ausschließlich ~0 und ~1 und wandelt sie in ~ beziehungsweise / um.

import Foundation

enum PointerError: Error {
    case syntax
    case missing
    case index
    case scalar
}

func decodeToken(_ token: Substring) throws -> String {
    var result = ""
    var cursor = token.startIndex

    while cursor < token.endIndex {
        let character = token[cursor]
        cursor = token.index(after: cursor)

        if character != "~" {
            result.append(character)
            continue
        }

        guard cursor < token.endIndex else {
            throw PointerError.syntax
        }

        switch token[cursor] {
        case "0": result.append("~")
        case "1": result.append("/")
        default: throw PointerError.syntax
        }
        cursor = token.index(after: cursor)
    }
    return result
}

func resolve(_ pointer: String, in document: Any) throws -> Any {
    if pointer.isEmpty {
        return document
    }
    guard pointer.first == "/" else {
        throw PointerError.syntax
    }

    let tokens = try pointer.dropFirst()
        .split(separator: "/", omittingEmptySubsequences: false)
        .map { try decodeToken($0) }

    var current = document

    for token in tokens {
        if let object = current as? [String: Any] {
            guard let value = object[token] else {
                throw PointerError.missing
            }
            current = value
        } else if let array = current as? [Any] {
            let digits = token.utf8
            guard !digits.isEmpty,
                  digits.allSatisfy({ $0 >= 48 && $0 <= 57 }),
                  token == "0" || !token.hasPrefix("0"),
                  let index = Int(token),
                  index < array.count else {
                throw PointerError.index
            }
            current = array[index]
        } else {
            throw PointerError.scalar
        }
    }
    return current
}

Es darf nicht zuerst ~0 und danach ~1 ersetzt werden: Die ursprüngliche Folge ~01 muss zum wörtlichen Text ~1 werden, nicht zu einem Schrägstrich. Das zeichenweise Einlesen der Segmente ermöglicht es außerdem, eine alleinstehende Tilde und ~2 zurückzuweisen, statt ungültige Pfade stillschweigend zu „reparieren“.

Der Array-Zweig prüft auf ASCII-Ziffern und weist Vorzeichen, Leerraum sowie andere Ziffernzeichen zurück. Auch eine fehlgeschlagene Umwandlung in Int gilt als Indexfehler; ein erzwungenes Entpacken ist hier unzulässig. In einem Objekt ist "01" dagegen ein gültiger Schlüsselname, auf den die Array-Regeln nicht angewendet werden dürfen.

3. Leicht verwechselbare Grenzfälle mit kleinen Testdaten abdecken

Hängen Sie den folgenden Prüfcode an. Die Testdaten enthalten einen leeren Schlüssel, Schlüssel mit Sonderzeichen, ein Array, skalare Werte und null. Jede Assertion prüft genau eine Aussage.

let source = #"{"a/b":{"~key":[null,7]},"":9,"~1":11,"01":12}"#
let document = try JSONSerialization.jsonObject(
    with: Data(source.utf8),
    options: [.fragmentsAllowed]
)

let nullValue = try resolve("/a~1b/~0key/0", in: document)
precondition(nullValue is NSNull)

let emptyKey = try resolve("/", in: document)
precondition((emptyKey as? NSNumber)?.intValue == 9)

let escapedKey = try resolve("/~01", in: document)
precondition((escapedKey as? NSNumber)?.intValue == 11)

let numericKey = try resolve("/01", in: document)
precondition((numericKey as? NSNumber)?.intValue == 12)

let root = try resolve("", in: document)
precondition(root is [String: Any])

let rejected = [
    "a", "#/a", "/bad~2", "/bad~",
    "/absent", "/a~1b/~0key/01",
    "/a~1b/~0key/-", "/a~1b/~0key/2",
    "/a~1b/~0key/999999999999999999999999999999",
    "/a~1b/~0key/1/child"
]

for pointer in rejected {
    var failed = false
    do {
        _ = try resolve(pointer, in: document)
    } catch is PointerError {
        failed = true
    }
    precondition(failed, "Unexpected resolution success")
}

Ergänzen Sie weitere Fälle mit einem Array beziehungsweise einem skalaren Wert als Wurzel. Ein skalarer Wurzelwert muss mit einem leeren Pfad erfolgreich gelesen werden können; ein weiterer Zugriff darunter muss dagegen scheitern. Der Stringvergleich bleibt unverändert: Groß- und Kleinschreibung dürfen nicht automatisch angeglichen und Unicode-Zeichen nicht automatisch normalisiert werden, da sonst möglicherweise ein anderes Mitglied gelesen wird.

4. In den Cloud-Build einbinden, statt nur das Beispiel auszuführen

Führen Sie in einer SSH-Sitzung Folgendes aus:

set -eu
xcrun --find swift
xcrun swift PointerCheck.swift

Bei Erfolg endet das Skript ohne Ausgabe. Schlägt eine Assertion fehl, liefert es einen Exit-Status ungleich null. Führen Sie dieses Prüfskript nicht in einem optimierten Kompilierungsmodus aus, der die Prüfungen entfernen könnte. Für die Einbindung in die App gehört der Pfadauflöser in ein gemeinsam genutztes Modul. Überführen Sie die Assertions in entsprechende Prüfungen im Test-Target und führen Sie diese über den bestehenden Test-Einstiegspunkt des Projekts aus.

Das eigenständige Skript prüft die Lesesemantik auf dem Foundation-Datenbaum und ersetzt keine Integrationstests für das iOS-Target. Falls die App direkt durch eigene Decodierungsmodelle navigiert, müssen dafür dieselben Testfälle angelegt werden. Gehen Sie nicht davon aus, dass beide Darstellungen automatisch dasselbe Verhalten zeigen.

Stammen die Eingaben aus einer externen API, müssen außerdem Dokumentgröße, Pfadlänge und Segmentanzahl begrenzt werden, bevor sie den Pfadauflöser erreichen. Die Grenzwerte sollten sich am Umfang der fachlichen Datenfelder orientieren. Testen Sie sowohl Werte genau an der Grenze als auch darüber. Die kurzen Beispielpfade sind keine geeignete Grenze für den Ressourcenverbrauch.

5. Bei Fehlern die Eingabe prüfen, statt auf Vermutungen auszuweichen

Schlägt die Auflösung fehl, protokollieren Sie zunächst Fehlerkategorie, Segmentposition und Typ des aktuellen Knotens, nicht jedoch das vollständige fachliche Dokument. Auch der Pfad selbst kann Nutzerdaten enthalten und darf erst nach dem Entfernen oder Maskieren sensibler Angaben in Build-Anhänge aufgenommen werden.

Prüfen Sie anschließend, ob der Server einen JSON Pointer oder eine andere Pfadsyntax liefert und ob der Pfad zusätzlich URL-dekodiert wurde. Das Prozentzeichen ist in der hier unterstützten String-Darstellung ein gewöhnliches Zeichen. Eine zusätzliche Dekodierung darf nicht eigenmächtig erfolgen.

Nehmen Sie abschließend jede Eingabe, deren Fehler behoben wurde, in den festen Testdatensatz auf. Das Abnahmekriterium lautet: gültige Pfade exakt lesen und ungültige Pfade eindeutig zurückweisen — nicht „möglichst ein ähnliches Feld finden“. So können Fehlermeldungen, Konfigurationszugriffe und Feldzuordnungen denselben stabilen Pfadvertrag nutzen.

Häufig gestellte Fragen

Sind ein leerer Pfad und ein einzelner Schrägstrich gleichwertig?

Nein. Der leere Pfad liefert das gesamte Dokument. Ein einzelner Schrägstrich adressiert im Wurzelobjekt den Schlüssel mit leerem Namen. Fehlt dieser Schlüssel, muss die Auflösung scheitern.

Sind 01 oder ein Bindestrich als Array-Index zulässig?

Dieser Lese-Resolver lehnt beide ab. Er akzeptiert 0 oder Dezimalzahlen ohne führende Null. Ein Bindestrich bezeichnet kein lesbares Element; Bereichsüberschreitungen und Integer-Überläufe führen ebenfalls zum Fehler.

Cloud-Mac mit eigener Pipeline testen

ArmRDP bietet dedizierte physische Mac mini statt virtueller Maschinen. Mieten Sie tageweise und prüfen Sie zuerst Toolchain und Build-Ergebnisse, bevor Sie die passende Laufzeit für Ihr Team wählen.

Cloud-Mac mieten

Konfigurationen nachvollziehbar machen, Builds reproduzierbar halten

Prüfen Sie Modell, Mietdauer und Verbindungsschritte, bevor Sie Aufgaben einem dedizierten physischen Knoten zuweisen.