JSContact-tools

By on 9 Sep 2021

Category: Tech matters

Tags: ,

Blog home

JSContact is a specification that defines a data model for contact card data normally used in address book or directory applications and services. It aims to be a more efficient alternative to the vCard data format and to provide a JSON-based standard representation of contact card data.

jscontact-tools is a Java library for JSContact creation, validation, and serialization/deserialization. Conversion from and to vCard, as well as transliterations in XML and JSON (namely, xCard and jCard) are also supported by following the mapping rules defined in this draft. Validation and conversion of vCard formats leverage the features provided by the ez-vcard library.

Why use JSContact?

There are some known issues with jCard.

Firstly, both client and server implementers consider jCard to be unintuitive and complicated. This is because it is only a JSON transliteration of the vCard text format and doesn’t define a data model.

Additionally, jCard allows some properties to be multi-valued. Therefore, implementers need to do additional work when serializing/deserializing a jCard, transforming it into a more practical format. 

Finally, because jCard is array-oriented, one is unable to use open-source object building and validation frameworks unless the serialization/deserialization process is customized to convert jCard into a more practical internal object-oriented data model. JSContact is object-oriented so there’s no additional work needed to implement validation features on objects before/after their serialization/deserialization.

For all the reasons above, jCard is incompatible with best practices of RESTful APIs.

JSContact can represent the same content as jCard but in a simpler and more efficient way. The information is highly structured, and most collections are defined as maps so that objects can be easily built and validated and the serialization/deserialization process is straightforward.

Creation

The JSContact specification defines two topmost objects:

  1. Card: Storing information about a person, organization, or a company.
  2. CardGroup: Representing a group of cards.

Other data types have been introduced to define the Card properties:

NameComponentA component of the entity name
AddressA physical location of the entity
StreetComponentA street address component (such as name, number, building, floor)
PhoneA phone number to contact the entity
EmailAddressAn email address to contact the entity
ResourceAn online resource (such as a web URL or a social media account) of the entity
FileA photograph or an image of the entity
ContactLanguageA language for contacting the entity
OrganizationCompany or organization names, and units of the entity
TitleA job title or functional position of the entity
RelationThe relationships between the entity and other entities
AnniversaryA memorable date or event for the entity
PersonalInformationPersonal information about the entity (such as a hobby)

Table 1 — Data types that define the Card properties, and descriptions.

This example shows the successful creation of an EmailAddress instance:

EmailAddress email = EmailAddress.builder()
                                 .context(Context.work(), Boolean.TRUE)
                                 .context(Context.private(), Boolean.TRUE)
                                 .email("mario.loffredo@iit.cnr.it")
                                 .build();

The build method throws the Java NullPointerException when a required property is missing. An unsuccessful creation of an EmailAddress instance is shown below.

// email is missing
EmailAddress.builder().context(Context.work(),Boolean.TRUE).build();

Creation can also be achieved via cloning. However, cloning can only be applied to Card and CardGroup objects.

Validation

JSContact validation

Even if the topmost JSContact objects are correctly built, they might need to be validated if they were obtained from an external producer. Validation is performed by invoking the method isValid. This method returns the boolean value true if the object satisfies all the constraints included in the specification or false otherwise. If the validation process doesn’t end successfully then the error message can be obtained by calling the getValidationMessage method.

The following shows an unsuccessful validation:

@Test
public void testInvalidCountryCode() {
        
    // a country code must be two-character long
    Map addresses = new HashMap<String,Address>() {{ 
              put("ADR-1", Address.builder().countryCode("ita").build()); }};
    Card jsCard = Card.builder()
                .uid(getUUID())
                .addresses(addresses)
                .build();
    assertTrue("testInvalidCountryCode-1", !jsCard.isValid());
    assertTrue("testInvalidCountryCode-2",
              jsCard.getValidationMessage().equals("invalid countryCode in Address"));
}

vCard validation

The validation of all vCard formats is supported as well. The following validation methods are available:

• JCardValidator
 - void validate(String json)
 - void validate(JsonNode jsonNode)
• VCardValidator
 - void validate(String vcf)
• XCardValidator
 - void validate(String xml)

Note: All the above methods can raise a CardException and JsonNode represents a JSON root node in the Jackson library.

