JSON顺序混乱

87

我在制作页面时遇到了一个问题,希望以我想要的顺序打印出JSONObject。在我的代码中,我输入了以下内容:

JSONObject myObject = new JSONObject();
myObject.put("userid", "User 1");
myObject.put("amount", "24.23");
myObject.put("success", "NO");

然而,当我在页面上看到显示时,它给出:

JSON格式的字符串:[{"success":"NO", "userid":"User 1", "bid":24.23}]

我需要按照userid、amount、然后是success的顺序排列。已经尝试在代码中重新排序,但没有效果。我还尝试过使用.append……需要一些帮助,谢谢!


这是使用 org.json 吗? - skaffman
6
@Leo,这可能是一个重复的问题,后来有一个问题发布了三年,并有一个回答链接回到这个问题?如果说有什么区别,那么应该将另一个问题视为重复。 - Adi Inbar
你想让我去其他问题并标记为重复吗? - Leo
2
这是我的问题...我知道JSON没有顺序,库可以自由生成任何它感觉到的顺序,但是这里有一个明显的人性因素。当查看JSON时,也许要进行视觉检查时,如果您期望一种顺序而库生成另一种顺序,则很难发现问题。当然,正如人们建议的那样,有解决方法等等,但是当我创建JSON时,我的大脑会按顺序考虑它,而当以不同的顺序发出时,这就很困难了。我认为库应该使用您指定的顺序,即使解析器不关心也应如此。 - The Welder
根据你所使用的库(我使用simplejson 1.1),你可以用LinkedHashMap替换JSONObject。 - Yaza
17个回答

119

在 JSON 对象中,你不能并且不应该依赖元素的顺序。

https://www.json.org/ 的 JSON 规范可知:

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

因此,JSON 库可以自由地重新排列元素的顺序,这不是一个错误。


14
因此,JSON库可以自由地重新排列元素的顺序,这并不是一个错误。重新排列可以帮助提高解析和序列化JSON数据的效率,并且对数据的含义没有影响。 - durai
12
有些关联容器使用排序函数来排列它们的元素,因此不保留顺序以实现更快的元素检索。 - ereOn
嘿,人们正在引用这个!但是数组有顺序,因此数组中的项不应被打乱。 - Thomas Weller
3
@Ted,那是GSON,一种由Google开发用于处理JSON的Java库。是否重新排序字段取决于每个库的开发人员。 - Andrew T.
4
关于 JSON 对象而非 JSON 数组。 - Daniel Higueras
显示剩余3条评论

17

我同意其他答案的观点。您不能依赖JSON元素的顺序。

但是,如果我们需要有一个有序的JSON,一个解决方案可能是准备一个LinkedHashMap对象与元素一起,并将其转换为JSONObject。

@Test
def void testOrdered() {
    Map obj = new LinkedHashMap()
    obj.put("a", "foo1")
    obj.put("b", new Integer(100))
    obj.put("c", new Double(1000.21))
    obj.put("d", new Boolean(true))
    obj.put("e", "foo2")
    obj.put("f", "foo3")
    obj.put("g", "foo4")
    obj.put("h", "foo5")
    obj.put("x", null)

    JSONObject json = (JSONObject) obj
    logger.info("Ordered Json : %s", json.toString())

    String expectedJsonString = """{"a":"foo1","b":100,"c":1000.21,"d":true,"e":"foo2","f":"foo3","g":"foo4","h":"foo5"}"""
    assertEquals(expectedJsonString, json.toString())
    JSONAssert.assertEquals(JSONSerializer.toJSON(expectedJsonString), json)
}

通常情况下,顺序不会像下面这样被保留。

@Test
def void testUnordered() {
    Map obj = new HashMap()
    obj.put("a", "foo1")
    obj.put("b", new Integer(100))
    obj.put("c", new Double(1000.21))
    obj.put("d", new Boolean(true))
    obj.put("e", "foo2")
    obj.put("f", "foo3")
    obj.put("g", "foo4")
    obj.put("h", "foo5")
    obj.put("x", null)

    JSONObject json = (JSONObject) obj
    logger.info("Unordered Json : %s", json.toString(3, 3))

    String unexpectedJsonString = """{"a":"foo1","b":100,"c":1000.21,"d":true,"e":"foo2","f":"foo3","g":"foo4","h":"foo5"}"""

    // string representation of json objects are different
    assertFalse(unexpectedJsonString.equals(json.toString()))
    // json objects are equal
    JSONAssert.assertEquals(JSONSerializer.toJSON(unexpectedJsonString), json)
}

