Durchlaufen und reduzieren Sie das JSON-Objekt in DataWeave rekursiv

Nov 19 2020

Ich möchte eine große JSON-Datei mit der folgenden Struktur durchlaufen und reduzieren, die eine Produkthierarchie zeigt (stellen Sie sich dies als Navigation in einem Online-Shop vor):

productGroups: [
    {
        "key": "child 1"
        ...
        "childrenProductGroups": [
            {
                "key": "child 1.1",
                ...,
                "childrenProductGroups": []
            },
            {
                "key": "child 1.2"
                ...
                "childrenProductGroups": [
                    {
                        "key": "child 1.2.1",
                        ...,
                        "childrenProductGroups": [
                            {
                                "key": "child 1.2.1.1",
                                ...,
                                childrenProductGroups": [
                                    ...
                                ]
                            }
                        ]
                    },
                    {
                        "key": "child 1.2.2",
                        ...,
                        "childrenProductGroups": []
                    }
                ]
                
            },
            {
                "key": "child 1.3",
                ...,
                "childrenProductGroups": [
                    ...
                ]
            }
        ]
    }, 
    {
        "key": "child 2",
        ...,
        "childrenProductGroups": [
            ...
        ]
    },
    {
        "key": "child 3",
        ...,
        "childrenProductGroups": [
            ...
        ]
    }
]

Und ich möchte sie in einem Format wie diesem reduzieren:

{
    "hierarchieSet": [
        {
            "Nodeid": "00000001", # Number in this json
            "Nodename": "child 1",
            "Tlevel": "01", # First child of product group
            "Parentid": "00000000", # Parent is null
            "Childid": "00000002", # Child node number
            "Nextid": "00000008" # Node number on the same level (child 2)
        }, 
        {
            "Nodeid": "00000002",
            "Nodename": "child 1.1",
            "Tlevel": "02",
            "Parentid": "00000001",
            "Childid": "00000003",
            "Nextid": "00000003"
        }, 
        {
            "Nodeid": "00000003",
            "Nodename": "child 1.2",
            "Tlevel": "02",
            "Parentid": "00000002",
            "Childid": "00000005",
            "Nextid": "00000007"
        }, 
        {
            "Nodeid": "00000004",
            "Nodename": "child 1.2.1",
            "Tlevel": "03",
            "Parentid": "00000003",
            "Childid": "0000005",
            "Nextid": "00000006"
        }
        , 
        {
            "Nodeid": "00000005",
            "Nodename": "child 1.2.1.1",
            "Tlevel": "04",
            "Parentid": "00000004",
            "Childid": "0000000", #No more children
            "Nextid": "00000000"
        }, 
        {
            "Nodeid": "00000006",
            "Nodename": "child 1.2.2",
            "Tlevel": "03",
            "Parentid": "00000003",
            "Childid": "0000000",
            "Nextid": "00000000"
        }, 
        {
            "Nodeid": "00000007",
            "Nodename": "child 1.3",
            "Tlevel": "02",
            "Parentid": "00000001",
            "Childid": "0000000",
            "Nextid": "00000000"
        }, 
        {
            "Nodeid": "00000008",
            "Nodename": "child 2",
            "Tlevel": "01",
            "Parentid": "00000000",
            "Childid": "0000009", # 00000009 not shown
            "Nextid": "00000014" # 
        }, 
        ...
        {
            "Nodeid": "000000014",
            "Nodename": "child 3",
            "Tlevel": "01",
            "Parentid": "00000000",
            "Childid": "00000015",
            "Nextid": "00000000" # 00000010 does not exist
        }
    ]
}

Daher habe ich einige Hauptprobleme identifiziert:

  • Rekursion der Baumstruktur
  • Die Elemente transformieren
  • Struktur abflachen
  • Eltern, Geschwister und Kinder im Auge behalten
  • Verfolgung der Rekursionsstufe
  • Zahlen formatieren

Ich habe versucht, dieses Problem mit zwei verschiedenen Ansätzen zu lösen:

  • Verwenden Sie DataWeave, um alle Elemente zu transformieren
  • Verwenden Sie Java, um die Struktur zu durchlaufen

