From 04:00 PM CDT – 08:00 PM CDT (09:00 PM UTC – 01:00 AM UTC) Tuesday, April 16, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Flatten object to XML doesn't work with default values in class fields

Solved!
Go to solution

Well, this is odd, and I can't exactly explain ALL of the ways to reproduce this issue, but I've found at least one way to reproduce this weirdness.

 

When connecting a Class constant to "Flatten to XML", it produces empty XML data. I would expect it to produce an XML representation of the default value of the Class, but it's empty. I could *maybe* accept that's intended behavior (though a weird, undocumented way of doing things) but the weird part is that, if you write the default value to the object, then flatten THAT to XML, it still doesn't work- but it doesn't work in a different way 🙂

 

I have attached a small example project in LV2016 to demonstrate, but hopefully the images will help. Not visible in these images is the class definition, which is a Numeric and a String. The default values for these are 42 and Hello, respectively. The accessors are the standard generated accessors, nothing special there.

 

Here we have 3 Flatten to XML functions:

Block diagram.png

 

The first flattens an Object constant to XML. The second writes a new value to the Numeric field, then flattens that to an XML. The third writes the default value back to the object, then flattens that to an XML.

 

Here is the result:

 Results.png

 

Only the second one worked, and the two that didn't work were slightly different to boot. The first one looks like it doesn't even try- the Version is all zeros. Writing a new value to the Numeric field, then flattening that, works as expected- even the String part, which was unused, came through as its default value. The weird part is that if you write the Numeric back to its default value, it stops working again, but this time it at least has the correct Version information.

 

I'm not quite sure how to get around this, other than to make sure I write at least some new value to the default Object.

 

Am I bonkers in assuming this is NOT expected behavior? I would have guessed the outputs to all three of these would be identical with the exception of the second one having a different value for the Numeric field.

 

 

0 Kudos
Message 1 of 14
(3,661 Views)
Solution
Accepted by topic author BertMcMahan

It's not expected, but it is by design.

 

I don't like it either, but it's deliberate. Not my place to explain the reasons, as I don't really agree ( a Boolean input to opt out would be nice). Flatten to XML, JSON, string, they all have this "feature".

 

The workaround is not easy either.

 

You need a check to see if it's default (recursively for parent classes), and if it is default, get the control from the class control cluster (get class name, replace .lvclass with .ctl, open ref, get 1st control, convert to cluster, etc.), then convert that to XML, JSON, string, and replace the default data with that data...

 

*Sigh*

Message 2 of 14
(3,614 Views)

It's design decisions like this that make me sad.

 

I've run into this before.  Technically, the combination of class mutation history and not exporting default values can - in a very narrow field of view - yield good results.  But forcing everyone who don't reside within the narrow field of view to implement workarounds is annoying to say the least.

0 Kudos
Message 3 of 14
(3,609 Views)

Wow, that's incredibly frustrating. I feel like this would be worth a mention in the documentation at least. I read through as much of the Help files as I could find on this, and Google'd to no avail. There seemed to be zero documentation on this "feature". I'd absolutely argue this is a non-obvious result of this function, and should warrant a note in the "Flatten to XML" help. Maybe it's there in newer version of LV. That workaround you mentioned is also incredibly terrible, enough to make me want to use a different solution. I think my best bet for this project is to just make the default values something awful, so the user is forced to change them if something breaks and a "reset to default" is in order.

 

Thanks for the input guys.

0 Kudos
Message 4 of 14
(3,595 Views)

@BertMcMahan wrote:

That workaround you mentioned is also incredibly terrible, 


Well, thanks! Smiley Very Happy

 

I actually agree. And there will be side problems as well. For instance, IIRC, opening a reference to a class will change the cursor to an annoying wait cursor. Not nice when the program is being used. So you need another way, but that doesn't work in an executable. The executable doesn't have the wait cursor. This kind of code is indeed tedious.

 