你也可以查看我的帖子:http://www.flyingtomoon.com/2011/04/preserving-order-in-json.html


3
这个解决方案对我没有用。转换为JSONObject时抛出异常。如果我构造JSONObject(map),那么顺序就不会保留。如果我不进行转换就将字符串赋值给对象。 - Ernest Poletaev
1
这对我有用,但我必须使用JSONObject.toJSONString(obj)。否则,我会像@Ernest上面提到的那样遇到转换错误。 - Tricky12
@Tricky12,这对我来说毫无意义:1.) org.json.JSONObject 没有这个方法。2.) 看起来可以解决问题的方法 org.json.JSONObject.valueToString(obj) 不起作用,因为它在内部执行以下操作:new JSONObject(map).toString(),再次在内部使用 HashMap 而不是 LinkedHashMapthis.map = new HashMap<String, Object>(); - Frederic Leitenberger
1
@FredericLeitenberger toJSONString() 是一个方法,如果你有 org.json.simple 库的话。这个库在我的 IDE 中是可用的。我已经有几年没有看过这个了,所以不确定还能怎么帮忙。 - Tricky12
2
@FredericLeitenberger 我相信这是Groovy,一种基于Java的脚本语言。 - Turtle
显示剩余2条评论

10

如果您使用属于com.google.gson的JsonObject,您可以保留顺序 :D

JsonObject responseObj = new JsonObject();
responseObj.addProperty("userid", "User 1");
responseObj.addProperty("amount", "24.23");
responseObj.addProperty("success", "NO");

这个 JsonObject 的使用甚至不需要使用 Map<>。

CHEERS!!!


太酷了!即使是针对特定库,这也正是我需要的,用于简单有序的JSON输出。只需添加以下内容: String myJson = new GsonBuilder().create().toJson(responseObj); - Diego1974
1
谢谢。这应该被接受为正确答案。 - Faramarz Afzali
@FaramarzAfzali,很高兴你发现这对你的探索有用,兄弟 :D - thyzz
7年过去了,我还是要说同样的话,这到底怎么不是被接受的答案呢?尝试了那么多其他的方法,都不起作用,而这一个却像魔法一样有效。 - undefined

6

实际答案可以在规范中找到,json是无序的。 但作为人类读者,我按重要性顺序排序我的元素。这不仅更加合乎逻辑,而且更容易阅读。也许规范的作者从来没有读过JSON,但我读过。所以,这里有一个修复:

/**
 * I got really tired of JSON rearranging added properties.
 * Specification states:
 * "An object is an unordered set of name/value pairs"
 * StackOverflow states:
 * As a consequence, JSON libraries are free to rearrange the order of the elements as they see fit.
 * I state:
 * My implementation will freely arrange added properties, IN SEQUENCE ORDER!
 * Why did I do it? Cause of readability of created JSON document!
 */
private static class OrderedJSONObjectFactory {
    private static Logger log = Logger.getLogger(OrderedJSONObjectFactory.class.getName());
    private static boolean setupDone = false;
    private static Field JSONObjectMapField = null;

    private static void setupFieldAccessor() {
        if( !setupDone ) {
            setupDone = true;
            try {
                JSONObjectMapField = JSONObject.class.getDeclaredField("map");
                JSONObjectMapField.setAccessible(true);
            } catch (NoSuchFieldException ignored) {
                log.warning("JSONObject implementation has changed, returning unmodified instance");
            }
        }
    }

    private static JSONObject create() {
        setupFieldAccessor();
        JSONObject result = new JSONObject();
        try {
            if (JSONObjectMapField != null) {
                JSONObjectMapField.set(result, new LinkedHashMap<>());
            }
        }catch (IllegalAccessException ignored) {}
        return result;
    }
}

不错的 hack 解决方案。在我的 JSONObject 版本中,map 字段是 final 的,但似乎仍然可以工作。如果它不能工作,这个补充应该有助于克服 final:https://dev59.com/TXA75IYBdhLWcg3wbofM#3301720 - Frederic Leitenberger

4

来自lemiorhan示例,我可以通过更改lemiorhan的代码来解决问题。使用:

