Erste Schritte yaml parsen in Java

Status
Nicht offen für weitere Antworten.

BobBob

Mitglied
Hallo,

weiß zufällig jemand von euch, wie man eine (generische) Yaml-Datei in Java einlesen und iterieren kann?

Hier ist mein Versuch:

Java:
import org.yaml.snakeyaml.Yaml;

import java.io.File;
import java.io.FileReader;
import java.util.LinkedHashMap;
import java.util.Map;


public class Main {
    public static class Items {
        public LinkedHashMap<String, Object> map;
    }

    public static void main(String[] args) throws Exception {
        prettify(new File("test.yml"));
    }

    public static void prettify(File file) throws Exception {
        Items load = null;
        try (FileReader fr = new FileReader(file)) {
            load = new Yaml().load(fr);
        }
        assert load != null;
        iterate(load, 0);
    }

    public static void iterate(Items items, int depth) {
        for (Map.Entry<String, Object> e : items.map.entrySet()) {
            if (e.getValue() instanceof Items) {
                iterate((Items) e.getValue(), depth + 1);
            } else {
                System.out.println("  ".repeat(depth) + e);
            }
        }
    }
}

Fehler:
Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class Main$Items (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; Main$Items is in unnamed module of loader 'app')
at Main.prettify(Main.java:20)

Ich hatte auch nach Beispielen gesucht, fand aber nur veralteten Käse.

