Serializing collections

In addition to making our citable collection comparable on URN logic and iterable, we must make it serializable to and from CEX format. When we defined our Isbn10Urn type, it automatically inherited the UrnComparable trait because it was a subtype of Urn. We saw this when we tested a collection with the urncomparable function.

urncomparable(jane)
true

In contrast, CEX-serializable content does not all fall within a single type hierarchy. Instead, we will implement the CexTrait from CitableBase.

Defining our type as serializable

We first define our ReadingList type as serializable by importing the CexTrait type and assigning it a value of CexSerializable() for our type. We can test whether this assignment is recognized using the cexserializable function from CitableBase.

import CitableBase: CexTrait
CexTrait(::Type{ReadingList}) = CexSerializable()

cexserializable(rl)
true

When cexserializable is true, we know that CitableBase will dispatch functions to our type correctly.

Implementing the required functions

Now we can implement the pair of inverse functions cex and fromcex from CitableBase.

To serialize our collection to CEX format, we'll compose a citecollection type of CEX block, and simply list each ISBN's string value, one per line.

import CitableBase: cex
function cex(reading::ReadingList; delimiter = "|")
    header = "#!citecollection\n"
    strings = map(ref -> ref.isbn, reading.reff)
    header * join(strings, "\n")
end
cex (generic function with 4 methods)

Let's see what our reading list looks in this format.

cexoutput = cex(rl)
println(cexoutput)
#!citecollection
urn:isbn:022661283X
urn:isbn:022656875X
urn:isbn:022656875X
urn:isbn:1108922036
urn:isbn:0141395203

Now we'll write a function to instantiate a ReadingList from a string source.

Warning

To keep this illustration brief and focused on the design of citable collections, we will naively begin reading data once we see a line containing the block header citecollection, and just read to the end of the file. This would fail on anything but the most trivial CEX source. For a real application, we would instead use the CiteEXchange package to work with CEX source data. It includes functions to extract blocks and data lines by type or identifying URN, for example.

import CitableBase: fromcex
function fromcex(src::AbstractString, ReadingList; delimiter = "|")
    isbns = []
    lines = split(src, "\n")
    inblock = false
    for ln in lines
        if ln == "#!citecollection"
            inblock = true
        elseif inblock
            push!(isbns,Isbn10Urn(ln))
        end
    end
    ReadingList(isbns)
end
fromcex (generic function with 2 methods)

The acid test: can we roundtrip the CEX output back to an equivalent ReadingList?

rl2 = fromcex(cexoutput, ReadingList)
rl == rl2
true