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


82

我正在使用此处http://www.json.org/java/index.html提供的JSON库将必须转换的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

有什么办法可以使用此库获得此结果吗?如果不是,是否还有其他库可以提供在结果中保留键顺序的功能?


4
字典未排序。我什至不认为JSON可以保证顺序。
Falmarri 2010年

2
谢谢(你的)信息。但是我别无选择,只能在我的应用程序中使用JSON,我的应用程序需要保持键的顺序:(
Hery 2010年

就我而言,问题不在于缺少命令,而是不确定的。有时候我在钥匙foo之前得到钥匙bar,有时bar在钥匙之前foo。这使得很难为其编写测试。
Sridhar Sarnobat,2015年

我也遇到了这种需求,但这是为了在实时测试期间进行快速日志比较。对于高吞吐量的应用程序,我需要实时将新生成的日志与以前生成的日志进行比较。还有其他方法可以执行此操作,但我希望日志采用JSON格式。但是,为了最大限度地减少CPU使用量,我正在编写自己的直接字符串JSON编写器。我根本不需要内部结构,并且可以维护键顺序以进行日志的快速字符串比较。有充分的理由想要可预测的顺序。
乔·拉普

编写自己的代码以将JSON转换为特定排序的CSV文件,这样您就可以尊重这两种格式。
马丁·谢弗

Answers:


95

有很多方法可以做到这一点……但是你不应该这样做。

在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实现。如果他们是付费客户,请确保他们为您要做的这项额外工作付费。
  • 您应该指出,如果其他工具需要使用“ not JSON”,这将是有问题的。确实,这个问题会反复发生...

这种事情真的很糟糕。一方面,您的软件将违反旨在促进互操作性的成熟/长期规范。另一方面,设计这种la脚(不是JSON!)文件格式的机智的人可能是从其他人的系统中渣etc而来,因为这些系统无法应付他们的废话。

更新

还值得一读JSON RFC(RFC 7159)关于此主题的内容。以下是一些摘录:

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

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

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

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

观察到JSON解析库在是否使调用软件可见对象成员的顺序方面存在差异。行为不取决于成员顺序的实现将可以互操作,因为它们不会受到这些差异的影响。


6
@Hery-给他们开帐单:-)
Stephen C

3
@YogeshSomani-“解决方案”是获取适当的JSON库,并对其进行“ hack”,以便保留密钥顺序。有关示例,请参见gary的答案。但是,您不应该期望标准的JSON库执行此操作,因为它是鼓励使用JSON规范的不良想法。真正的解决方案是修复您的应用程序以正确使用JSON。
斯蒂芬·C

10
做到最纯粹和绝对是个好主意,但我们不要自欺欺人,在现实世界中,JSON文件需要保留其定义值的顺序。以任何顺序传递和接受它们是json的基本方法,但我认为这与能够获取json文档并平等地进行序列化/反序列化的能力不同。这是一个常见的用例,必须获取一个json文档,并使用它形成另一个需要保留排序的标准WDDX,HTML等文档
Andrew Norman

3
@AndrewNorman-如果您所说的“需求”确实是真的,那么规范应该并且将会被更改。现实情况是,很容易以其他方式表示JSON中的排序。
斯蒂芬·C

2
JSON仅要求不按键顺序对信息进行编码。否则不是JSON。序列化的一致性是一个不同的问题。在单个应用程序中,通常可以使用公共JSON库保证一致性,但是也有例外,例如单个解决方案跨越多个平台或框架时。在这些情况下,您要么必须找到一种方法来处理不一致(引入效率低下),要么就必须找到一种确保一致性的方法。两种方法都是有效的。
Joe Lapp

47

维持秩序非常简单。我在维护从数据库层到用户界面层的顺序时遇到了同样的问题。

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

将其更改为LinkedHashMap:

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

这对我有用。在评论中让我知道。我建议JSON库本身应该有另一个维护顺序的JSONObject类,例如JSONOrderdObject.java。我在选择名字方面很穷。


2
svn checkout json-简单。修改文件org.json.simple.JSONObject,<code> JOSNObject从<code> .. HashMap .. </ code>扩展LinkedHashMap ... </ code>,修复导入,它对我有用。v1.1.1。
Marku 2013年

2
这是一个很棒的单线解决方案,我从未想过要尝试。完美运行,并且我不必更改任何其他代码。+1
DaaaahWhoosh 2015年

伟大的修复。您是英雄先生
格雷戈里·诺瓦科夫斯基

没用,我添加了导入,按照您说的进行了更改,甚至尝试这样做:this.map = new LinkedHashMap<String, Object>();仍然不起作用。请帮忙 ?
塔雷克

4
哈哈真的?修改JSON库以满足您的需求?我可以想像这在公司知识库中正在下降。“进行mvn全新安装,下载jar,找到它,反编译,然后用LinkedHashMap替换HashMap”。这将对多开发人员团队(而非)非常有用。
克里斯·内夫

22

JSONObject.java拍摄您通过的所有地图。它可以是LinkedHashMap或,TreeMap并且hashmap仅在map为null时才需要。

这是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类。希望它能帮助到别人。