Nach Dokumentation (https://bitbucket.org/snakeyaml/snakeyaml/wiki/Documentation) sollte dies eigentlich mit SnakeYaml möglich sein.

Oder gäbe es auch noch eine andere Parser Library?

Vielen Dank für jede Anregung
 
Ufff, es hat schon geklappt:

Java:
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

import java.io.File;
import java.io.FileReader;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


public class Main {
    public static void main(String[] args) throws Exception {
        prettify(new File("test.yml"));
    }

    public static void prettify(File file) throws Exception {
        LinkedHashMap<String, Object> load = null;
        try (FileReader fr = new FileReader(file)) {
            load = new Yaml(new Constructor(LinkedHashMap.class, new LoaderOptions())).load(fr);
        }
        assert load != null;
        iterate(load, 0);
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void iterate(LinkedHashMap<String, Object> map, int depth) {
        for (Map.Entry<String, Object> e : map.entrySet()) {
            if (e.getValue() instanceof LinkedHashMap) {
                System.out.println("  ".repeat(depth) + e.getKey() + ":");
                iterate((LinkedHashMap<String, Object>) e.getValue(), depth + 1);
            } else if (e.getValue() instanceof List list) {
                System.out.println("  ".repeat(depth) + e.getKey() + ":");
                for (Object li : list) {
                    System.out.println("  ".repeat(depth) + " - " + li);
                }
            } else {
                System.out.println("  ".repeat(depth) + e);
            }
        }
    }
}

Mit mehrzeiligen Items gibt es aber noch Probleme.

Weiß jemand, wie man einen bestimmten Item ändern kann und dann die Datei neu schreiben kann?
 
Machst Du jetzt ein "Suchen & Ersetzen", oder baust Du Dir deinen eigenen Parser?
Ich suche mir "Chunks" heraus und dann jeweils einzelne "Attribute", danach modifiziere und setze ich die "Chunks" geordnet wieder zusammen.

Ein DOM mit Manipulation wäre schön gewesen, aber das bietet die Lib nicht an.
 
In der Map fehlt aber die (einfache) Navigierbarkeit. Ich kann nicht sagen, wo ich gerade bin, herkam oder hin will. Das ist etwas unkomfortabel, bzw. brauche ich dann zusätzliche Hilfs-Konstrukte drum rum.
 
Na gut mihe7 (eine Antwort auf meine Frage wäre schön gewesen, vielleicht ist das aber auch heutzutage schon zu viel verlangt), bis jetzt habe ich Folgendes:

Java:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.PrintWriter;
import java.util.*;

public class Main {
    private static class Service extends AbstractList<String> implements Comparable<Service> {
        private final ArrayList<String> lines = new ArrayList<>();

        @Override
        public String get(int index) {
            return lines.get(index);
        }

        @Override
        public int size() {
            return lines.size();
        }

        @Override
        public boolean add(String s) {
            return lines.add(s);
        }

        @Override
        public String toString() {
            return String.join("\n", lines);
        }

        @Override
        public int compareTo(Service o) {
            boolean b1 = lines.contains("  traefik:");
            boolean b2 = o.lines.contains("  traefik:");
            if (b1 || b2) {
                return b1 ? b2 ? 0 : -1 : 1;
            }
            boolean b3 = lines.contains("    profiles:");
            boolean b4 = o.lines.contains("    profiles:");
            if (b3 ^ b4) {
                return b3 ? 1 : -1;
            }
            boolean b5 = lines.isEmpty();
            boolean b6 = o.lines.isEmpty();
            if (!b5 && !b6) {
                return get(0).replaceAll(":$", "").compareTo(o.get(0).replaceAll(":$", ""));
            }
            return b5 ? b6 ? 0 : -1 : 1;
        }
    }

    public static void main(String[] args) throws Exception {
        format(new File("test.yml"), new File("test_neu.yml"));
    }

    public static void format(File from, File to) throws Exception {
        TreeSet<Service> services = new TreeSet<>();
        try (BufferedReader br = new BufferedReader(new FileReader(from))) {
            Service current = new Service();
            String l;
            while ((l = br.readLine()) != null) {
                if (l.isBlank() || "services:".equals(l)) {
                    continue;
                }
                int c = l.length() - l.replace(" ", "").length();
                if (c == 2 && !current.isEmpty()) {
                    services.add(current);
                    current = new Service();
                }
                current.add(l);
            }
            services.add(current);
        }
        try (PrintWriter pw = new PrintWriter(to)) {
            pw.println("services:");
            pw.println();
            for (Service s : services) {
                pw.println(s);
                pw.println();
            }
        }
    }
}

Vermutlich wird das mit einer Map nicht einfacher umzusetzen sein, sodass reinventing the wheel möglicherweise gegeben ist.

Alle Services sollen nach Name sortiert werden, "traefik" nach vorn und alle mit "profiles" nach unten. Viel mehr Magic passiert nich.
 
Wenn es Dir um entsprechend klar definierte yml Dateien geht, dann könnte eine Möglichkeit sein, dass man hier die Entity Klassen entsprechend erstellt und dann snakeyaml nutzt, eben genau dieses Entities zu laden.

Wenn ich beim überfliegen es richtig gesehen habe, unterstützt snakeyaml das auch und das würde dir die Möglichkeit geben, halt die Daten direkt zu laden, im Model zu verändern und dann einfach wieder zu dumpen.
 
-> indirekte Frage.
Das verstehe ich in einem Forum als Angebot, den Code zu zeigen, falls jemand Bedarf anmeldet.

-> das steht oben drüber, generisch = keine feste Struktur
Bei Deiner Lösung müssen in der Datei mit einer Einrückung von 2 Leerzeichen "traefik:" und mit einer Einrückung von 4 Leerzeichen "profiles:" vorhanden sein. Da scheint mir diesbezüglich doch eine sehr starre Struktur vorausgesetzt zu werden. Letztlich aber egal, so lange Du mit Deiner Lösung zufrieden bist.
 
Letztlich aber egal, so lange Du mit Deiner Lösung zufrieden bist.
Mir ist in compareTo noch eine Unschönheit aufgefallen... besser wäre so :

Java:
    @Override
    public int compareTo(Service o) {
      boolean b1 = lines.contains("  traefik:");
      boolean b2 = o.lines.contains("  traefik:");
      if (b1 || b2) {
        return b1 ? b2 ? 0 : -1 : 1;
      }
      boolean b3 = lines.contains("    profiles:");
      boolean b4 = o.lines.contains("    profiles:");
      if (b3 ^ b4) {
        return b3 ? 1 : -1;
      }
      boolean b5 = lines.isEmpty();
      boolean b6 = o.lines.isEmpty();
      if (b5 || b6) {
        return b5 ? b6 ? 0 : -1 : 1;
      }
      return get(0).replaceAll(":$", "").compareTo(o.get(0).replaceAll(":$", ""));
    }

Bei Deiner Lösung müssen in der Datei mit einer Einrückung von 2 Leerzeichen "traefik:" und mit einer Einrückung von 4 Leerzeichen "profiles:" vorhanden sein.
Damit hast du genau recht.

Es wird ein valides und jeweils mit zwei Leerzeichen richtig eingerücktes yaml file vorausgesetzt. Ich fürchte, sollte ich diese Annahme nicht machen , dann wird es codemäßig etwas komplexer werden (da für jede "Zeile" dann eine zusätzliche Klasse brauche).
 
Also dein CompareTo sieht ja mal richtig Crazy aus.

Einerseits ist es so, dass es nur mit bestimmten Schlüsselwörtern auskommt. Siehe Implementierung. Aber da drin ist es auch inkonsequent. Warum kann ich denn bei Profiles ein exklusives oder verwenden und bei den anderen reicht oder?

Warum wird, wenn nix zutrifft ein ganz anderer Inhalt verglichen??

Sind wir uns sicher, dass das eine korrekte Implementierung von compareTo ist?

Ich tippe auf nein.
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Zurück
Oben