Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Avoid sorted properties by using AMEMBERS()

Status
Not open for further replies.

Irwin1985

Programmer
Feb 2, 2017
44
0
0
ES
Hi everyone,

if you try this example:

Code:
CLEAR
obj = CREATEOBJECT("Empty")
ADDPROPERTY(obj, "c", "c content")
ADDPROPERTY(obj, "b", "b content")
ADDPROPERTY(obj, "a", "a content")
AMEMBERS(aProp, obj)
DISPLAY MEMORY LIKE aProp

you'll notice that AMEMBERS() function retrieves an ordered array but do you know any trick to avoid this?, I want my object's properties as it is.

thanks!

A team is only pieces that you exchange until you finish the work, it is efficient, it works.
 
I've never really thought about this, but having run your code, I can see that the array is in alphabetical order by property name. You say you want the "object's properties as it is". Do you mean you want them in the order in which you create them?

If so, I don't off-hand know of any way of doing that. But can I ask why you want to do this? If you can explain your overall goal, there might be some other way of achieving it.

Just to stress: The fact that AMEMBERS() produces a sorted array does not in any way affect how you access or use the individual properties.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Thanks Mike,

You're right, AMEMBERS() sorted array will not affect the way I access the object properties. I really need it because I have a class called JSONFox and some users are claiming they need the JSON format the same way they created it. In order to encode from VFP object to JSONString I do use AMEMBERS() but the final JSONString format is ordered alphabetically and that's the problem.

See below picture for a better understanding. jsonStr variable contains the raw json data and oJson is the object representation of that data, then stringify method takes that object and convert it back to string (with the wrong property order).

thanks!

json_example_bskjca.png


A team is only pieces that you exchange until you finish the work, it is efficient, it works.
 
Irwin, I understand this better now. Thanks for the explanation.

I can't think of any easy way of doing this. That said, if all the properties are being created at the same time (that is, in the same function or method), it might be possible to do it by brute force. Instead of using AMEMBERS(), you would create an array explicitly, then each time you create a property, you would store its name in the next row in the array. When you are ready to create the Json, you would add the current values of each the properties to the array, then pass the array to your stringify function.

The array would have to remain in scope during the process, so it might itself be a property of the method that creates it. As an alternative to an array, you could perhaps build jsonStr directly.

I'm not sure how well this would work. Ideally, one of the experts here will come up with a better solution.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Thanks for your suggestion Mike, I was thinking something similar but I really don't want to compromise the speed of the algorithm. Honestly the final JSONString order doesn't matter so I don't believe this extra code worth it.

regards!

A team is only pieces that you exchange until you finish the work, it is efficient, it works.
 
Indeed I could think of situations where Json member order plays a role, be it to easily compare the final stringified JSON object with the original JSON string.
In general it does not matter, but indeed if you list members of a JavaScript object in JS with Object.keys(obj) you get them in the order they came from the JSON string.

How about defining oJSON this way?
Code:
oJSON = createobject('empty')
Addproperty(oJson,'members','')
Addproperty(oJson,'object',Createobject('empty'))

Then the actual object will be oJSon.object instead of directly oJson. I know, this would need to change all code using oJSON to use oJSon.object instead. And the stringify method would need to be compatible with any fox object that isn't constructed this way, so stringify() would need to check whether oJSON is this special composition or not and if not, simply use AMEMBERS again.

There also is a way to automatically make oJSON mean oJSON.object with a THIS_ACCESS method, but that's not working for something based on the empty class, which just has properties, no events or methods.

I'd use a class based on the base class relation, that's the most lightweight base class has besides empty, especially less members than custom. And that it's related to a workarea doesn't matter. It's still tricky to access oJson.members when a THIS_ACCESS is established that returns THIS.object. But whether to redirect or not could be made dependent on a mode you define somewhere else - in a goApp property, for example.

Chriss
 
My head hurts just thinking about it :)

I honestly don't think it's worth the effort so I'll just leave it like that for now.

thank you all!

A team is only pieces that you exchange until you finish the work, it is efficient, it works.
 
I was going to suggest a wrapper function, that would create the property and add it to the array at the same time. But you're right that the whole thing is probably not worth the effort (except perhaps as an intellectual challenge).

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
I also don't think it'S worth it if all works so fine and fast based on the empty class.

