Sort Objects with respect to the value of a specific key

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
omareg94
Posts: 94
Joined: 27 Jun 2016, 22:46

Sort Objects with respect to the value of a specific key

10 Dec 2017, 07:59

I have objects that I've successfully read and parsed from a JSON file using Getfree's ParseJSON.

What I'm looking for is to be able to sort objects by the value of a specific key in each.
Let's say I want to sort students by their names or by their last_date_of_contribution, ids, or scores. How could I do this?

This is my sample.json JSON file:
Show code
This is my script to read and parse my sample.json file:
Show code
This is the included library JSONParse.ahk:
Show code
Last edited by omareg94 on 13 Dec 2017, 15:14, edited 2 times in total.
A_AhkUser
Posts: 1147
Joined: 06 Mar 2017, 16:18
Location: France
Contact:

Re: Sort Objects with respect to the value of a specific key

10 Dec 2017, 21:52

Hi omareg94,

First, unless I'm mistaken, your sample.json is badly formatted; there's a trailing comma at the end of the line whereas last_date_of_contribution entry is the last one in the object:

Code: Select all

   "name": "Olivia",
   "id": "19745",
   "last_date_of_contribution": 1484406671159,
For this - particular - case the following can be used:

Code: Select all

sortBy := "id"
; sortBy := "last_date_of_contribution"
; sortBy := "name"

