open-source-parsers / jsoncpp

A C++ library for interacting with JSON.
Other
8.08k stars 2.63k forks source link

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

Open fedormsv opened 4 years ago

fedormsv commented 4 years ago

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.

dota17 commented 4 years ago

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

samsonsite1 commented 4 years ago

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.

fedormsv commented 4 years ago

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.

BillyDonahue commented 4 years ago

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.

samsonsite1 commented 4 years ago

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?

fedormsv commented 4 years ago

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.

samsonsite1 commented 4 years ago

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 commented 4 years ago

Ok, I tried a couple of things:

////////////////////////////////////// // this works typedef std::map<CZString, Value, std::less> 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?

fedormsv commented 4 years ago
  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.
samsonsite1 commented 4 years ago

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

fedormsv commented 4 years ago

nothing specific, except move constructor and assignment probably

BillyDonahue commented 4 years ago

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

samsonsite1 commented 4 years ago

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

fedormsv commented 4 years ago

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

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

Crazy-CodingCF commented 4 years ago

Only compiled using c++14

fedormsv commented 4 years ago

Fixed for c++ 11 now

samsonsite1 commented 4 years ago

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

Thank you very much!

GermanAizek commented 3 years ago

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

zcream commented 3 years ago

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

nmoreaud commented 2 years ago

Alphanumeric sorting would be useful too

PatrickYuSuper commented 2 years ago

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