在 JSON 转换为 CSV 期间保持 JSON 键的顺序

Posted

技术标签:

【中文标题】在 JSON 转换为 CSV 期间保持 JSON 键的顺序【英文标题】:Keep the order of the JSON keys during JSON conversion to CSV 【发布时间】:2011-05-29 18:39:15 【问题描述】:

我正在使用此处提供的 JSON 库http://www.json.org/java/index.html 将我必须的 json 字符串转换为 CSV。 但我遇到的问题是,转换后键的顺序丢失了。

这是转换代码:

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

这是“someString”的内容:


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

这是结果:

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

虽然我希望保持键的顺序:

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

有什么方法可以让我使用这个库获得这个结果?如果没有,是否有任何其他库可以提供保持结果中键顺序的功能?

【问题讨论】:

字典未排序。我什至认为 JSON 不能保证顺序。 感谢您的信息。但是我别无选择,只能在我的应用程序中使用 JSON,并且我的应用程序需要保持键的顺序:( 就我而言,问题不在于缺少订单,而在于它是不确定的。有时我在密钥bar 之前得到密钥foo,有时在foo 之前得到bar。这使得编写测试变得困难。 我也遇到过这个需求,不过是为了实时测试时的快速日志对比。对于高吞吐量应用程序,我需要实时比较新生成的日志和以前生成的日志。还有其他方法可以做到这一点,但我希望日志采用 JSON 格式。但是,为了最大限度地减少 CPU 使用,我正在编写自己的直接到字符串的 JSON 编写器。我根本不需要内部结构,我可以维护关键顺序,以便对日志进行快速字符串比较。有充分的理由需要可预测的顺序。 编写您自己的代码以将 JSON 转换为特定排序的 CSV 文件,这样您就可以尊重这两种格式应该是什么。 【参考方案1】:

有(hacky)方法可以做到这一点......但你不应该这样做。

在 JSON 中,对象是这样定义的:

对象是一组无序的名称/值对。

见http://json.org。

JSON 的大多数实现都不会努力保留对象的名称/值对的顺序,因为它(根据定义)并不重要。

如果你想保留订单,你需要重新定义你的数据结构;例如


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

或更简单地说:


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

跟进

感谢您的信息,但我别无选择,只能在我的应用程序中使用 JSON,并且我的应用程序需要保持键的顺序,无论 JSON 对象的定义如何...我不允许更改格式JSON 文件也是如此...

您需要与设计该文件结构的人进行一次艰苦的对话,并且不会让您更改它。这是/他们完全错了。你需要说服他们。

如果他们真的不会让你改变它:

您应该坚持称它为 JSON ...'因为它不是。 您应该指出,您将不得不专门编写/修改代码来处理这种“非 JSON”格式……除非您能找到一些保留顺序的 JSON 实现。如果他们是付费客户,请确保他们为您必须做的这些额外工作付费。 您应该指出,如果其他工具需要使用“非 JSON”,那将是有问题的。确实,这个问题会反复出现...

这种事情真的很糟糕。一方面,您的软件将违反旨在促进互操作性的完善/长期规范。另一方面,设计这种蹩脚(不是 JSON!)文件格式的傻瓜可能正在淘汰其他人的系统等,因为系统无法处理他们的废话。

更新

同样值得一读JSON RFC (RFC 7159) 关于这个主题的说法。以下是部分摘录:

自 RFC 4627 发布以来的几年中,JSON 发现非常 用途广泛。这种经验揭示了某些模式,其中, 虽然其规范允许,但已导致互操作性 问题。

javascript Object Notation (JSON) 是一种文本格式,用于 结构化数据的序列化。 ...

JSON 可以表示四种基本类型(字符串、数字、布尔值、 和 null)和两种结构化类型(对象和数组)。

对象是零个或多个名称/值的无序集合 对,其中名称是字符串,值是字符串、数字、 布尔值、空值、对象或数组。

观察到 JSON 解析库在是否或 不是他们使对象成员的顺序对调用可见 软件。行为不依赖于成员的实现 订购将是可互操作的,因为它们不会 受到这些差异的影响。

【讨论】:

@YogeshSomani - “解决方案”是获取适当的 JSON 库,并“破解”它以保留密钥顺序。有关示例,请参见 gary 的答案。但是您不应该期望标准 JSON 库会这样做,因为鼓励像这样滥用 JSON 规范是一个坏主意。真正的解决方案是修复您的应用程序以正确使用 JSON。 最纯粹和绝对的感觉很棒,但我们不要自欺欺人,在现实世界的场景中,JSON 文件需要保留其定义值的顺序。以任何顺序传递和接受它们是一种基本的 json 方法,但我认为这与能够获取 json 文档并平等地序列化/反序列化的能力不同。一个常见的用例是必须获取一个 json 文档并使用它来形成另一个需要保留顺序的标准 WDDX、HTML 等文档 @AndrewNorman - 如果你所说的“需要”是真的,那么规范应该并且将会改变。现实情况是,在 JSON 中以其他方式表示排序很容易。 JSON 只要求信息不按键的顺序编码;否则它不是 JSON。序列化的一致性是一个不同的问题。在单个应用程序中,通常可以通过使用通用 JSON 库来保证一致性,但也有例外,例如当单个解决方案跨越多个平台或框架时。在这些情况下,您要么必须找到一种方法来处理不一致(引入低效率),要么您必须找到一种方法来确保一致性。两种方法都有效。 “JSON 只要求信息不按键的顺序编码;否则它不是 JSON。”。我认为你写错了。正确的说法是“JSON 不需要按照键的顺序对信息进行编码”。这两种说法有很大区别....【参考方案2】:

解决了。

我使用https://code.google.com/p/json-simple/ 的 JSON.simple 库读取 JSON 字符串以保持键的顺序,并使用http://sourceforge.net/projects/javacsv/ 的 JavaCSV 库转换为 CSV 格式。

【讨论】:

我很惊讶这能奏效。根据我对 JSONObject 类 (code.google.com/p/json-simple/source/browse/trunk/src/main/java/…) 的代码和 cmets 的阅读,它没有做任何事情来保留键的顺序。 这里我没有具体说明完整的解决方案,但它的要点是json-simple提供了一个工厂,您可以通过它指定用于存储json对象的数据结构。只需指定使用 LinkedHashMap。 @Hery,我面临同样的问题,但我无法解决。我使用org.jsonList<LinkedHahMap> 转换为JSONArray 以创建CSV,创建CSV 但与我的List 的顺序不同。我尝试使用您在此处提供的两个库,但找不到转换为 CSV 的方法。请您提供更多详细信息。 你能解释一下你到底做了什么吗?【参考方案3】:

维持秩序很简单。我在维护从 DB 层到 UI 层的顺序时遇到了同样的问题。

打开 JSONObject.java 文件。它在内部使用不维护顺序的 HashMap。

将其更改为 LinkedHashMap:

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

这对我有用。在 cmets 中告诉我。我建议 JSON 库本身应该有另一个 JSONObject 类来维护顺序,比如 JSONOrderdObject.java。我在选择名字方面很差。

【讨论】:

svn checkout json-simple.修改文件 org.json.simple.JSONObject,JOSNObject extends LinkedHashMap ... from .. HashMap..,修复导入,它对我有用。 v1.1.1. 这是一个很棒的单线解决方案,我从未想过要尝试。完美运行,我不必更改任何其他代码。 +1 很好的修复。你是英雄先生 没用,我添加了导入,按照您说的进行了更改,甚至尝试这样做:this.map = new LinkedHashMap<String, Object>(); 仍然不起作用。请帮忙? 哈哈真的吗?修改 JSON 库以满足您的需求?我可以想象这会在公司知识库中下降。 “做你的 mvn 全新安装,下载 jar,找到它,反编译它,然后用 LinkedHashMap 替换 HashMap”。这对多开发团队(不是)非常有用。【参考方案4】:

JSONObject.java 获取您通过的任何地图。它可能是LinkedHashMapTreeMap,只有当地图为空时才会使用hashmap

这里是 JSONObject.java 类的构造函数,它将检查地图。

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

所以在构建一个json对象之前构造LinkedHashMap然后像这样传递给构造函数,

LinkedHashMap<String, String> jsonOrderedMap = new LinkedHashMap<String, String>();

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));