Da ich ziemlich neu in der funktionalen Programmierung bin, habe ich mich mehr auf die Java-Implementierung konzentriert, bin aber auf eine Reihe von Problemen gestoßen.

Java Ansatz


lesen json> Init Baum var und weisen Sie die Java - Instanz> für jedes Element in der obersten Ebene Array invoke traverse(data, level)in Tree.java.
Tree.java:

import org.json.JSONObject;

public class Tree {
    private int id = 0;
    private List<Node> nodes = new ArrayList<Node>();
    
    public Tree() {
        nodes.add(new Node("01", "00000001", "HOME", "01", "00000000", "00000002", "00000000"));
    }
    
    public void traverse(String data, int level) {
        System.out.println(data);
        // TODO parse json
    }
    
    private void visit(JSONObject parent, JSONObject node, int level) {
        id++;
        nodes.add(new Node("01", String.valueOf(id), node.getString("key"), String.valueOf(level), "", "", ""));
    }
    
    public List<Node> getNodes() {
        return nodes;
    }

    private static class Node {
        private String zshop, nodename, parentid, childid, nextid, nodeid, tlevel;
        
        public Node(String zshop, String nodeid, String nodename, String tlevel, String parentid, String childid, String nextid) {
            this.zshop = zshop;
            this.nodeid = nodeid;
            this.nodename = nodename;
            this.tlevel = tlevel;
            this.parentid = parentid;
            this.childid = childid;
            this.nextid = nextid;
        }
    }
}

Beim Aufrufen der Aufrufaktion verwende ich folgende Nutzdaten:

%dw 2.0
output application/java
---
{
    data: vars.rootMessage.payload as String,
    level: 1
}

Dies führt jedoch zu folgendem Fehler:

"Objekt {Codierung: UTF-8, Medientyp: Text / JSON; Zeichensatz = UTF-8, MIME-Typ: Text / JSON, RAW: org.mule.weave.v2.el.SeekableCursorStream@50ecee52} (org.mule kann nicht erzwungen werden. weave.v2.el.MuleTypedValue@511ba9cc) zu String

5 | Daten: vars.rootMessage.payload als String, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Trace: at main (Zeile : 5, Spalte: 7) "Auswertungsausdruck:"% dw 2.0-Ausgabeanwendung / Java --- {Daten: vars.rootMessage.payload als String, Ebene: 1} ".

Ich habe eine Reihe von Dingen ausprobiert:

  • Wirf es auf ein ProductGroupObjekt, das ich in Java geschrieben habe
  • Versuchen Sie, das abgerufene Objekt zu übertragen org.json.JSONObject
  • Versuchen Sie zu puffern und zu lesen vars.rootMessage.payload (Binary)

Aber ich konnte es mit keinem dieser Probleme lösen.

DataWeave-Ansatz Mein .dw-Skript

%dw 2.0
fun append
(item: Object, acc: Object = {
}) = acc ++ item

fun mapper(item: Object) = 
{
    Zshop: "01",
    Nodeid: "00000000",
    Nodename: item.key as Number as String {format: ""},
    Tlevel: "02",
    Parentid: "00000000",
    Childid: "00000000",
    Nextid: "00000000"
}
    
fun traverse(a: Array, level: Number) = 
    a map $ flatMap(value, index) -> value
    
output application/json
---
{
    test: payload.productGroups reduce (item, acc) -> append(mapper(item), acc)
}

Wo ich versucht habe, einige der Probleme zu lösen. mapper(item)sollte json-Objekte erstellen, mit denen ich die endgültige Ausgabe anhängen kann appender(item, acc). Die Rekursion wurde skizziert, ist aber noch nicht mein Hauptanliegen.

Dies ergibt dieses Ergebnis:

(original payload),
"Zshop": "01",
"Nodeid": "00000000",
"Nodename": "800",
"Tlevel": "02",
"Parentid": "00000000",
"Childid": "00000000",
"Nextid": "00000000",
"Zshop": "01",
"Nodeid": "00000000",
"Nodename": "110",
"Tlevel": "02",
"Parentid": "00000000",
"Childid": "00000000",
"Nextid": "00000000",
"Zshop": "01",
"Nodeid": "00000000",
"Nodename": "720",
"Tlevel": "02",
"Parentid": "00000000",
"Childid": "00000000",
"Nextid": "00000000",
"Zshop": "01",
"Nodeid": "00000000",
"Nodename": "710",
"Tlevel": "02",
"Parentid": "00000000",
"Childid": "00000000",
"Nextid": "00000000",
...

