14

I am using a JSONObject in order to remove a certin attribute I don't need in a JSON String:

JSONObject jsonObject = new JSONObject(jsonString);
jsonObject.remove("owner");
jsonString = jsonObject.toString();

It works ok however the problem is that the JSONObject is "an unordered collection of name/value pairs" and I want to maintain the original order the String had before it went through the JSONObject manipulation.

Any idea how to do this?

brice
  • 24,329
  • 7
  • 79
  • 95
Joly
  • 3,218
  • 14
  • 44
  • 70
  • Possible duplicate of [Keep the order of the JSON keys during JSON conversion to CSV](http://stackoverflow.com/questions/4515676/keep-the-order-of-the-json-keys-during-json-conversion-to-csv) – deep Mar 28 '17 at 14:09
  • Take a look at this. http://stackoverflow.com/questions/4515676/keep-the-order-of-the-json-keys-during-json-conversion-to-csv – deep Mar 28 '17 at 14:13

12 Answers12

6

I have faced the same problem recently and just transitioned all our tests (which expect JSON attributes to be in the same order) to another JSON library:

<dependency>
    <groupId>org.codehaus.jettison</groupId>
    <artifactId>jettison</artifactId>
    <version>1.3.5</version>
</dependency>

Internally it uses a LinkedHashMap, which maintains the order of attributes. This library is functionally equivalent to the json.org library, so I don't see any reason why not use it instead, at least for tests.

kaya3
  • 47,440
  • 4
  • 68
  • 97
anydoby
  • 408
  • 4
  • 9
  • I'm glad you called this out @anydoby. The need for testing Jackson's order of properties when serializing an object drove me to search for this question. For now, I plan to just stick with a simple string comparison ignoring whitespace; But ideally I'd like a JSON Object that maintains the order of the elements it was given, even though we know that the order _should_ be arbitrary. – aaiezza Feb 14 '20 at 13:44
6

try this

JSONObject jsonObject = new JSONObject(jsonString) {
    /**
     * changes the value of JSONObject.map to a LinkedHashMap in order to maintain
     * order of keys.
     */
    @Override
    public JSONObject put(String key, Object value) throws JSONException {
        try {
            Field map = JSONObject.class.getDeclaredField("map");
            map.setAccessible(true);
            Object mapValue = map.get(this);
            if (!(mapValue instanceof LinkedHashMap)) {
                map.set(this, new LinkedHashMap<>());
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return super.put(key, value);
    }
};
jsonObject.remove("owner");
jsonString=jsonObject.toString();
tremendous7
  • 721
  • 6
  • 9
  • 1
    May be better using the constructor public class JSONObjectOrder extends JSONObject { public JSONObjectOrder() { super(); try { Field map = JSONObject.class.getDeclaredField("map"); map.setAccessible(true); Object mapValue = map.get(this); if(!(mapValue instanceof LinkedHashMap)) { map.set(this, new LinkedHashMap()); } } catch(NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } } – David Apr 19 '21 at 12:45
  • 2
    @David your suggestion does not cover all 3 constructors of JSONObject. The problem with the constructor with the map parameter is that you can not change the field map into a LinkedHashMap before it is filled – tremendous7 Apr 19 '21 at 19:31
5

You can't.

That is why we call it an unordered collection of name/value pairs.

Why you would need to do this, I'm not sure. But if you want ordering, you'll have to use a json array.

brice
  • 24,329
  • 7
  • 79
  • 95
  • 1
    I just need it, hard to explain the whole system here :) JSON Array seems like the right way to go, thanks – Joly Mar 28 '12 at 14:13
  • one question tho: JSONArray has remove method which I'd like to use but I need to feed it with the index and I can't find a method that would return the index of an attribute in the array. Any ideas here other than looping through the Array manually? – Joly Mar 28 '12 at 15:06
  • The index of an attribute in a JSON array is it's position in the array. Try `jsonObject.getJSONArray("name").remove(12)` where 12 is the index in the array. – brice Mar 28 '12 at 15:09
  • yes but I need to figure out which index first. Anyway, I've done it with a for loop but now getting a different problem. While I was able to create a JSONObject from my JSON String it doesn't like it to be a JSONArray, getting A JSONArray text must start with '[' at character 1 – Joly Mar 28 '12 at 15:14
  • 1
    I think Jackson might be a better solution here – Joly Mar 28 '12 at 15:48
  • Well, they state the following here (if using gson). http://google-gson.googlecode.com/svn/tags/1.2.3/docs/javadocs/com/google/gson/JsonObject.html "The member elements of this object are maintained in order they were added." – Ted Aug 05 '14 at 14:49
  • net.sf.json provides a fixed ordering for its elements. If you need a feature like this, you should shop around the varying JSON implementations. – Fred Haslam Jul 24 '15 at 22:35
  • Although the answer is legit, an example is Mongo DB. If you search on an object out of order, it doesn't find it. Seems like a mistake to me, but that doesn't change the need to keep order. – Michael Nov 18 '20 at 22:53
4

You can go for the JsonObject provided by the com.google.gson it is nearly the same with the JSONObject by org.json but some different functions. For converting String to Json object and also maintains the order you can use:

Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(<Json String>, JsonObject.class);

For eg:-

String jsonString = "your json String";

JsonObject jsonObject = gson.fromJson(jsonString, JsonObject.class);

It just maintains the order of the JsonObject from the String.

Amit Jain
  • 97
  • 4
2

You can use Jsckson library in case to maintain the order of Json keys. It internally uses LinkedHashMap ( ordered ).

import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

The code to remove a field, the removed JsonToken could itself be read if required.

  String jsonString = "{\"name\":\"abc\",\"address\":\"add\",\"data\":[\"some 1\",\"some 2\",\"some3 3\"],\"age\":12,\"position\":8810.21}";
  ObjectMapper mapper = new ObjectMapper();
  JsonNode node = mapper.readTree(jsonString);
  System.out.println("In original order:"+node.toString());
  JsonToken removedToken = ((ObjectNode) node).remove("address").asToken();
  System.out.println("Aft removal order:"+node.toString());

ObjectNode implementation uses a LinkedHashMap, which maintains the insertion order:

 public ObjectNode(JsonNodeFactory nc) {
   super(nc);
   _children = new LinkedHashMap<String, JsonNode>();
 }
TechFree
  • 2,600
  • 1
  • 17
  • 18
1

If you can edit the server repose then change it to array of JSON objects.

JSON:

[
{PropertyName:"Date of Issue:",PropertyValue:"3/21/2011"},
PropertyName:"Report No:",PropertyValue:"2131196186"},{PropertyName:"Weight:",PropertyValue:"1.00"},
{PropertyName:"Report Type:",PropertyValue:"DG"}
]

And I handled it with JSONArray in client side (Android):

String tempresult="[{PropertyName:"Date of Issue:",PropertyValue:"3/21/2011"},PropertyName:"Report No:",PropertyValue:"2131196186"},PropertyName:"Weight:",PropertyValue:"1.00"},{PropertyName:"Report Type:",PropertyValue:"DG"}]"

JSONArray array = new JSONArray(tempresult);
             for (int i = 0; i < array.length(); i++) 
             {
                 String key = array.getJSONObject(i).getString("PropertyName"); 
                 String value = array.getJSONObject(i).getString("PropertyValue");
                 rtnObject.put(key.trim(),value.trim()); //rtnObject is LinkedHashMap but can be any other object which can keep order.


         }
Gil Allen
  • 1,169
  • 14
  • 24
1

Go on JSONObject class Change from HashMap() to LinkedHashMap()

 /**
     * Construct an empty JSONObject.
     */
    public JSONObject() {
        this.map = new LinkedHashMap();
    }

The LinkedHashMap class extends the Hashmap class. This class uses a doubly linked list containing all the entries of the hashed table, in the order in which the keys were inserted in the table: this allows the keys to be "ordered".

Yolomep
  • 173
  • 1
  • 5
  • 16
0

This is not easy, the main idea is to use LinkedHashMap, either pass in to the constructor (JSONObject(Map map)), or modify bytecode to handle the String parameter (JSONObject(String source)), which is the main use case. I got a solution in oson:

    public static JSONObject getJSONObject(String source) {
    try {
        int lineNumberToReplace = 157;

        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.get("org.json.JSONObject");

        if (ctClass.isFrozen() || ctClass.isModified()) {
            if (source == null) {
                return new JSONObject();
            } else {
                return new JSONObject(source);
            }
        }

        ctClass.stopPruning(true);
        CtConstructor declaredConstructor = ctClass.getDeclaredConstructor(new CtClass[] {}); 

        CodeAttribute codeAttribute = declaredConstructor.getMethodInfo().getCodeAttribute();

        LineNumberAttribute lineNumberAttribute = (LineNumberAttribute)codeAttribute.getAttribute(LineNumberAttribute.tag);

        // Index in bytecode array where the instruction starts
        int startPc = lineNumberAttribute.toStartPc(lineNumberToReplace);

        // Index in the bytecode array where the following instruction starts
        int endPc = lineNumberAttribute.toStartPc(lineNumberToReplace+1);

        // Let's now get the bytecode array
        byte[] code = codeAttribute.getCode();
        for (int i = startPc; i < endPc; i++) {
          // change byte to a no operation code
           code[i] = CodeAttribute.NOP;
        }

        declaredConstructor.insertAt(lineNumberToReplace, true, "$0.map = new java.util.LinkedHashMap();");

        ctClass.writeFile();

        if (source == null) {
            return (JSONObject) ctClass.toClass().getConstructor().newInstance();
        } else {
            return (JSONObject) ctClass.toClass().getConstructor(String.class).newInstance(source);
        }

    } catch (Exception e) {
        //e.printStackTrace();
    }

    if (source == null) {
        return new JSONObject();
    } else {
        return new JSONObject(source);
    }
}

need to include jar file from using mvn

<dependency>
    <groupId>javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.12.1.GA</version>
</dependency>
David He
  • 54
  • 4
  • 1
    Just hope you don't use this solution in production. – cassiomolin Sep 15 '16 at 17:05
  • unfortunately, this is one of the 2 ways to change a private final field, another one is to create a totally new class, replacing JSONObject, and change HashMap to LinkedHashMap. all the other solutions, either not general enough, or not work at all, such as reflection, extend to create a subclass... – David He Sep 15 '16 at 18:36
  • Instead of adding a dependency to modify the bytecode and perform workarounds, why not adding a dependency of a JSON parser that provides this feature out of the box? It can be achieved with Jackson, for example. – cassiomolin Sep 16 '16 at 07:38
0

From Android 20, JSONObject preserves the order as it uses LinkedHashMap to store namevaluepairs. Android 19 and below uses HashMap to store namevaluepairs. So, Android 19 and below doesn't preserve the order. If you are using 20 or above, don't worry, JSONObject will preserve the order. Or else, use JSONArray instead.

Roja Vangapalli
  • 259
  • 2
  • 5
0

In JDK 8 and above, We can do it by using nashorn engine, supported in JDK 8. Java 8 support to use js engine to evaluate:

String content = ..json content...  
String name = "test";  
String result = (String) engine.eval("var json = JSON.stringify("+content+");"
                                + "var jsResult = JSON.parse(json);"
                                + "jsResult.name = \"" + name + "\";"
                                + "jsResult.version = \"1.0\";"
                                + "JSON.stringify( jsResult );"
                            );
Vu Truong
  • 1,655
  • 1
  • 12
  • 10
0

I was able to do this with help of classpath overriding.

  1. created package package org.json.simple which is same as in jar and class named as JSONObject.
    1. Took existing code from jar and updated the class by extending LinkedHashmap instead of Hashmap

by doing these 2 steps it will maintain the order, because preference of picking `JSONObject will be higher to pick from the new package created in step 1 than the jar.

spandey
  • 1,034
  • 1
  • 15
  • 30
0

I accomplished it by doing a:

JSONObject(json).put(key, ObjectMapper().writeValueAsString(ObjectMapper().readValue(string, whatever::class)))

So essentially I deserialize a string to an ordered class, then I serialize it again. But then I also had to format that string afterwards to remove escapes.

.replace("\\\"", "\"").replace("\"{", "{").replace("}\"", "}")

You may also have to replace null items as well if you don't want nulls.

Michael
  • 1,177
  • 15
  • 15