因此无需更改 JSONObject.java 类。希望它可以帮助某人。

【讨论】:

好答案,适用于普通 java,但不适用于 android。我检查了android中的org.json.JSONObject,很遗憾android仍然使用内部创建的hashmap。它只将名称/值对从 paramMap 复制到 HashMap... :( 我订购了 JsonObject 但是当我尝试将该对象添加到 JsonArray 时,它给了我一个无序的对象。我需要将对象放入数组,我需要数组对象,然后将标头放入数组并发回。例如:一个对象; CUSTOMER_SECTOR_ID=611, CUSTOMER_NO=0000003204, CUSTOMER_NAME=MARMARİS - KARAS GIDA KARAS TÜKETİM MADDELERİ GIDA LOJ. 如果我将两个对象放入数组中,我会得到这个:["CUSTOMER_NAME":"SILA GIDA PAZARLAMA","CUSTOMER_NO":" 0000003122","CUSTOMER_SECTOR_ID":"611","CUSTOMER_NAME":"M":"0013114714","CUSTOMER_SECTOR_ID":"611"] 如您所见,这是错误的。我该如何解决它 @Deepak Nagaraj 。我已经尝试过您的解决方案,但没有成功,JSONObject 没有像我的LinkedHashMap 一样订购。你在使用org.jsonlib 吗? 这不是org.json 之一。它在内部定义了一个HashMap,无论你给它什么Map:@Deepak 你能告诉我们你使用的实现吗?跨度> 这不适用于org.json 库。当您构造一个新的JSONObject 对象时,将映射作为参数传递,它将始终转换为HashMap。构造函数首先创建一个新地图:this.map = new HashMap&lt;String, Object&gt;();,然后循环遍历你的地图,将每个元素复制到他的HashMap【参考方案5】:

Apache Wink 有OrderedJSONObject。它在解析字符串时保持顺序。

【讨论】:

【参考方案6】:

解决这类问题的一个更详细但广泛适用的解决方案是使用一对数据结构:一个包含排序的列表和一个包含关系的映射。

例如:


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

您迭代 itemOrder 列表,并使用它们来查找地图值。订单被保留,没有任何杂物。

这个方法我用过很多次了。

【讨论】:

如果您的地图不仅仅是简单的键值对,这将变得复杂......【参考方案7】:

偶然发现了同样的问题,我相信作者使用的最终解决方案是使用自定义ContainerFactory:

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<String,Object>)parser.parse(msgData, containerFactory);
     catch (ParseException e) 
        log.warn("Exception parsing JSON string ", msgData, e);
    
    return null;
  

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