Wo ich mich frage, warum ich ein flaches Ergebnis ohne Objektstruktur erhalte.

Meine Fragen:

  • Java: Warum kann ich den String nicht umwandeln oder wie wird er richtig gemacht?
  • DataWeave: Gibt es eine einfache Lösung, die ich nicht sehe?
  • Warum ist es ein flaches Ergebnis und kein Objekt?
  • Sind die Verwendungen von reduceund flatMapfür diesen Zweck korrekt?

Jede Hilfe und / oder Rückmeldung ist willkommen.

Antworten

2 Alex Nov 19 2020 at 13:42

Java: Warum kann ich den String nicht umwandeln oder wie wird er richtig gemacht?

JSON ist kein String. Verwenden Sie write (payload, 'application / json'), um String zu haben.

DataWeave: Gibt es eine einfache Lösung, die ich nicht sehe?

Übergeben Sie einfach das Objekt, es ist Map in Java. Da es sich um einen Baum handelt, ist jeder Zweig eine andere Karte in dieser Karte.

Warum ist es ein flaches Ergebnis und kein Objekt?

Es ist IMMER Objekt. Es gibt keine anderen Dinge in der Java-Welt.

Sind die Verwendungen der Funktionen "Reduzieren" und "FlatMap" für diesen Zweck korrekt?

MapObject und Rekursion sollten ein guter Ansatz sein.

Peter Nov 27 2020 at 14:02

Basierend auf der Hilfe, die ich erhalten habe, konnte ich diese Lösung entwickeln:

public class Tree implements Serializable {
    private int id = 0;
    private List<Node> nodes = new ArrayList<Node>();
    
    public Tree() {
        nodes.add(new Node("01", "00000001", "HOME", "0", "00000000", "00000002", "00000000"));
    }
    
    public void enter(String jsonString, Integer level) {
        JSONObject json = new JSONObject(jsonString);
        traverse(json, level);
    }
    
    public void traverse(JSONObject json, int level) {
        visit(json, level);
        
        JSONArray arr = json.getJSONArray("childProductGroups");
        if(arr != null) {
            for(int i = 0; i < arr.length(); i++) {
                traverse(arr.getJSONObject(i), level + 1);
            }
        }       
    }
    
    private void visit(JSONObject object, int level) {
        id++;
        nodes.add(new Node("01", String.valueOf(id), object.getString("name_de"), String.valueOf(level), "", "", ""));
    }
    
    public Node[] getNodes() {
        assignParentIds();
        assignNextIds();
        assignChildIds();
            
        Node[] nodeArr = new Node[nodes.size()];
        for(int i = 0; i < nodes.size(); i++) {
            nodeArr[i] = nodes.get(i);
        }
        return nodeArr;
    }
    
    private void assignParentIds() {
        Map<String, Node> lastNodesWithHigherLevel = new HashMap<String, Node>();
        for(Node node : nodes) {
            Node higher = lastNodesWithHigherLevel.get(String.valueOf(Integer.valueOf(node.tlevel) - 1));
            if(higher != null) {
                node.parentid = higher.nodeid;
            }   
            lastNodesWithHigherLevel.put(node.tlevel, node);
        }
    }
    
    private void assignNextIds() {
        Map<String, Node> lastNodeOnSameLevel = new HashMap<String, Node>();
        for(Node node : nodes) {
            Node last = lastNodeOnSameLevel.get(node.tlevel);
            if(last != null) {
                if(last.parentid.equals(node.parentid)) {
                    // If the last and this node have the same parent
                    last.nextid = node.nodeid;
                }
            }   
            lastNodeOnSameLevel.put(node.tlevel, node);
        }
    }

    private void assignChildIds() {
        Iterator<Node> parentIterator = nodes.iterator();
        Iterator<Node> childIterator = nodes.iterator();
        
        // Init child iterator one further
        if(childIterator.hasNext()) {
            childIterator.next();
        }
        
        do {
            Node parent = parentIterator.next();
            Node child = childIterator.next();
            // If level of parent is higher (<) set the value
            if(Integer.valueOf(parent.tlevel) < Integer.valueOf(child.tlevel)) {
                parent.childid = child.nodeid;
            }
        } while (childIterator.hasNext());
        // Because childIterator will first be done, but parentIterator might need one more reference, we need do while
    }
    