The only positive part is it will fit in a sub VI (or .vim, in  >17.1). I don't think I actually have one laying around for this exact use case, and it will be a puzzle of a few hours to get all the details right.

 

I bet this is already in some toolkit somewhere...

0 Kudos
Message 5 of 14
(3,571 Views)

Just to point out another way of looking at things: "Objects" are meant to be encapsulated, with their internal data hidden from the outside world.  A serialization that converts any object's internal state to a readable string is a violation of encapsulation.  The by-the-OOP-book way to do what you guys want to do is to have "To XML" methods that must be explicitly overridden by all child classes.  That's how AQ's "Character Lineator" works, for example.  Of course, that can be a lot of work!

 

NI's flatten-to-XML functions and Objects don't quite make sense together , as they are a conflicted mix of "opaque string that can only be unflattened back to the original Object" and "readable string that makes all the internal info available".

0 Kudos
Message 6 of 14
(3,565 Views)

@BertMcMahan wrote:

 

 

I'm not quite sure how to get around this,...

 


BTW, you guys should describe your use cases (none of you have mentioned what you are trying to accomplish) so NI can understand the requirements.    

0 Kudos
Message 7 of 14
(3,561 Views)

@drjdpowell wrote:

Just to point out another way of looking at things: "Objects" are meant to be encapsulated, with their internal data hidden from the outside world.  A serialization that converts any object's internal state to a readable string is a violation of encapsulation.  The by-the-OOP-book way to do what you guys want to do is to have "To XML" methods that must be explicitly overridden by all child classes.  That's how AQ's "Character Lineator" works, for example.  Of course, that can be a lot of work!

 

NI's flatten-to-XML functions and Objects don't quite make sense together , as they are a conflicted mix of "opaque string that can only be unflattened back to the original Object" and "readable string that makes all the internal info available".


That's a somewhat valid point about encapsulation.

 

I'm not sure the To XML is by the book. The Single Responsibility rule would argue that the class should have nothing to do with the To XML. That would be a second responsibility!

 

And of course, that To XML method would face the same problem. As a workaround, 'flattening' the private data must be done by unbundling each item. That is error prone (and a bit annoying): if one item is added it's easy to forget to extend the To XML method.

 

As for a use case (for NI, if they are reading), you might want to send objects to non-LabVIEW programs. Try telling Python or C++ an empty XML string is the default, whatever that is (or actually: used to be). I often don't care about the versions (update one, update the other). I care about the program actually getting the data. If those values are sometimes empty, I need double administration of the default values, and lots of boilerplate code as well.

 

I think there where valid reasons for making it like it is. Less data, more speed probably... But it's often a problem. I don't suggest to change it, but to provide an option.

0 Kudos
Message 8 of 14
(3,555 Views)

 wrote:

 

As for a use case (for NI, if they are reading), you might want to send objects to non-LabVIEW programs. Try telling Python or C++ an empty XML string is the default, whatever that is (or actually: used to be). I often don't care about the versions (update one, update the other). I care about the program actually getting the data. If those values are sometimes empty, I need double administration of the default values, and lots of boilerplate code as well.

Your using Objects here as a "Cluster of attributes with methods", rather than an encapsulated member of a class whose implementation details are private.  Your data has meaning independent of its class, which is why it makes sense to pass it to a Python program (that cannot load its class definition).

 

For this use case, that XML flattened format is pretty hideous, so it would be good for NI to consider a better way of implementing such unencapsulated object-like clusters.  I believe NXG is supposed to make Clusters-->Objects more of a continuum. 

0 Kudos
Message 9 of 14
(3,547 Views)

wiebe@CARYA wrote:

@drjdpowell

I'm not sure the To XML is by the book. The Single Responsibility rule would argue that the class should have nothing to do with the To XML. That would be a second responsibility!


The Class is responsible for its internal data.   AQ's Lineator separates the formating to a separate class.  Personally, I just define JSON a core technology for expressing data as a string, and if another format is required, I use another component to convert to/from the JSON.

0 Kudos
Message 10 of 14
(3,545 Views)