51

Seems like Gson.toJson(Object object) generates JSON code with randomly spread fields of the object. Is there way to fix fields order somehow?

public class Foo {
    public String bar;
    public String baz;
    public Foo( String bar, String baz ) {
        this.bar = bar;
        this.baz = baz;
    }
}
Gson gson = new Gson();
String jsonRequest = gson.toJson(new Foo("bar","baz"));

The string jsonRequest can be:

  • { "bar":"bar", "baz":"baz" } (correct)
  • { "baz":"baz", "bar":"bar" } (wrong sequence)
ℛɑƒæĿᴿᴹᴿ
  • 4,983
  • 4
  • 38
  • 58
Sergey Romanovsky
  • 4,216
  • 4
  • 25
  • 27
  • 7
    Why is sequence relevant? This indicates a wrong tool to parse the JSON. – BalusC Jun 16 '11 at 01:15
  • 1
    I also wondered why. But the server is a .NET application. Authors of the application say that they need the field named "__type" (it points to schema) to be first among others. – Sergey Romanovsky Jun 16 '11 at 01:40
  • Wow. Anyway, this is possible with a custom serializer. I posted an answer. – BalusC Jun 16 '11 at 01:46
  • 3
    I'd petition the .NET app authors to discard this requirement and implement more robust JSON processing. – Programmer Bruce Jun 16 '11 at 03:10
  • 4
    If you get a chance, you could also smack the .NET app authors with the JSON spec, which, regarding what a JSON object is, clearly states, "An object is an unordered collection of zero or more name/value pairs..." [http://www.ietf.org/rfc/rfc4627.txt?number=4627] So, their app/messaging API is not JSON spec compliant. (Arguments like this tend to work especially well with those fancy "enterprise architect" folks.) – Programmer Bruce Jun 19 '11 at 19:22
  • I wanted a different order of fields in my JSON, so I changed the order of fields in my source code and it did the trick for me. Maybe, it can help others as well. (GSON 2.6.2) – MartyIX May 02 '16 at 09:11
  • 1
    Perhaps if you want metadata in the beginning you would want this. – Andrew Mar 24 '17 at 13:09
  • 1
    @BalusC An application that parses a large JSON document as a stream can optimize it's memory consumption if it gets the elements in optimal order. The fact that an application can not rely on the order of the fields does not mean the programmer should not take an advantage if they were in suitable order. – Torben Dec 07 '19 at 17:45
  • @Torben: That's not what's the problem is about. The main issue is that the JSON spec does nowhere require ordering of object's properties. In other words, if you attempt to rely on the ordering or object's properties, then you're basically attempting to rely on something unspecified. And that makes the code brittle and unportable. Just use arrays instead. Their values are in paper required to be in order. Then you can safely assume that any tool which massages JSON strings will work the way you expect and you could of course optimize further on that. – BalusC Dec 08 '19 at 21:40
  • @BalusC Please read what I wrote. – Torben Dec 09 '19 at 04:00
  • Landed here looking for exactly this. The comments are old, but my use case for specific ordering is that some of the records are spot-checked by people. They want the order consistent and they read the spec the other way around - Since the computer doesn't care about the order there is no reason that we should not put them out in the order that the humans want them. @BalusC – Brick Dec 15 '21 at 22:12
  • This is a good question. The Authorize.net JSON implementation has this ordered requirement (sadly). From their docs: "The Authorize.net API, which is not based on REST, offers JSON support through a translation of JSON elements to XML elements. While JSON does not typically require a set order to the elements in an object, XML requires strict ordering. Developers using the Authorize.net API should force the ordering of elements to match this API Reference." – Perry Tew Oct 19 '22 at 15:26

4 Answers4

41

You'd need to create a custom JSON serializer.

E.g.

public class FooJsonSerializer implements JsonSerializer<Foo> {

    @Override
    public JsonElement serialize(Foo foo, Type type, JsonSerializationContext context) {
        JsonObject object = new JsonObject();
        object.add("bar", context.serialize(foo.getBar());
        object.add("baz", context.serialize(foo.getBaz());
        // ...
        return object;
    }

}

and use it as follows:

Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, new FooJsonSerializer()).create();
String json = gson.toJson(foo);
// ...

This maintains the order as you've specified in the serializer.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • That this approach works is unfortunately not in the documentation for the JsonObject.add(String, JsonElement) method (or in the documentation for the addProperty methods), rather it's revealed in the documentation for the entrySet() method! (For the interested: The underlying implementation that provides for this feature stores added elements using a LinkedHashMap.) – Programmer Bruce Jun 16 '11 at 03:06
  • thanks. However in my case the solution becomes too heavy. I'd rather prefer JsonWriter recomended by @Programmer Bruce. And yes, shame on .NET json parser. – Sergey Romanovsky Jun 16 '11 at 22:53
  • @romanovsky Too heavy? Maybe you're doing something for which there is an easier way to do it. I'd prefer using a custom deserializer over using the JsonWriter directly in almost every situation. For the specific problem you described, I'd take BalusC's solution a few steps further, to make it more generic and to make the element order externally configurable. It could then be reused for different objects. – Programmer Bruce Jun 16 '11 at 23:53
  • Thats a great solution. I wanted something like that and this solution really helped me. Thanks Mate :) – frost Jun 26 '15 at 03:49
  • Great answer. +1 We can register multiple adapters in the GsonBuilder depending on the object composition. ```GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapter(MyType2.class, new MyTypeAdapter()); gson.registerTypeAdapter(MyType.class, new MySerializer()); gson.registerTypeAdapter(MyType.class, new MyDeserializer()); gson.registerTypeAdapter(MyType.class, new MyInstanceCreator());``` – Vadiraj Purohit Jun 12 '16 at 23:37
26

If GSON doesn't support definition of field order, there are other libraries that do. Jackson allows definining this with @JsonPropertyOrder, for example. Having to specify one's own custom serializer seems like awful lot of work to me.

And yes, I agree in that as per JSON specification, application should not expect specific ordering of fields.

Carl Manaster
  • 39,912
  • 17
  • 102
  • 155
StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • That is the thing I needed. It should work the best in this case (i.e. simple objects serialization with forcing one property to be the first for .NET parser on the other side). Thanks @StaxMan. – Sergey Romanovsky Jun 20 '11 at 19:42
  • 1
    I'm serializing some Beans to JSON using Jackson for persistence and testing. Changes to the format made it difficult to write assertions against the serialized string. This was just the medicine to make my tests work =) – Spina Jun 21 '12 at 21:31
  • @Spina you should never ever compare JSON output to a hard-coded String, that is just asking for trouble. Besides ordering that may change aspects of white-space or escaping can change, legally, and without changing logical content. So either comparison should enforce canonicality of some kind (no standard specified for JSON as far as I know), or it should use JSON object model (better choice). Jackson has `JsonNode`, and its `equals()` works well for tests, for example; ignores ordering of properties, values are unified. – StaxMan Mar 18 '15 at 21:31
  • 2
    @StaxMan While I agree with your statement for production code, for unit tests (and certain integration tests) I consider it a convenient and simple way to write assertions. – Spina Mar 26 '15 at 16:11
  • @Spina Ok yes, for simple tests that may suffice. It is still good to be aware of problems with things like ordering, but at least with tests one may have full control over input so avoid most problems. – StaxMan Mar 26 '15 at 20:27
6

Actually Gson.toJson(Object object) doesn't generate fields in random order. The order of resulted json depends on literal sequence of the fields' names.

I had the same problem and it was solved by literal order of properties' names in the class.

The example in the question will always return the following jsonRequest:

{ "bar":"bar", "baz":"baz" }

In order to have a specific order you should modify fields' names, ex: if you want baz to be first in order then comes bar:

public class Foo {
    public String f1_baz;
    public String f2_bar;

    public Foo ( String f1_baz, String f2_bar ) {
        this.f1_baz = f1_baz;
        this.f2_bar = f2_bar;
    }
}

jsonRequest will be { "f1_baz ":"baz", "f2_bar":"bar" }

YouQam
  • 61
  • 1
  • 3
2

Here's my solution for looping over json text files in a given directory and writing over the top of them with sorted versions:

private void standardizeFormat(File dir) throws IOException {
    File[] directoryListing = dir.listFiles();
    if (directoryListing != null) {
        for (File child : directoryListing) {
            String path = child.getPath();
            JsonReader jsonReader = new JsonReader(new FileReader(path));

            Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(LinkedTreeMap.class, new SortedJsonSerializer()).create();
            Object data = gson.fromJson(jsonReader, Object.class);
            JsonWriter jsonWriter = new JsonWriter(new FileWriter(path));
            jsonWriter.setIndent("  ");
            gson.toJson(data, Object.class, jsonWriter);
            jsonWriter.close();
        }
    }
}

private class SortedJsonSerializer implements JsonSerializer<LinkedTreeMap> {
    @Override
    public JsonElement serialize(LinkedTreeMap foo, Type type, JsonSerializationContext context) {
        JsonObject object = new JsonObject();
        TreeSet sorted = Sets.newTreeSet(foo.keySet());
        for (Object key : sorted) {
            object.add((String) key, context.serialize(foo.get(key)));
        }
        return object;
    }
}

It's pretty hacky because it depends on the fact that Gson uses LinkedTreeMap when the Type is simply Object. This is an implementation details that is probably not guaranteed. Anyway, it's good enough for my short-lived purposes...

matt burns
  • 24,742
  • 13
  • 105
  • 107
  • Works really well! As long as Gson internally uses LinkedTreeMap this would work just fine. – Teddy Jun 16 '17 at 13:14