Joly
Joly

Reputation: 3276

How to maintain the order of a JSONObject

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?

Upvotes: 19

Views: 80982

Answers (11)

TechFree
TechFree

Reputation: 2974

You can use Jackson 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>();
 }

Upvotes: 2

Michael
Michael

Reputation: 1183

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.

Upvotes: -1

JavaExpert12
JavaExpert12

Reputation: 17

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".

Upvotes: 0

tremendous7
tremendous7

Reputation: 776

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

Upvotes: 8

anydoby
anydoby

Reputation: 448

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.

Upvotes: 7

Amit Jain
Amit Jain

Reputation: 127

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.

Upvotes: 5

Vu Truong
Vu Truong

Reputation: 1883

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

Upvotes: -1

Roja Vangapalli
Roja Vangapalli

Reputation: 259

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.

Upvotes: 0

David He
David He

Reputation: 54

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>

Upvotes: 0

Gil Allen
Gil Allen

Reputation: 1237

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.


         }

Upvotes: 1

brice
brice

Reputation: 25039

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.

Upvotes: 2

Related Questions