You could see if other Json implementations for foxpro take into account the order of members in the JSON string. For example:
West wind web connection: nofox Json (or search whole Github for foxpro json projects, there are a lot)

Web Wind does have a lot more than just JSON support. But I haven't tested.

I think you just asked if there is some way to get amembers to produce a list of members in order of creating them with addproperty, as there is no creation history saved that orders the properties.

I mentioned JS can give you the list of members (they are called keys there) with Object.keys(obj), but intellisense, for example in the JS console of any browser also lists members in alphabetical order. If a developers concern is to identify unchanged objects by comparing their JSON string, you also depend on how the JSON is formatted with indention or not, with multiple lines or just one, etc., etc., so you do have a few facts that support a decision to not implement this feature and keep in mind properties always are listed alphabetically.

Chriss
 
And just FYI: VFP has COMPOBJ() to make a batch comparison of the members of two objects.

Chriss
 
Back in 2012 I wrote this code: See SaveToParser on tek-tips.

I'm investigating it presently to see if it will reveal object members in native order. If so, you could retrieve them in native order using SAVE TO and then that parser, which I'll provide here in a DLL if it works.

It was two days after that post that I stared the Visual FreePro project.

--
Rick C. Hodgin

 
I got my SaveToParser.dll working properly, but SAVE TO doesn't save objects.

However, there is a way to get them unsorted. If you have an object that was created with the SaveAsClass() member function, you can use:

Code:
lo = CREATEOBJECT("label")
lo.AddProperty("lValue", .t.)
lo.AddProperty("cValue", "test")
lo.SaveAsClass("c:\temp\test.vcx", "test_class")
USE c:\temp\test.vcx
LOCATE FOR NOT EMPTY(Timestamp)
ALINES(laLines, Properties)
FOR lnI = 1 TO ALEN(LaLines, 1)
    ? laLines[lnI]
NEXT

It will show the properties in the order added. In addition, if you reference what's there against an lo2 object which is created as a raw class of the same type, then you can remove anything that is in both identically, leaving only the custom / new properties added in the order added.

Using a reference class to filter out default members:

Code:
lo = CREATEOBJECT("label")
lo.AddProperty("lValue", .t.)
lo.AddProperty("cValue", "test")
lo.SaveAsClass("c:\temp\test.vcx", "test_class")

USE c:\temp\test.vcx
LOCATE FOR NOT EMPTY(Timestamp)
ALINES(laLines, Properties)

loRef = CREATEOBJECT("label")
AMEMBERS(laMembers, loRef)

FOR lnI = 1 TO ALEN(LaLines, 1)
    IF ASCAN(laMembers, UPPER(GETWORDNUM(laLines[lnI], 1))) = 0
        ? laLines[lnI]
    ENDIF
NEXT

--
Rick C. Hodgin
 
Rick. Yes, I could have told you SAVE TO does not save objects. There was a chance it could save objects based on empty, but even those can have objects in one of their property and that would reintroduce whatever problem there is with saving objects.

As we're talking of objects based on empty there is no way to save them as a class, the empty class has no events or methods, also not SaveAsClass. More general, you can't subclass empty. Neither as visual nor as prg class.

Okay, but you could change from empty to another base class like "custom" and then see into the vcx as a table. By the way, the Properties memo of the class record will only contain defined properties. So no need to check against amembers of an unmodified base class, as you do with ASCAN in your code.

But the problem may arise, one of the JSON properties matches a standard property. You may be able to still use it, but then some properties are either readonly or can't be set to any data type. So you actually need an empty object to be able to add any JSON property name. There still are things JS allows in names, that VFP doesn't. There's case sensitivity in JS, for example. You could easily have an object with properties a and A and they'd be different properties.

I know, corner cases, and it's corner cases anyway, not only about member sort order.



Chriss
 
My point is / was, switch it to another class and you can get there.

--
Rick C. Hodgin
 
Chris Miller said:
But the problem may arise, one of the JSON properties matches a standard property. You may be able to still use it, but then some properties are either readonly or can't be set to any data type. So you actually need an empty object to be able to add any JSON property name. There still are things JS allows in names, that VFP doesn't. There's case sensitivity in JS, for example. You could easily have an object with properties a and A and they'd be different properties.

