Nikita Savchenko · Jun 1, 2016

What is the best way to serialize object/list/array/etc to string?


Are there any short ways of serializing COS entity to the string? I am searching for something like .$toJSON() , but I need Caché 2013.* support.

The one way I supposed to work is to use $listbuild:


set str = $LISTTOSTRING($LB(1,2,3,",",5))
set list = $LISTFROMSTRING(str,",")
zw list


But the problem is that $listbuild does not escape delimiters, and it is not possible to deserialize serialized string. The structure I need to serialize may contain any characters.

Are there any short ways to do this in Caché to avoid writing custom parser?

0 2,336
Discussion (15)3
Log in or sign up to continue

ZWRITE command is implemented in the ObjectScript, and if you are happy with the way it's quoting $LB then you could reuse it's core functionality, i.e.

DEVLATEST:22:47:39:USER>set lb = $listbuild(1,2,3,",",5)
DEVLATEST:22:47:41:USER>write $$Quote^%qcr(lb)

Thanks for the tips! My use case is only to get the string, it doesn't matter printable or not.

I also tested that this works with nested lists, so everything is OK now:

USER>set l = $lb(1,2,$lb(3,4,5),6)

Now it becomes clear!

I had wondered if your requirement for "string" was that it contained only printable characters. In such a case one solution could be to base64-encode the result of $listbuild. On a Unicode instance of Caché you'd need to convert it to UTF8 first.

USER>s list=$lb($c(0),$c(1),"Hello",",","World")
USER>s printable=$system.Encryption.Base64Encode($zconvert(list,"O","UTF8"))
USER>w printable
USER>s list2=$zconvert($system.Encryption.Base64Decode(printable),"I","UTF8")
USER>w list2=list
USER>w $a($li(list2,1))
USER>w $a($li(list2,2))
USER>w $li(list2,3)
USER>w $li(list2,4)
USER>w $li(list2,5)

Note that Base64Encode adds a CRLF after every 76 characters, so if you want to remove these from your "printable" you can either $TR(printable,$c(13,10)) or on 2015.2 or later you can pass a second argument to Base64Encode.

Thanks John, the above terminal transcript is not correct showing what I wanted to show. I've fixed this.

Why can't you understand the purpose of this?

I can only transfer strings by some channel (f.e. interprocess communication). Now I need to send the array of strings (list, object, etc) from sender to receiver. To send this array, I must convert it to the string and then, after receiving, convert it back to array to read it's elements. How to do this in Caché 2013.*? The strings in array can contain any characters, so it is not possible to specify any delimiter character.

Finally I got why the question is pretty confusing for you. Just imagine, I didn't know that $lb() returns the simple string. I didn't know that I can write this string to file, read it from file, and then apply $LISTGET function to it. I didn't know $LISTGET function takes string type. I thought that $LIST is something more than just a string.

So the solution:

set string = $LB(1,2,3,",",5)
/* send this string anywhere */
set element = $LISTGET(string, 4) // = ","

I don't understand what you mean by "until the list values may contain any set of characters".

Nor do I understand the purpose of the second line of your terminal transcript above. None of your subsequent lines do anything with your local variable called list

[UPD fixed] Got you. Incredible! It seems that this is what I needed:

USER>set string = $lb($C(1),$c(2),$c(3),",",$c(0))
USER>w $a($LISTGET(a,1))
USER>w $a($LISTGET(a,2))
USER>w $a($LISTGET(a,3))
USER>w $a($LISTGET(a,5))

Until the list values may contain any set of characters here this is the best solution, thank you, Jon!

Given the returned from Quote^%qcr expression you could use XECUTE to reevaluate the string, i.e.:

DEVLATEST:22:51:39:USER>set q= $$Quote^%qcr(lb)
DEVLATEST:22:51:54:USER>x "s u = "_q
DEVLATEST:22:52:30:USER>zw u

$lts and $lfs are the abbreviated forms of $listtostring and $listfromstring, respectively. $lb(1,2,3,",",5) returns a string, which seems to be what you want. I don't understand why you are then converting it to a delimited string with $listtostring.

Thanks! But if I have possibility to avoid using this method - I'll use one :)

Nice, but how to deserialize this string? Are there any built-in methods to do so?

You can use %SerialObject for that:

Class Utils.Serial Extends %SerialObject

Property Payload As %String;

/// zw ##class(Utils.Serial).Test()
ClassMethod Test(input As %String = {$lb(1,2,3,",",5)}) As %String
    set obj = ##class(Utils.Serial).%New()
    set obj.Payload = input
    do obj.%SerializeObject(.str)
    kill (str)
    set obj = ##class(Utils.Serial).%Open(str)
    return obj.Payload

For example:

zw ##class(Utils.Serial).Test("1,2,3,,,5")

zw ##class(Utils.Serial).Test($lb(1,2,3,",",5))

Even after looking at Timur and Eduard's answers, I don't understand the question. $lb(1,2,3,",",5) returns a string. Why are you trying to round trip it through a delimited string using $lts and $lfs?

Don't understand what are $lts and $lfs. I need to convert this string back to the structure, so I can read it's elements. The delimiter needs to be escaped somehow.

Thank you Fabio! The problem with all of the methods in %ZEN.Auxiliary.jsonProvider is that it writes JSON string directly to the current device, not allowing to get the string itself. But nevertheless I did it by rewriting this method and creating a custom method, which returns a string instead of printing JSON to the device.