【讨论】:

真的非常好实用的解决方案。给containerFactory写方法本地匿名内部类怎么样?【参考方案8】:

我知道这个问题已经解决了,很久以前就有人问过这个问题,但是当我正在处理一个类似的问题时,我想给出一个完全不同的方法:

对于数组,它说“数组是值的有序集合”。在http://www.json.org/ - 但是对象(“对象是一组无序的名称/值对。”)没有排序。

我想知道为什么该对象在数组中 - 这意味着不存在的顺序。


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

因此,一种解决方案是将键放入“真实”数组中,并将数据作为对象添加到每个键,如下所示:


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

因此,这是一种尝试重新思考原始建模及其意图的方法。但我还没有测试(我想知道)是否所有相关工具都会保留原始 JSON 数组的顺序。

【讨论】:

这不允许我查找密钥。 那你想要什么 - 哈希或订购的东西!?如果您想将两者结合起来,则创建两者并让其中一个结构索引另一个结构......否则这似乎更像是一个设计问题或混合兴趣。 这不是关于混合兴趣,这是一个相当合理的用例,在像 Java 这样的语言中由 LinkedHashMap 等结构处理,这是一个保留插入顺序的有序映射。那我想要什么,我希望既能以定义的顺序迭代键,又能快速查找键。我认为@Robotic Pants 的答案非常有效,尽管它有点骇人听闻。 没错,mickeymoon,我正要这么说!但是在 JSON 中这不存在,不是吗?然后你必须涉及语言/机制,它们确实为你提供了这样的东西。【参考方案9】:

最安全的方法可能是覆盖用于生成输出的 keys 方法:

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

【讨论】:

这并不能解决上述问题。它会导致对键进行排序......但问题要求保留插入顺序。【参考方案10】:

patchFor(回答@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<String, Object>();
+//      this.map = new HashMap<String, Object>();
+        // I want to keep order of the given data:
+        this.map = new LinkedHashMap<String, Object>();
     

     /**
@@ -243,7 +245,7 @@ public class JSONObject 
      * @throws JSONException
      */
     public JSONObject(Map<String, Object> map) 
-        this.map = new HashMap<String, Object>();
+        this.map = new LinkedHashMap<String, Object>();
         if (map != null) 
             Iterator<Entry<String, Object>> i = map.entrySet().iterator();
             while (i.hasNext()) 

【讨论】:

【参考方案11】:

测试了眨眼解决方案,并且工作正常:

@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());

