jsoncpp icon indicating copy to clipboard operation
jsoncpp copied to clipboard

Keep order of fields as they are added, not sorting alphabetically

Open fedormsv opened this issue 5 years ago • 21 comments

To use json files in a context where human readability makes sense, order of the fields insertion can make sense. As jsoncpp is sorting them alphabetically, it becomes hard to read the file and find related fields in ddifferent ends of the object. For example Json::Value root; root["name"] = "Test;" root["host"] = "127.0.0.1"; root["port"] = 80;

would be nice to have printed as

{ "name" : "Test, "host" : "127.0.0.1", "port" : 80 }

and not as

{ "host" : "127.0.0.1", "name" : "Test, "port" : 80 }

I actually succeeded to have this feature by replacing a container used by Json::Value with a custom one.

fedormsv avatar Jul 09 '20 21:07 fedormsv

Relate discussion : https://github.com/open-source-parsers/jsoncpp/issues/237

dota17 avatar Jul 10 '20 07:07 dota17

Can someone please point out exactly where in the json code the fields are being sorted alphabetically? And what changes must be made to stop this from happening?

I want to read in a json file, and write it out again, without making any edits or changes. Field order must be preserved.

samsonsite1 avatar Sep 13 '20 23:09 samsonsite1

It's std::map container used to store fields that sorts them. To solve the issue you need another container. Take care: this map is also used for storing arrays elements, and there is a requrement to get an element with max key.

I solved the issue in our project creating custom container, something like LinkedMap.

fedormsv avatar Sep 14 '20 08:09 fedormsv

Can someone please point out exactly where in the json code the fields are being sorted alphabetically?

It's implicated by this typedef for the Json::Value::ObjectValues map type:

typedef std::map<CZString, Value> ObjectValues;

And std::map uses sorted keys. We currently have no option to track insertion order of the keys within a Json::Value.

It would be neat to let the Reader emit SAX parse events instead of just building up a Json::Value and returning it when it's finished. Then the parse and the storage of its results can be decoupled for special cases like yours. But that's perhaps a job for a different json library.

BillyDonahue avatar Sep 14 '20 14:09 BillyDonahue

Thanks for the reply. I haven't used std::map much to realize it's sorting keys while inserting them.

Is jsoncpp dependent on this sorting behavior in any way?

Because I'm willing to make a compromise. I can use std::map, but only if I can change the rules. I want to give priority to a small list of keywords, so they are always sorted at the top, while everything else is sorted as usual. So, I would like to able to pass std::map my own comparison function.

Would that cause any problems?

samsonsite1 avatar Sep 14 '20 20:09 samsonsite1

As I mentioned, it makes sense for arrays. More precisely: Value:size() method depends on the fact map is sorted and can give max key by accessing element before end() iterator. It allows array to keep only non-null entries and still easily get its size.

fedormsv avatar Sep 14 '20 21:09 fedormsv

I must be using an older version of jsoncpp. I can't find a method called max_key. I use an older compiler that doesn't require C+11.

Found std::map, it's hiding in value.h: typedef std::map<CZString, Value> ObjectValues;

I still would like to know if I can pass it my own comparsion function without breaking anything.

samsonsite1 avatar Sep 15 '20 03:09 samsonsite1

Ok, I tried a couple of things:

////////////////////////////////////// // this works typedef std::map<CZString, Value, std::less<CZString>> ObjectValues;

////////////////////////////////////// // this works struct cmp { bool operator()(const CZString l, const CZString r) const { return (l < r); } }; typedef std::map<CZString, Value, cmp> ObjectValues;

////////////////////////////////////// // this works

struct cmp { bool operator()(const CZString l, const CZString r) const {

      const char *a = (const char *)l.c_str();
      const char *b = (const char *)r.c_str();
      if(a && b) {
         return (std::strcmp(a, b) < 0);
      }
      return (l.index() < r.index());
  }

}; typedef std::map<CZString, Value, cmp> ObjectValues;

(please ignore bad formatting, code is correct, github stripped some symbols from it)

Which all follow the std::less rule, but if I try to change the ordering, it breaks, and the file only partially loads, with broken arrays.

I'm guessing it has something to do with this index() member? Is it not possible to change the sorted order of map elements without breaking anything?

samsonsite1 avatar Sep 16 '20 04:09 samsonsite1

  1. comparators shoud take references, not strings by value
  2. As already said, you need a special container for that. I have implementation that doesn't fit the styling of the project. I can only create own fork with it.

fedormsv avatar Sep 16 '20 09:09 fedormsv

thanks, but if it's written for C+11, I won't be able to compile it with my old compiler.

samsonsite1 avatar Sep 17 '20 01:09 samsonsite1

nothing specific, except move constructor and assignment probably

fedormsv avatar Sep 17 '20 09:09 fedormsv

You need a new compiler. Just bite the bullet. 2011 was 9 years ago.

BillyDonahue avatar Sep 17 '20 13:09 BillyDonahue

If I can get custom field ordering working, I just might. :)

samsonsite1 avatar Sep 18 '20 19:09 samsonsite1

Just have a look on my fork, branch named "non_sorting"

https://github.com/fedormsv/jsoncpp/tree/non_sorting

fedormsv avatar Sep 18 '20 19:09 fedormsv

Only compiled using c++14

Crazy-CodingCF avatar Sep 21 '20 03:09 Crazy-CodingCF

Fixed for c++ 11 now

fedormsv avatar Sep 21 '20 06:09 fedormsv

That is great, I got it compiled with VS 2015 C++, and it was seamless. Perfectly preserved order.

Thank you very much!

samsonsite1 avatar Sep 22 '20 02:09 samsonsite1

@fedormsv, Good and necessary sorting function, you must try to transfer completely to the master branch via PR.

GermanAizek avatar Jan 12 '21 22:01 GermanAizek

@fedormsv Thanks for the version. Another recommendation to add to the master branch.

For reference (mine as well as others) -

git clone https://github.com/fedormsv/jsoncpp.git
cd jsoncpp
git branch -a     
git checkout   remotes/origin/non_sorting

Now, I used the amalgamated version python amalgamate.py

Also copy historic_map.h into the json/ dir as its now needed.

zcream avatar Aug 20 '21 18:08 zcream

Alphanumeric sorting would be useful too

nmoreaud avatar Mar 18 '22 11:03 nmoreaud

The sorting/non-sorting option should be a STD feature in the main branch.

PatrickYuSuper avatar Aug 10 '22 10:08 PatrickYuSuper