You're limited in VFP what you can do. So, you have to work around it.

Prefix every property you want to use in your JSON variables with an underscore.

--
Rick C. Hodgin
 
When you go for another base class, I'd prefer a solution a la:

Code:
goApp = CREATEOBJECT("myappclass")

oJson = CREATEOBJECT("JSON")
&& adding property by assignment
oJSon.foo = "bar"
oJson.easy = .t.
oJson.abc = 123

goApp.lJSONRedirect = .f.
? oJson.cMemberlist
goApp.lJSONRedirect = .t.

DEFINE CLASS JSON as relation
    object = null
    cMemberlist = ","
    
    PROCEDURE Init()
        LPARAMETERS tcJson
        goApp.lJSONRedirect = .f.
        
        This.object = CREATEOBJECT("empty")
        IF PCOUNT()>0 && AND This.validJSON(tcJson)
           This.parse(tcJSON)
        ENDIF
        
        goApp.lJSONRedirect = .t.
    ENDPROC 
    
    PROCEDURE This_Access()
        LPARAMETERS tcMember

        loObject = IIF(goApp.lJSONRedirect,This,This.object)
        IF ISNULL(loObject)
           RETURN This
        ENDIF
        
        IF goApp.lJSONRedirect AND NOT PEMSTATUS(loObject,tcMember,5)
           goApp.lJSONRedirect = .f.
           This.AddProperty(tcMember,.null.)
           * goApp.lJSONRedirect = .t. && already done by Addproperty
        ENDIF 
        
        IF goApp.lJsonRedirect
           RETURN This.object
        ELSE
           RETURN This
        ENDIF
    ENDPROC 
        
    PROCEDURE Addproperty()
        LPARAMETERS tcProperty, tvValue, tlNoJSONRedirect

        goApp.lJSONRedirect = .f.
        Try
           ADDPROPERTY(this.object,tcProperty, tvValue)
           * only if ADDPROPERTY succeeds:
           This.cMemberlist = This.cMemberlist + tcProperty + ","
        CATCH
           *
        ENDTRY
        goApp.lJSONRedirect = NOT tlNoJSONRedirect
    ENDPROC
    
    PROCEDURE Removeproperty()
        LPARAMETERS tcProperty
        
        goApp.lJSONRedirect = .f.
        Try
           REMOVEPROPERTY(this.object,tcProperty)
           * only if REMOVEPROPERTY succeeds:
           This.cMemberlist = STRTRAN(This.cMemberlist, ","+tcProperty+",",",",1,1,2)
        CATCH
           *
        ENDTRY
        goApp.lJSONRedirect = .t.
    ENDPROC
    
    PROCEDURE serialize()
        *... serialize This.object to JSON string
        goApp.lJSONRedirect = .f. && Access to This.cMemberlist is granted
        * serialize This.object in the order of This.cMemberlist
        * Return cJSON
        goApp.lJSONRedirect = .t.
    ENDPROC 

    PROCEDURE parse()
        LPARAMETERS tcJSON
        goApp.lJSONRedirect = .f.
        *... populate This.object (based on empty) with JSON members and their values
        goApp.lJSONRedirect = .t.
    ENDPROC
    
    PROCEDURE validJSON()
        LPARAMETERS tcJson
        * validate JSON string to be correct JSON
        * Return .t./.f.
    ENDPROC 
ENDDEFINE


DEFINE CLASS myappclass as Custom
   lJSONRedirect = .f.
ENDDEFINE

Chriss
 
Code:
*cMemberlist = ","
cMemberlist = SPACE(0)

*This.cMemberlist = This.cMemberlist + tcProperty + ","
This.cMemberlist = This.cMemberlist + IIF(NOT EMPTY(this.cMemberList), ",", SPACE(0)) + tcProperty

*This.cMemberlist = STRTRAN(This.cMemberlist, ","+tcProperty+",",",",1,1,2)
This.cMemberlist = ALLTRIM(STRTRAN("," + This.cMemberlist + ",", ","+tcProperty+",",",",1,1,1), 1, ",")

--
Rick C. Hodgin
 
Why, Rick?

It works very well if you have a comma delimited list starting and ending with a comma and why not let the list itself have that, instead of adding it just in time.


Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top