【讨论】:

Maven 依赖:org.apache.winkwink-json4j1.4跨度> 与@cypressious ... 2 年前发布的答案相同。而且这个例子并没有增加任何实际价值。【参考方案12】:

您可以使用以下代码对 JSON Array 进行自定义的 ORDERED 序列化和反序列化(本示例假设您正在订购 Strings 但可以应用于所有类型):

序列化

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);

反序列化

JSONArray paramsJsonArray = json.optJSONArray("orderedArray");
if (null != paramsJsonArray)

    ArrayList<String> 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);
            
        
    

【讨论】:

【参考方案13】:

另一个使用反射的 hacky 解决方案:

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

【讨论】:

我没有得到你的答案 诀窍是在内部修改类以使用 LinkedHashMap 而不是默认的 HashMap,使 JSON 对象按照您放入的顺序具有数据,这不是直接排序的,但它适用于我因为解析的数据已经排序了。 看起来不错,但是如何在 map.set(json, new LinkedHashMap()); 之后解析 Json 字符串因为 Json String 是在构造函数中传递的,对吗?比如,新的 JSONObject(jsonString)。 另外,这也没用。没有“map”字段,有一个“nameValuePairs”字段已经是LinkedHashMap,还是不行。 您使用的是什么 JSONObject 实现?我正在使用 org.json 版本 20140107 并且必须保留顺序,这就是它对我的工作方式。【参考方案14】:

你的例子:


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

添加一个元素“itemorder”


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

此代码生成没有列标题行的所需输出:

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

【讨论】:

这删除了 ​​CSV 的标题,现在我有排序但没有标题的行【参考方案15】:

在现实世界中,应用程序几乎总是具有要序列化/反序列化到 JSON 的 java bean 或域。它已经提到 JSON 对象规范不保证顺序,并且对该行为的任何操作都不能证明该要求是合理的。 我在我的应用程序中遇到了同样的情况,我需要保留顺序只是为了便于阅读。我使用标准的杰克逊方式将我的 java bean 序列化为 JSON:

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

为了使 json 具有一组有序的元素,我只在用于转换的 Java bean 中使用 JSON 属性注释。下面是一个例子:

@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():

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

输出按Json属性顺序注解显示:


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

【讨论】:

【参考方案16】:

而不是使用 jsonObject 尝试使用 CsvSchema 更容易直接将对象转换为 csv

CsvSchema schema = csvMapper.schemaFor(MyClass.class).withHeader();
        csvMapper.writer(schema).writeValueAsString(myClassList);

它表示您的 pojo 中包含 @JsonPropertyOrder 的订单 ID

【讨论】:

【参考方案17】:

Underscore-java 在读取 json 时保持元素的顺序。

String json = "\n"
      + "    \"items\":\n"
      + "    [\n"
      + "        \n"
      + "            \"WR\":\"qwe\",\n"
      + "            \"QU\":\"asd\",\n"
      + "            \"QA\":\"end\",\n"
      + "            \"WO\":\"hasd\",\n"
      + "            \"NO\":\"qwer\"\n"
      + "        \n"
      + "    ]\n"
      + "";
System.out.println(U.fromJson(json));

// items=[WR=qwe, QU=asd, QA=end, WO=hasd, NO=qwer]

【讨论】:

【参考方案18】:

你可以使用

toString(JSONArray names, JSONArray ja)

它使用提供的名称列表从 JSONObjects 的 JSONArray 中生成逗号分隔的文本,其顺序与 json 数组相同。

参考:https://stleary.github.io/JSON-java/org/json/CDL.html#toString-org.json.JSONArray-org.json.JSONArray-

【讨论】:

以上是关于在 JSON 转换为 CSV 期间保持 JSON 键的顺序的主要内容,如果未能解决你的问题,请参考以下文章

在 Scala 中将 JSON 转换为 CSV?

csv转换obj

在 Java 中将 JSON 转换为 XLS/CSV [关闭]

使用 python 将 JSON 转换为 CSV

如何在python中将json转换为csv?

在 bash 中将 CSV 转换为 JSON