Mantieni l’ordine delle chiavi JSON durante la conversione JSON in CSV

Sto usando la libreria JSON fornita qui http://www.json.org/java/index.html per convertire una stringa json che ho in CSV. Ma il problema che ho è, l’ordine delle chiavi è perso dopo la conversione.

Questo è il codice di conversione:

JSONObject jo = new JSONObject(someString); JSONArray ja = jo.getJSONArray("items"); String s = CDL.toString(ja); System.out.println(s); 

Questo è il contenuto di “someString”:

 { "items": [ { "WR":"qwe", "QU":"asd", "QA":"end", "WO":"hasd", "NO":"qwer" }, ] } 

Questo è il risultato:

 WO,QU,WR,QA,NO hasd,asd,qwe,end,qwer 

Mentre quello che mi aspetto è mantenere l’ordine delle chiavi:

 WR,QU,QA,WO,NO qwe,asd,end,hasd,qwer 

C’è un modo per ottenere questo risultato usando questa libreria? In caso contrario, c’è qualche altra libreria che fornirà la capacità di mantenere l’ordine delle chiavi nel risultato?

Ci sono (hacky) modi per farlo … ma non dovresti.

In JSON, un object è definito così:

Un object è un insieme non ordinato di coppie nome / valore.

Vedi http://json.org .

La maggior parte delle implementazioni di JSON non si sforzano di preservare l’ordine delle coppie nome / valore di un object, poiché è (per definizione) non significativo.

Se vuoi che l’ordine sia preservato, devi ridefinire la struttura dei dati; per esempio

 { "items": [ [ {"WR":"qwe"}, {"QU":"asd"}, {"QA":"end"}, {"WO":"hasd"}, {"NO":"qwer"} ], ] } 

o più semplicemente:

 { "items": [ {"WR":"qwe"}, {"QU":"asd"}, {"QA":"end"}, {"WO":"hasd"}, {"NO":"qwer"} ] } 

AZIONE SUPPLEMENTARE

Grazie per le informazioni, ma non ho altra scelta che usare JSON nella mia applicazione e la mia applicazione deve mantenere l’ordine delle chiavi indipendentemente dalla definizione dell’object JSON … Non mi è permesso cambiare il formato del file JSON anche…

Devi avere una conversazione dura con chi ha progettato quella struttura di file e non ti permetterà di cambiarla. È / sono semplicemente sbagliati. Devi convincerli.

Se davvero non ti permettono di cambiarlo:

  • Dovresti insistere per non chiamarlo JSON … ‘perché non lo è.
  • Dovresti sottolineare che dovrai scrivere / modificare il codice appositamente per gestire questo formato “non JSON” … a meno che non trovi qualche implementazione JSON che preservi l’ordine. Se sono clienti paganti, assicurati che paghino per questo lavoro extra che devi fare.
  • Dovresti sottolineare che se il “non JSON” deve essere usato da qualche altro strumento, sarà problematico. In effetti, questo problema si verificherà più e più volte …

Questo genere di cose è davvero pessimo. Da un lato, il tuo software violerà una specifica consolidata / di vecchia data progettata per promuovere l’interoperabilità. D’altra parte, il pidocchio che ha progettato questo formato di file storpio (non JSON!) Sta probabilmente scoraggiando i sistemi di altre persone, perché i sistemi non riescono a far fronte alle loro assurdità.

AGGIORNARE

Vale anche la pena leggere ciò che la JSON RFC (RFC 7159) dice su questo argomento. Ecco alcuni estratti:

Negli anni trascorsi dalla pubblicazione di RFC 4627, JSON ha trovato un uso molto ampio. Questa esperienza ha rivelato alcuni modelli che, pur consentiti dalle sue specifiche, hanno causato problemi di interoperabilità.

JavaScript Object Notation (JSON) è un formato di testo per la serializzazione di dati strutturati. …

JSON può rappresentare quattro tipi primitivi (stringhe, numeri, booleani e null) e due tipi strutturati (oggetti e matrici).

Un object è un insieme non ordinato di zero o più coppie nome / valore, dove un nome è una stringa e un valore è una stringa, un numero, un booleano, un null, un object o un array.

È stato osservato che le librerie di analisi JSON differiscono sul fatto se rendono l’ordine dei membri degli oggetti visibile o meno al software di chiamata. Le implementazioni il cui comportamento non dipende dall’ordinamento dei membri saranno interoperabili nel senso che non saranno influenzate da queste differenze.

È abbastanza semplice mantenere l’ordine. Ho avuto lo stesso problema con il mantenimento dell’ordine dal livello DB al livello dell’interfaccia utente.

Aprire il file JSONObject.java. Utilizza internamente HashMap che non mantiene l’ordine.

Passalo a LinkedHashMap:

  //this.map = new HashMap(); this.map = new LinkedHashMap(); 

