Work

Home | Work | Play | Photos | Contact | About

Type-Length-Value - An XML/JSON Alternative

Home \ Work \ Type-Length-Value

Introduction

The problem I've been trying to solve is to find a data format that lets me efficiently send any kind of data over a network. Transmission needs to be efficient, as even in this time of Internet everywhere, networks can be unreliable and slow. I also want to process data before sending it - specifically, compression and encryption. XML and JSON are both really popular and useful ways to represent data. Each have benefits - some of which are shared. Each also have shortcomings, which make them... non-ideal.

The biggest issue I have with XML is that it's verbose. It bundles a ton of meta data with the data itself. Whilst I have other problems with XML (namespaces, the role of CDATA, DTDs), that in itself discounts it.

JSON also has limitations. It only supports a small number of data types, so no dates or byte arrays (BSON supports byte arrays and dates, but not universal numbers). Most uses of JSON have an implicit schema, which adds to the amount of data being transferred. Granted, not nearly as much as XML, but still more than is required when both ends of an exchange already know what they're exchanging.

At Nokia I discovered TLV (Type, Length, Value) - a format that can encode virtually any data. We used it to transmit maps and driving directions to phones. As its name implies, TLV elements consist of three fields -

The value field can contain anything. The Type field could, for example, be used to identify a data type (string, integer, date, and so on). It could be used to identify a field type, such as Customer Id or Customer Name. It is as you wish. This flexibility is what gives TLV another advantage over formats like XML and JSON - beyond the Type field, TLV does not include any field meta data, resulting in smaller data payloads.

Multiple TLV elements can be assembled as a sequence, forming a record, or row of data. TLV can do nesting, so a sequence of TLV elements can itself be represented as a single TLV element. Some advantages of using a TLV representation:

As you can imagine, TVL is no silver bullet either. It has baggage:

Happily these disadvantages don't affect the way I use TLV. I've no need to see the contents of what's transmitted (encryption will kill that benefit off anyway). I use TLV to exchange data between clients and servers that form part of the same system, so I use the same code to serialize and deserialize TLV on both client and server.

Implementation

By way of example, here's an application that proves the concept simply by serializing a list of customers to both a TLV element and a byte array, and desrializing back to the customer list. The sample app is a Windows Forms application written in Visual Basic.NET, and consists of the following:

TLV Sample app Solution Explorer

Scroll to the bottom to download the source code.

Tlv.vb (TLV Element)

The following code listing shows a class describing a TLV element, and how it might encode and decode a byte array (click the little +/- signs to expand and collapse code blocks):

 
Option Explicit On
Option Strict On

Public NotInheritable Class Tlv
Expand/Collapse
#Region "Private instance attributes"
Expand/Collapse
#Region "Public instance attributes"
Expand/Collapse
#Region "Constructors"
Expand/Collapse
#Region "Private instance methods"
Expand/Collapse
#Region "Public instance methods"
 
End Class

TlvCollection.vb (TLV Sequence)

Listing 2 constains a class describing a TLV sequence:

 
Option Explicit On
Option Strict On

Imports System.Collections.ObjectModel

Public NotInheritable Class TlvCollection
    Inherits Collection(Of Tlv)
Expand/Collapse
#Region "Constructors"
Expand/Collapse
#Region "Private instance methods"
Expand/Collapse
#Region "Public instance methods"
 
End Class

Util.vb (Application-Level constants)

Before we start with customers, we need to define some application-level constants that identify customer entities and customer collections. These constants will be used as values for the Type field in TLV elements:

    Option Explicit On
    Option Strict On

    Public Module Util

        ' TVL Type constants.
        Friend Const ENTITY_CUSTOMER As Byte = 1
        Friend Const COLLECTION_CUSTOMERS As Byte = 2

    End Module

Customer.vb (Customer class)

The Customer class is an entity class that describes a customer. I won't provide the full listing as it's just a bunch of properties and a default constructor:

Customer class

The interesting part is serializing and desrializing a Customer to and from TLV. First we add constants to identity customer fields in the TLV Type attribute:

    ' TLV type constants.
    Private Const CUSTOMER_ID As Byte = 1
    Private Const CUSTOMER_NAME As Byte = 2
    Private Const CUSTOMER_ADDRESS As Byte = 3
    Private Const CUSTOMER_JOIN_DATE As Byte = 4