    private static class Node implements Serializable {
        public String zshop, nodename, parentid, childid, nextid, nodeid, tlevel;
        
        public Node(String zshop, String nodeid, String nodename, String tlevel, String parentid, String childid, String nextid) {
            this.zshop = zshop;
            this.nodeid = nodeid;
            this.nodename = nodename;
            this.tlevel = tlevel;
            this.parentid = parentid;
            this.childid = childid;
            this.nextid = nextid;
        }
        
        @Override
        public String toString() {
            return "{zshop:" + zshop + ", nodename:" + nodename + ", parentid:" + parentid + ", childid:" + childid
                    + ", nextid:" + nextid + ", nodeid:" + nodeid + ", tlevel:" + tlevel + "}";
        }
    }
}

Und die Flow-Konfiguration:

<?xml version="1.0" encoding="UTF-8"?>

<mule xmlns:java="http://www.mulesoft.org/schema/mule/java" xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core"
    xmlns:file="http://www.mulesoft.org/schema/mule/file"
    xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/file http://www.mulesoft.org/schema/mule/file/current/mule-file.xsd
http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd
http://www.mulesoft.org/schema/mule/java http://www.mulesoft.org/schema/mule/java/current/mule-java.xsd">
    <http:listener-config name="HTTP_Listener_config" doc:name="HTTP Listener config" doc:id="e31dc867-3f9f-457b-8013-cd50a74c0af1" >
        <http:listener-connection host="0.0.0.0" port="8081" />
    </http:listener-config>
    <file:config name="File_Config" doc:name="File Config" doc:id="3279487e-61ca-4845-89a2-5332e7c02638" />
    <flow name="java-flow" doc:id="3c036218-560d-44f0-aef5-0c7f0ef4d776" >
        <http:listener doc:name="Listener" doc:id="265a1691-4f9b-40e2-a280-592daf719002" config-ref="HTTP_Listener_config" path="java">
            <http:response >
                <http:body ><![CDATA[#[output application/json ---
payload]]]></http:body>
            </http:response>
        </http:listener>
        <file:read doc:name="Copy_of_Read" doc:id="9f8b3c6f-f674-45f7-80fe-756f64602b30" config-ref="File_Config" path="response.json" />
        <set-variable doc:name="Set Variable" doc:id="ecff267b-3b73-45d1-924e-227a01a99e4e" variableName="Tree" value="#[null]" />
        <java:new doc:name="New Tree" doc:id="764d60bf-da41-4f17-8e13-c8bf31f141dc" class="valion.Tree" constructor="Tree()" target="Tree" />
        <foreach doc:name="For Each" doc:id="e612fd8b-636e-41c2-b603-e6861514306b" collection="#[payload.productGroups]">
            <java:invoke doc:name="Traverse" doc:id="96da4743-00d3-4970-a9e5-712877bcf2a9" class="valion.Tree" instance="#[vars.Tree]" method="enter(java.lang.String,java.lang.Integer)">
                <java:args><![CDATA[#[%dw 2.0
output application/java
---
{
    jsonString: write( payload,'application/json'),
    level: 1
}]]]></java:args>
            </java:invoke>
        </foreach>
        <java:invoke doc:name="Get Nodes" doc:id="999d5f4c-80b4-4793-9188-e17725ad030b" instance="#[vars.Tree]" class="valion.Tree" method="getNodes()">
        </java:invoke>
        <ee:transform doc:name="Transform Message" doc:id="b078bef3-45aa-4821-b7d1-720c2c7d0580">
            <ee:message>
                <ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
    hierarchieSet: payload map ( item , index ) -> {
        zshop: item.zshop,
        nodeid: item.nodeid,
        nodename: item.nodename,
        tlevel: item.tlevel,
        parentid: item.parentid,
        childid: item.childid,
        nextid: item.nextid
    }
}]]></ee:set-payload>
            </ee:message>
        </ee:transform>
    </flow>
</mule>