JSONObject json = new JSONObject(obj);

改为这样:

JSONObject json = (JSONObject) obj

因此,在我的测试代码中是:

Map item_sub2 = new LinkedHashMap();
item_sub2.put("name", "flare");
item_sub2.put("val1", "val1");
item_sub2.put("val2", "val2");
item_sub2.put("size",102);

JSONArray itemarray2 = new JSONArray();
itemarray2.add(item_sub2);
itemarray2.add(item_sub2);//just for test
itemarray2.add(item_sub2);//just for test


Map item_sub1 = new LinkedHashMap();
item_sub1.put("name", "flare");
item_sub1.put("val1", "val1");
item_sub1.put("val2", "val2");
item_sub1.put("children",itemarray2);

JSONArray itemarray = new JSONArray();
itemarray.add(item_sub1);
itemarray.add(item_sub1);//just for test
itemarray.add(item_sub1);//just for test

Map item_root = new LinkedHashMap();
item_root.put("name", "flare");
item_root.put("children",itemarray);

JSONObject json = new JSONObject(item_root);

System.out.println(json.toJSONString());

如果JSONObject可以由LinkedHashMap支持构建,那将是非常好的。这样的改进应该在哪里发布? - Frederic Leitenberger

3

JavaScript对象和JSON没有设置键的顺序的方法。你可能在Java中做得很好(我真的不知道Java对象是如何工作的),但如果它将被发送到Web客户端或另一个JSON的消费者,就无法保证键的顺序。


好的,他们在这里陈述了以下内容(如果确实是gson)。http://google-gson.googlecode.com/svn/tags/1.2.3/docs/javadocs/com/google/gson/JsonObject.html “此对象的成员元素按添加顺序维护。” - Ted
1
@Ted,那是gson,不是json - Madbreaks

2

1

如果您正在使用Maven,请尝试使用com.github.tsohr/json

<!-- https://mvnrepository.com/artifact/com.github.tsohr/json -->
<dependency>
    <groupId>com.github.tsohr</groupId>
    <artifactId>json</artifactId>
    <version>0.0.1</version>
</dependency>

它是从JSON-java衍生而来,但切换了其映射实现为LinkedHashMap,正如@lemiorhan在上面指出的那样。

0
主要意图是将有序的JSON对象作为响应发送。我们不需要使用javax.json.JsonObject来实现这一点。我们可以将有序的json创建为一个字符串。 首先,按照所需的顺序创建一个LinkedHashMap,其中包含所有的键值对。然后按照下面所示生成字符串形式的json。 使用Java 8更加简单。
public Response getJSONResponse() {
    Map<String, String> linkedHashMap = new LinkedHashMap<>();
    linkedHashMap.put("A", "1");
    linkedHashMap.put("B", "2");
    linkedHashMap.put("C", "3");

    String jsonStr = linkedHashMap.entrySet().stream()
            .map(x -> "\"" + x.getKey() + "\":\"" + x.getValue() + "\"")
            .collect(Collectors.joining(",", "{", "}"));
    return Response.ok(jsonStr).build();
}

该函数返回的响应如下所示: {"A":"1","B":"2","C":"3"}

抱歉,这是一个糟糕的建议。根据您的键/值包含的内容,可能会有很多潜在的故障风险。在当今时代,自己编写JSON逻辑是一个不好的想法。 - Madbreaks
@Madbreaks,我不打算推出任何JSON逻辑。如果要求有一个有序的JSON,则更简单的方法是将LinkedHashMap转换为JSON字符串。对于列表(如JsonArray),逻辑会有所不同。根据使用情况,还可以包括编码。 - Joy Banerjee

0
对于Java代码,应该为您的对象创建一个POJO类,而不是JSONObject,并使用JSONEncapsulator来处理您的POJO类。这样,元素的顺序取决于POJO类中getter和setter的顺序。例如,POJO类将如下所示:
Class myObj{
String userID;
String amount;
String success;
// getter setters in any order that you want

以及您需要在响应中发送json对象的位置

JSONContentEncapsulator<myObj> JSONObject = new JSONEncapsulator<myObj>("myObject");
JSONObject.setObject(myObj);
return Response.status(Status.OK).entity(JSONObject).build();

这行的响应将会是:
{myObject : {//属性顺序与getter setter顺序相同。}}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接