Serializing a Customer object to TLV now simply requires a TLV element per field, and adding each element to a TLV sequence. Finally, the entire sequence is returned as a single TLV element:

    ' Serialize to a TLV element.
    Public Function ToTlv() As Tlv
    
        Dim _Tlv As Tlv = Nothing
        Dim _Tlvs As TlvCollection = New TlvCollection

        If Not m_Id.Equals(Guid.Empty) Then _Tlvs.Add(New Tlv(CUSTOMER_ID, m_Id.ToByteArray))
        If Not String.IsNullOrWhiteSpace(m_Name) Then _Tlvs.Add(New Tlv(CUSTOMER_NAME, Encoding.UTF8.GetBytes(m_Name)))
        If Not String.IsNullOrWhiteSpace(m_Address) Then _Tlvs.Add(New Tlv(CUSTOMER_ADDRESS, Encoding.UTF8.GetBytes(m_Address)))
        If Date.Compare(m_JoinDate, Date.MinValue) > 0 And _
           Date.Compare(m_JoinDate, Date.MaxValue) < 0 Then _
                _Tlvs.Add(New Tlv(CUSTOMER_JOIN_DATE, BitConverter.GetBytes(m_JoinDate.ToUniversalTime.Ticks)))

        _Tlv = New Tlv(ENTITY_CUSTOMER, _Tlvs.ToByteArray())

        Return _Tlv

    End Function

Deserializing a Customer object back from TLV is accomplished by deserializing the byte array into a TVL sequence containing the customer fields. We then cycle through the sequence, and assign values to customer properties using a switch statement:

    ' Deserialize from a TLV element.
    Public Sub FromTlv(ByVal value As Tlv)

        If value.Type = ENTITY_CUSTOMER Then

            Dim _Tlvs As TlvCollection = New TlvCollection(value.Value)

            For Each _Tlv As Tlv In _Tlvs
                Select Case _Tlv.Type
                    Case CUSTOMER_ID
                        m_Id = New Guid(_Tlv.Value)
                    Case CUSTOMER_NAME
                        m_Name = Encoding.UTF8.GetString(_Tlv.Value, 0, _Tlv.Length)
                    Case CUSTOMER_ADDRESS
                        m_Address = Encoding.UTF8.GetString(_Tlv.Value, 0, _Tlv.Length)
                    Case CUSTOMER_JOIN_DATE
                        m_JoinDate = New Date(BitConverter.ToInt64(_Tlv.Value, 0)).ToLocalTime
                End Select
            Next

        End If

    End Sub

As a final touch we'll add a second constructor to Customer.vb that accepts a TLV element as a parameter:

    Public Sub New(ByVal value As Tlv)
        MyBase.New()
        If value IsNot Nothing AndAlso value.Length > 0 Then
            If value.Type = ENTITY_CUSTOMER Then
                FromTlv(value)
            End If
        End If
    End Sub

CustomerCollection.vb (Customer list)

The CustomerCollection class contains a list of customers. It inherits from Collection(Of T) where T is Customer:

CustomerCollection class

Serializing customers is as easy as cycling through the list and adding customer TLV elements to a TLV sequence. The sequence is returned as a TLV element with type COLLECTION_CUSTOMERS:

    ' Serialize to a TLV sequence.
    Public Function ToTlv() As Tlv

        Dim _Tlv As Tlv = Nothing
        Dim _Tlvs As TlvCollection = Nothing

        If Me.Count > 0 Then
            _Tlvs = New TlvCollection
            For Each _Customer As Customer In Me.Items
                _Tlvs.Add(New Tlv(_Customer.ToTlv.ToByteArray()))
            Next
            _Tlv = New Tlv(COLLECTION_CUSTOMERS, _Tlvs.ToByteArray)
        End If

        Return _Tlv

    End Function

Deserializing customers is the inverse - cycle through the sequence and deserialize TLV elements into customers:

    ' Deserialize from a TLV sequence.
    Public Sub FromTlv(ByVal value As Tlv)

        If value.Type = COLLECTION_CUSTOMERS Then

            Dim _Tlvs As TlvCollection = New TlvCollection(value.Value)

            For Each _Tlv As Tlv In _Tlvs
                If _Tlv.Type = ENTITY_CUSTOMER Then
                    Me.Add(New Customer(_Tlv))
                End If
            Next

        End If

    End Sub

Download

.Zip   TLV sample application (VB.NET, works with Visual Studio Community editions 2010 to 2022)

< Back to Work | ^ Back to top


All content copyright © Michael Wittenburg 1995 to 2025. All rights reserved.
Merch (t-shirts designed by my twin)