file := FileOpen(A_ScriptDir . "\sample.json", "r", "utf-8") ; create a file object (the file object is closed thereafter using the close method)
input := file.read() ; alternative to FileRead
if (sortBy <> "name") ; see next comment
	input := RegExReplace(input, "(""name"":.+?,\R).+?(""" . sortBy . """:.+?,\R)", "$2$1") ; this swap the first entry ('name') in the input if necessary with the 'sortby' one to allow the sort method to operate upon it instead
file.close()
sampleObj := JSON_parse(input)

o := sampleObj.students
Loop % o.length()
	MsgBox % o[ a_index ][sortBy]

  /*
  derived from Getfree's parseJson function
  http://www.autohotkey.com/board/topic/93300-what-format-to-store-settings-in/#entry588268
  credits to Getfree
  */
  JSON_parse(jsonStr) {
    SC := ComObjCreate("ScriptControl") 
    SC.Language := "JScript"
    ;ComObjError(false)
    jsCode =
    (
    function arrangeForAhkTraversing(obj) {
      if(obj instanceof Array) {
        for(var i=0 ; i<obj.length ; ++i)
          obj[i] = arrangeForAhkTraversing(obj[i]) ;
        return ['array',obj.sort()] ; // added sort method upon obj
      } else if(obj instanceof Object) {
        var keys = [], values = [] ;
        for(var key in obj) {
          keys.push(key) ;
          values.push(arrangeForAhkTraversing(obj[key])) ;
        }
        return ['object',[keys,values]] ;
      } else
        return [typeof obj,obj] ;
    }
    )
    SC.ExecuteStatement(jsCode "; obj=" jsonStr)
    return convertJScriptObj2AHK(SC.Eval("arrangeForAhkTraversing(obj)"))
  }

  convertJScriptObj2AHK(jsObj) {
    if (jsObj[0]="object") {
      obj := {}, keys := jsObj[1][0], values := jsObj[1][1]
      loop % keys.length
        obj[keys[A_Index-1]] := convertJScriptObj2AHK(values[A_Index-1])
      return obj
    } else if (jsObj[0]="array") {
      array := []
      loop % jsObj[1].length
        array.Insert(convertJScriptObj2AHK(jsObj[1][A_Index-1]))
      return array
    } else return jsObj[1]
  }
The javascript, for its part, only requires a minor modification:

Code: Select all

    function arrangeForAhkTraversing(obj) {
      if(obj instanceof Array) {
        //...
        return ['array',obj.sort()] ; // added sort method upon obj
        //...
    }
To work with json data, you should consider using coco's JSON lib instead (in particular, you code can only read and not write json formatted data).

Hope this helps
my scripts
omareg94
Posts: 94
Joined: 27 Jun 2016, 22:46

Re: Sort Objects with respect to the value of a specific key

13 Dec 2017, 15:26

@A_AhkUser
Hi A_AhkUser,

I've fixed sample.json.

There's a problem with this sort that it considers the numeric value as a string (not numeric).
Let's say I will order by score, I've add this other property to sample.json to make this more clear.
It will order them as 11123, 2345, 563, 6311, 93 not 93, 563, 2345, 6311, 11123. How could I make it consider it as string?

I will be migrating to coco's JSON lib, how could I use sort separately, without editing the library JSON.Load() because I will need to reuse it without the sort functionality.

Thanks for your contribution.
A_AhkUser
Posts: 1147
Joined: 06 Mar 2017, 16:18
Location: France
Contact:

Re: Sort Objects with respect to the value of a specific key

18 Dec 2017, 12:40

Hi omarge94,
There's a problem with this sort that it considers the numeric value as a string (not numeric).
That's what I had in view saying 'this' and 'particular' case...
I will be migrating to coco's JSON lib, how could I use sort separately, without editing the library JSON.Load() because I will need to reuse it without the sort functionality.
I personally don't use this library even though I suggested using it. But as per the method description, the library can receive a reviver/ a replacer on loading/dumping respectively - that is, the parsing/stringification process of dump/load method can be altered before returning:

Code: Select all

SETTINGS_FILE := A_ScriptDir . "\sample.json"
output := JSON.Load(file:=FileOpen(SETTINGS_FILE, "r", "utf-8").read(), Func("myReviver")), file.close()

myReviver(k, v) {
; return value as is if you don't want to alter it
; return [value][1] ; for numeric values, preserve internally cached number
}
Here's an other solution which operate once the object is returned instead (note: it handle only integers):

Code: Select all

global sample ; assuming the sample json is loaded and save in 'sample' variable

sort(who, sortBy, ByRef obj) {
(p := (sortBy == "last_date_of_contribution" ? "_" : "")) ; last_date_of_contribution value is to large and should be converted to a string for this to work
for _, var in (who:=sample[who]), obj := []
	obj[ p . var[sortBy] ] := a_index
}
for each, characteristic in ["last_date_of_contribution", "id", "score"]
{
sort("students", characteristic, myObj)
	for k, v in myObj
		MsgBox % characteristic . A_Space . k . "," . sample.students[v].name
}

	
	

function() {
static __ := function()
global sample :=
(LTrim Join
{
    "students": [
        {
            "name": "Aubrey",
            "id": "2",
            "score": "93",
            "last_date_of_contribution": 1488192196481
        },
        {
            "name": "Lillian",
            "id": "5",
            "score": "11123",
            "last_date_of_contribution": 1491491812832
        },
        {
            "name": "Hannah",
            "id": "4",
            "score": "2345",
            "last_date_of_contribution": 1488037180604
        },
        {
            "name": "Alexis",
            "id": "1",
            "score": "563",
            "last_date_of_contribution": 1488036780822
        },
        {
            "name": "Charlotte",
            "id": "3",
            "score": "6311",
            "last_date_of_contribution": 1488231252924
        }
    ],
    "teachers": [
        {
            "name": "Natalie",
            "id": "21695",
            "last_date_of_contribution": 1503710683888
        },
        {
            "name": "Olivia",
            "id": "19745",
            "last_date_of_contribution": 1484406671159
        },
        {
            "name": "Isabella",
            "id": "18674",
            "last_date_of_contribution": 1471546107140
        },
        {
            "name": "Sophia",
            "id": "17169",
            "last_date_of_contribution": 1481488109129
        },
        {
            "name": "Emma",
            "id": "14051",
            "last_date_of_contribution": 1465785844146
        }
    ]
}
)
}

- Btw, it's your own json structure, I suggest you review it to make it more compliant with your specific goals.
- Heres' another link you might find intersesting: Sorting an associative array by values
- Alternatively, you can build a list and and use the built-in Sort command.

Hope this helps


[EDIT]Once again sorry for the late reply, I simply missed yours.
my scripts
buliasz
Posts: 26
Joined: 10 Oct 2016, 14:31
Contact:

Re: Sort Objects with respect to the value of a specific key

20 Dec 2017, 18:59

You might also find useful my function which preforms QuickSort on array of objects. Passing in second argument your own comparison function you can sort by any field you want.
Here's the code: https://github.com/buliasz/AHK_quicksor ... _array.ahk

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: doodles333, iamMG and 175 guests