Questo ha funzionato per me. Fatemi sapere nei commenti. Suggerisco che la libreria JSON stessa dovrebbe avere un’altra class JSONObject che mantiene l’ordine, come JSONOrderdObject.java. Sono molto povero nella scelta dei nomi.

JSONObject.java accetta qualsiasi mappa tu passi. Potrebbe essere LinkedHashMap o TreeMap e prenderà hashmap solo quando la mappa è nullo.

Ecco il costruttore della class JSONObject.java che eseguirà il controllo della mappa.

  public JSONObject(Map paramMap) { this.map = (paramMap == null ? new HashMap() : paramMap); } 

Quindi, prima di build un object json build LinkedHashMap e poi passarlo al costruttore in questo modo,

 LinkedHashMap jsonOrderedMap = new LinkedHashMap(); jsonOrderedMap.put("1","red"); jsonOrderedMap.put("2","blue"); jsonOrderedMap.put("3","green"); JSONObject orderedJson = new JSONObject(jsonOrderedMap); JSONArray jsonArray = new JSONArray(Arrays.asList(orderedJson)); System.out.println("Ordered JSON Fianl CSV :: "+CDL.toString(jsonArray)); 

Quindi non c’è bisogno di cambiare la class JSONObject.java . Spero che aiuti qualcuno.

Una soluzione più verbosa, ma ampiamente applicabile a questo tipo di problema è l’uso di una coppia di strutture dati: un elenco per contenere l’ordine e una mappa per contenere le relazioni.

Per esempio:

 { "items": [ { "WR":"qwe", "QU":"asd", "QA":"end", "WO":"hasd", "NO":"qwer" }, ], "itemOrder": ["WR", "QU", "QA", "WO", "NO"] } 

Si esegue l’iterazione dell’elenco ItemOrder e si utilizzano quelli per cercare i valori della mappa. L’ordine è conservato, senza problemi.

Ho usato questo metodo molte volte.

Un’altra soluzione hacky che utilizza riflette:

 JSONObject json = new JSONObject(); Field map = json.getClass().getDeclaredField("map"); map.setAccessible(true);//because the field is private final... map.set(json, new LinkedHashMap<>()); map.setAccessible(false);//return flag 

Apache Wink ha ordinato JSONObject . Mantiene l’ordine mentre analizza la stringa.

Risolto.

Ho usato la libreria JSON.simple da qui https://code.google.com/p/json-simple/ per leggere la stringa JSON per mantenere l’ordine delle chiavi e utilizzare la libreria JavaCSV da qui http://sourceforge.net/ projects / javacsv / per convertire in formato CSV.

Sono incappato nello stesso problema, credo che la soluzione finale utilizzata dall’autore consistesse nell’usare una ContainerFactory personalizzata:

 public static Values parseJSONToMap(String msgData) { JSONParser parser = new JSONParser(); ContainerFactory containerFactory = new ContainerFactory(){ @Override public Map createObjectContainer() { return new LinkedHashMap(); } @Override public List creatArrayContainer() { return null; } }; try { return (Map)parser.parse(msgData, containerFactory); } catch (ParseException e) { log.warn("Exception parsing JSON string {}", msgData, e); } return null; } 

vedi http://juliusdavies.ca/json-simple-1.1.1-javadocs/org/json/simple/parser/JSONParser.html#parse(java.io.Reader,org.json.simple.parser.ContainerFactory)

So che questo è stato risolto e la domanda è stata posta molto tempo fa, ma poiché ho a che fare con un problema simile, vorrei dare un approccio completamente diverso a questo:

Per gli array dice “Un array è una collezione di valori ordinati”. a http://www.json.org/ – ma gli oggetti (“Un object è un insieme non ordinato di coppie nome / valore.”) non sono ordinati.

Mi chiedo perché quell’object sia in un array – questo implica un ordine che non c’è.

 { "items": [ { "WR":"qwe", "QU":"asd", "QA":"end", "WO":"hasd", "NO":"qwer" }, ] } 

Quindi una soluzione sarebbe mettere le chiavi in ​​una matrice “reale” e aggiungere i dati come oggetti a ciascuna chiave come questa:

 { "items": [ {"WR": {"data": "qwe"}}, {"QU": {"data": "asd"}}, {"QA": {"data": "end"}}, {"WO": {"data": "hasd"}}, {"NO": {"data": "qwer"}} ] } 

Quindi questo è un approccio che cerca di ripensare la modellazione originale e il suo intento. Ma non ho provato (e mi chiedo) se tutti gli strumenti coinvolti preservassero l’ordine di quell’array JSON originale.

Il modo più sicuro è probabilmente il metodo override delle chiavi che viene utilizzato per generare output:

 new JSONObject(){ @Override public Iterator keys(){ TreeSet sortedKeys = new TreeSet(); Iterator keys = super.keys(); while(keys.hasNext()){ sortedKeys.add(keys.next()); } return sortedKeys.iterator(); } }; 

