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:
- Card: Storing information about a person, organization, or a company.
- CardGroup: Representing a group of cards.
Other data types have been introduced to define the Card properties:
NameComponent | A component of the entity name |
Address | A physical location of the entity |
StreetComponent | A street address component (such as name, number, building, floor) |
Phone | A phone number to contact the entity |
EmailAddress | An email address to contact the entity |
Resource | An online resource (such as a web URL or a social media account) of the entity |
File | A photograph or an image of the entity |
ContactLanguage | A language for contacting the entity |
Organization | Company or organization names, and units of the entity |
Title | A job title or functional position of the entity |
Relation | The relationships between the entity and other entities |
Anniversary | A memorable date or event for the entity |
PersonalInformation | Personal 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.
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.