3
好的答案,适用于普通的Java,但不适用于android。我检查了android中的org.json.JSONObject,可惜的是android仍然使用内部创建的hashmap。它仅将名称/值对从paramMap复制到HashMap ... :(
正宗白布鞋

我订购了JsonObject,但是当我尝试将该对象添加到JsonArray时,它给了我无序的对象。我需要将对象放入array,我需要array的对象,然后将标头放入array并发回。例如:一个物体;{CUSTOMER_SECTOR_ID = 611,CUSTOMER_NO = 0000003204,CUSTOMER_NAME =MARMARİS-KARAS GIDA KARASTÜKETİMMADDELERİGIDA LOJ。}如果我将两个对象放在数组中,则会得到此结果:[{“ CUSTOMER_NAME”:“ SILA GIDA PAZARLAMA” 0000003122“,” CUSTOMER_SECTOR_ID“:” 611“},{” CUSTOMER_NAME“:” M“:” 0013114714“,” CUSTOMER_SECTOR_ID“:” 611“}]如您所见,这是错误的。我该如何解决
SahinYanlık2014年

2
@Deepak Nagaraj。我已经尝试过您的解决方案,但没有成功,JSONObject未按我的要求订购LinkedHashMap。你是org.jsonlib吗?
伊斯梅尔·森

1
这不是org.json一个。.它在HashMap内部定义了一个对象,无论Map您给它什么:@Deepak您能否指出我们所使用的实现?
坎帕2015年

8
这不适用于org.json库。构造新JSONObject对象时,将地图作为参数传递,它将始终转换为HashMap。构造函数首先创建一个新的map:,this.map = new HashMap<String, Object>();然后在您的地图上循环,将每个元素复制到他的HashMap
itachi 2015年

13

对于此类问题,更冗长但可广泛应用的解决方案是使用一对数据结构:一个包含顺序的列表,以及一个包含关系的映射。

例如:

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

您可以迭代itemOrder列表,并使用它们来查找地图值。订单被保留,没有纠缠。

我已经多次使用这种方法。


如果您的地图不只是简单的键值对,则情况将变得复杂...
zfj3ub94rf576hc4eegm '18 -10-3

10

另一个骇人听闻的解决方案使用体现:

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

1
我没有收到您的答案
乌斯曼·汗

1
诀窍是在内部修改该类以使用LinkedHashMap而不是默认的HashMap,使JSON对象按您放入的顺序存储数据,这不是直接排序的,但对我有用,因为解析的数据已经排序。
fabe

看起来不错,但是如何解析Json String AFTER map.set(json,new LinkedHashMap <>()); 因为Json String是在构造函数中传递的?例如,新的JSONObject(jsonString)。
M. Usman Khan

另外,这没有用。没有字段“地图”,没有字段“ nameValuePairs”,它已经是LinckedHashMap,但仍然无法正常工作。
M. Usman Khan

1
您正在使用JSONObject的哪种实现?我正在使用org.json版本20140107,必须保留顺序,这就是它对我的工作方式。
fabe


4

偶然发现了同一问题,我相信作者使用的最终解决方案是使用自定义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编写方法本地匿名内部类?
Shailesh Saxena

3

解决了。

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


我对此工作感到惊讶。根据我对JSONObject类的代码和注释的阅读(code.google.com/p/json-simple/source/browse/trunk/src/main/java/…),它不会做任何保留的工作键的顺序。
Stephen C

1
我没有在此处指定完整的解决方案,但要点是json-simple提供了一个工厂,您可以使用该工厂指定用于存储json对象的数据结构。只需指定使用LinkedHashMap。
Hery 2012年

@Hery,我面临着同样的问题,但我无法解决。我通常使用org.json转换List<LinkedHahMap>JSONArray,以创建CSVCSV但创建顺序与我的顺序不同List 。我尝试使用您在此处提供的两个库,但是找不到转换为CSV.lib的方法。请提供更多详细信息。
伊斯梅尔·森

2

我知道已经解决了,很久以前就问过这个问题,但是由于我正在处理类似的问题,因此我想提供一种完全不同的方法:

对于数组,它说“数组是值的有序集合”。在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数组的顺序。


1
这不能让我查找密钥。
mickeymoon 2015年

那你想要什么-哈希或有序的东西!?如果您想将两者结合起来,则要同时创建两者,并使其中一个结构索引另一个。
cslotty 2015年

1
这与混合兴趣无关,这是一个很合理的用例,在Java之类的语言中,用例是由LinkedHashMap这样的结构处理的,LinkedHashMap是一种保留插入顺序的有序映射。然后我想要什么,我希望两者都能够以定义的顺序迭代键,并且还可以快速查找键。我认为@Robotic Pants的答案将非常有效,尽管它有点骇人听闻。
mickeymoon 2015年

没错,mickeymoon,我要说的是!但是在JSON中不存在,不是吗?然后,您将不得不涉及确实为您提供这种东西的语言/机制。
cslotty 2015年

1

在现实世界中,应用程序几乎总是会具有要从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
}

0

最安全的方法可能是重写用于生成输出的键方法:

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

1
这不能解决所述问题。它导致对键进行排序...但是问题是要求保留插入顺序。
斯蒂芬·C

0

patchFor(answer @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()) {

0

您可以使用以下代码对JSON数组进行自定义ORDERED序列化和反序列化(此示例假定您正在订购字符串,但可以应用于所有类型):

序列化

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

0

你的例子:

{
    "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的标头,现在我对行进行了排序但没有标头
PedroJoaquín19年

0

与其使用jsonObject,不如尝试使用CsvSchema,将其更轻松地直接将对象转换为csv

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

它mentains订单ID您的POJO具有@JsonPropertyOrder在它


0

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}]}

-1

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

@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依赖关系:<dependency> <groupId> org.apache.wink </ groupId> <artifactId> wink-json4j </ artifactId> <version> 1.4 </ version> </ dependency>
jgpelaez

与@cypressious ...的答案相同... 2年前发布。该示例没有任何实际价值。
斯蒂芬·C
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.