Serialization/deserialization

JSContact serialization/deserialization is performed through Jackson library annotations and methods.

Serialization

Card jsCard = Card.builder.build();
String serialized = objectMapper.writeValueAsString(jsCard);
To prettyprint serialized JSContact objects, use the following:
Card jsCard = Card.builder.build();
String serialized = PrettyPrintSerializer.print(jsCard);

Deserialization

String json = "{"uid": \"c642b718-7c89-49f4-9497-d9fb279bb437\"}";
ObjectMapper objectMapper = new ObjectMapper();
Card jsCard = objectMapper.readValue(json, Card.class);

Deserialization of a CardGroup object and the related cards is performed through a custom deserializer returning a list of polymorphic objects (such as Card or CardGroup instances):

@Test
public void testDeserialization4() throws IOException {

    String json = IOUtils.toString(getClass().getClassLoader()
            .getResourceAsStream("jcard/jsCardGroup.json"), Charset.forName("UTF-8"));
    ObjectMapper objectMapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addDeserializer(JSContact.class, new JSContactListDeserializer());
    objectMapper.registerModule(module);
    JSContact[] jsContacts = objectMapper.readValue(json, JSContact[].class);
    for (JSContact jsContact : jsContacts)
        assertTrue("testDeserialization4", jsContact.isValid());
}

vCard conversion

The following conversion methods are available:

• EZVCard2JSContact
 - List<JSContact> convert(List<VCard>)
• VCard2JSContact
 - List<JSContact> convert(String vcf)
• JCard2JSContact
 - List<JSContact> convert(String json)
 - List<JSContact> convert(JsonNode jsonNode)
• XCard2JSContact
 - List<JSContact> convert(String xml)

All these methods return a list of JSContact topmost objects and can raise a CardException. VCard is the class mapping a vCard object in ez-vcard. JsonNode is as defined before.

Conversion profiles from vCard to JSContact

By default, where a collection of objects is mapped to a map of entries, the key has the following format: <vCard element name> + “-” + <index of the element among the vCard sibling elements (starting from 1)> (such as ‘ADR-1’). A different setting schema can be defined by assigning the key values based on the positions of vCard elements.

RDAP conversion profile from jCard to JSContact

A predefined conversion profile to convert a jCard instance, inside an RDAP response, is also available in RFC 9083. The values of the map keys used in such profiles are defined in the JSON Responses draft.

JSContact conversion

The following conversion methods are available:

• JSContact2EZVCard
 - List<VCard> convert(List<JSContact> jsContacts)
 - List<VCard> convert(String json)
• JSContact2VCard
 - String convertToText(JSContact jsContact)
 - String convertToText(List<JSContact> jsContacts)
• JSContact2JCard
 - String convertToJson(JSContact jsContact)
 - String convertToJson(List<JSContact> jsContacts)
 - JsonNode convertToJsonNode(JSContact jsContact)
 - JsonNode convertToJsonNode(List<JSContact> jsContacts)
• JSContact2XCard
 - String convertToXml(JSContact jsContact)
 - String convertToXml(List<JSContact> jsContacts)

All the methods take as input a list of JSContact topmost objects and can raise a CardException. VCard and JsonNode are as defined before.

What’s next?

The jscontact-tools project is still in progress as the JSContact specification hasn’t been completed yet, but it is expected to be published as an IETF RFC before the end of 2021. At present, the only open issue is about the representation of localizations.

Another possible development regards the creation of wrappers to make the conversion and validation methods easy to be invoked by other languages. Such development is connected with the availability of specific use cases. Your feedback is encouraged.

jscontact-tools is available as source or a Maven dependency, and is licensed under AGPLv3. The library includes more than 300 test cases covering all its features.

Mario Loffredo is the Deputy Chief of the Technological Unit, Digital Innovation at IIT-CNR. He is an active member of working groups at IETF and CENTR, with current interests in domain name protocol and applications, cybersecurity, and web technologies.

Rate this article

The views expressed by the authors of this blog are their own and do not necessarily reflect the views of APNIC. Please note a Code of Conduct applies to this blog.

Leave a Reply

Your email address will not be published. Required fields are marked *

Top