patchFor (risposta @ gary):

 $ git diff JSONObject.java diff --git a/JSONObject.java b/JSONObject.java index e28c9cd..e12b7a0 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -32,7 +32,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Enumeration; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; @@ -152,7 +152,9 @@ public class JSONObject { * Construct an empty JSONObject. */ public JSONObject() { - this.map = new HashMap(); +// this.map = new HashMap(); + // I want to keep order of the given data: + this.map = new LinkedHashMap(); } /** @@ -243,7 +245,7 @@ public class JSONObject { * @throws JSONException */ public JSONObject(Map map) { - this.map = new HashMap(); + this.map = new LinkedHashMap(); if (map != null) { Iterator> i = map.entrySet().iterator(); while (i.hasNext()) { 

È ansible utilizzare il seguente codice per eseguire la serializzazione ORDATA personalizzata e la deserializzazione dell’array JSON (in questo esempio si presuppone che si stiano ordinando stringhe ma che possano essere applicate a tutti i tipi):

serializzazione

 JSONArray params = new JSONArray(); int paramIndex = 0; for (String currParam : mParams) { JSONObject paramObject = new JSONObject(); paramObject.put("index", paramIndex); paramObject.put("value", currParam); params.put(paramObject); ++paramIndex; } json.put("orderedArray", params); 

deserializzazione

 JSONArray paramsJsonArray = json.optJSONArray("orderedArray"); if (null != paramsJsonArray) { ArrayList paramsArr = new ArrayList<>(); for (int i = 0; i < paramsJsonArray.length(); i++) { JSONObject param = paramsJsonArray.optJSONObject(i); if (null != param) { int paramIndex = param.optInt("index", -1); String paramValue = param.optString("value", null); if (paramIndex > -1 && null != paramValue) { paramsArr.add(paramIndex, paramValue); } } } } 

Il tuo esempio:

 { "items": [ { "WR":"qwe", "QU":"asd", "QA":"end", "WO":"hasd", "NO":"qwer" }, ... ] } 

aggiungi un elemento “itemorder”

 { "items": [ { "WR":"qwe", "QU":"asd", "QA":"end", "WO":"hasd", "NO":"qwer" }, ... ], "itemorder":["WR","QU","QA","WO","NO"] } 

Questo codice genera l’output desiderato senza la riga del titolo della colonna:

 JSONObject output = new JSONObject(json); JSONArray docs = output.getJSONArray("data"); JSONArray names = output.getJSONArray("itemOrder"); String csv = CDL.toString(names,docs); 

Nel mondo reale, un’applicazione ha quasi sempre un java bean o un dominio che deve essere serializzato / de-serializzato su / da JSON. Si è già detto che la specifica dell’object JSON non garantisce l’ordine e qualsiasi manipolazione a tale comportamento non giustifica il requisito. Ho avuto lo stesso scenario nella mia applicazione in cui avevo bisogno di mantenere l’ordine solo per il sacco di scopo di leggibilità. Ho usato il modo standard di jackson per serializzare il mio java bean su JSON:

 Object object = getObject(); //the source java bean that needs conversion String jsonString = new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(object); 

Al fine di rendere il JSON con un insieme ordinato di elementi, uso solo annotazioni di proprietà JSON nel bean Java che ho usato per la conversione. Un esempio qui sotto:

 @JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({"name","phone","city","id"}) public class SampleBean implements Serializable { private int id; private String name: private String city; private String phone; //...standard getters and setters } 

getObject () usato sopra:

 public SampleBean getObject(){ SampleBean bean = new SampleBean(); bean.setId("100"); bean.setName("SomeName"); bean.setCity("SomeCity"); bean.setPhone("1234567890"); return bean; } 

L’output viene visualizzato secondo l’annotazione dell’ordine proprietà Json:

 { name: "SomeName", phone: "1234567890", city: "SomeCity", id: 100 } 

Testato la soluzione wink e funzionante:

 @Test public void testJSONObject() { JSONObject jsonObject = new JSONObject(); jsonObject.put("bbb", "xxx"); jsonObject.put("ccc", "xxx"); jsonObject.put("aaa", "xxx"); jsonObject.put("xxx", "xxx"); System.out.println(jsonObject.toString()); assertTrue(jsonObject.toString().startsWith("{\"xxx\":")); } @Test public void testWinkJSONObject() throws JSONException { org.apache.wink.json4j.JSONObject jsonObject = new OrderedJSONObject(); jsonObject.put("bbb", "xxx"); jsonObject.put("ccc", "xxx"); jsonObject.put("aaa", "xxx"); jsonObject.put("xxx", "xxx"); assertEquals("{\"bbb\":\"xxx\",\"ccc\":\"xxx\",\"aaa\":\"xxx\",\"xxx\":\"xxx\"}", jsonObject.toString()); }