SURF Format

JSON replacement for rich, readable data interchange.
SURF
Simple URF
URF
Uniform Resource Framework
https://urf.io/surf/

SURF Serializes Resources

Categories of SURF resource serializations:
  • literals
  • objects
  • collections

But why SURF?

Isn't JSON simple and easy?

{
  "name": "Jane Doe",
  "id": "bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832",
  "email": "jane_doe@example.com",
  "phone": "+12015550123",
  "usernames": ["jdoe", "janed"],
  "homePage": "http://www.example.com/jdoe/",
  "joined": "2016-01-23",
  "balance": 0.90,
  "charge": 0.30
}

Isn't JSON good enough?

Want a date?

As of 2011, there are some de facto standards, e.g., converting from Date to String, but none universally recognized.

JSON, Wikipedia, retrieved 2017-05-14.

JSON Dates in the Wild

  • "\"\\/Date(1335205592410)\\/\""
  • "\"\\/Date(1335205592410-0500)\\/\""
  • "2012-04-23T18:25:43.511Z"
  • "@1335205592410@"
The “right” JSON date format, Stack Overflow. / Bertrand Le Roy.

But these are still all JSON strings, not dates!

How can one distinguish a date string from a string string? And what about local dates? Times? Durations?

Have a Date @ SURF

{
  "name": "Jane Doe",
  "id": "bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832",
  "email": "jane_doe@example.com",
  "phone": "+12015550123",
  "usernames": ["jdoe", "janed"],
  "homePage": "http://www.example.com/jdoe/",
  "joined": @2016-01-23,
  "balance": 0.90,
  "charge": 0.30
}

SURF Likes Dates!

SURF provides over a dozen date/time/duration types.
  • Instant: @2017-02-12T23:29:18.829Z
  • ZonedDateTime: @2017-02-12T15:29:18.829-08:00[America/Los_Angeles]
  • LocalDate: @2017-02-12
  • LocaleTime: @15:29:18.829
  • Year: @2017

Aren't JSON numbers OK?

Calculating a JSON balance…

{"balance": 0.90, "charge": 0.30}

…using Java

double balance = 0.90;  //starting account balance of $0.90
double charge = 0.30;  //monthly charge of $0.30
while(balance > 0) {
  balance -= charge;  //subtract the charge each month
  System.out.println("balance: " + balance);
}
balance: 0.6000000000000001
balance: 0.3000000000000001
balance: 1.1102230246251565E-16
balance: -0.2999999999999999

Never use IEEE 754 floating point numbers to represent money!

But that's all JSON has!

Should you put money in JSON strings instead?

Should you store integer cents as JSON numbers?

SURF Has a $ Decimal Type

{
  "name": "Jane Doe",
  "id": "bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832",
  "email": "jane_doe@example.com",
  "phone": "+12015550123",
  "usernames": ["jdoe", "janed"],
  "homePage": "http://www.example.com/jdoe/",
  "joined": @2016-01-23,
  "balance": $0.90,
  "charge": $0.30
}
Native language support: Java (BigDecimal), C#, Python, Ruby, …

SURF Has Lots of Types

  • Regular Expression: /a?b+c*/
  • IRI/URI/URL/URN: <http://example.com/>
  • UUID: &5623962b-22b1-4680-ae1c-7174a46144fc
  • Email Address: ^jdoe@example.com
  • Telephone Number: +12015550123
  • Binary: %Zm9vYmFy

Rich Types with SURF

{
  "name": "Jane Doe",
  "id": &bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832,
  "email": ^jane_doe@example.com,
  "phone": +12015550123,
  "usernames": ["jdoe", "janed"],
  "homePage": <http://www.example.com/jdoe/>,
  "joined": @2016-01-23,
  "balance": $0.90,
  "charge": $0.30
}

Look Mom, no commas!

{
  "name": "Jane Doe"
  "id": &bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832
  "email": ^jane_doe@example.com
  "phone": +12015550123
  "usernames": [
    "jdoe"
    "janed"
  ]
  "homePage": <http://www.example.com/jdoe/>
  "joined": @2016-01-23
}

Compact with Commas

{"name":"Jane Doe","id":&bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832,
"email":^jane_doe@example.com,"phone":+12015550123,"usernames":
["jdoe","janed"],"homePage":<http://www.example.com/jdoe/>,
"joined":@2016-01-23,"balance":$0.90,"charge": $0.30}

Clearer with ! Comments

{
  "name": "Jane Doe"
  "id": &bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832
  "email": ^jane_doe@example.com
  "phone": +12015550123
  "usernames": ["jdoe", "janed"]
  "homePage": <http://www.example.com/jdoe/>
  "joined": @2016-01-23
  "balance": $0.90  !current account balance
  "charge": $0.30  !monthly charge
}

JSON objects aren't really.

JSON objects are maps.

{
  "name": "Jane Doe",
  "id": "bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832",
  "email": "jane_doe@example.com",
  "phone": "+12015550123",
  "usernames": ["jdoe", "janed"],
  "homePage": "http://www.example.com/jdoe/",
  "joined": "2016-01-23",
  "balance": 0.90,
  "charge": 0.30
}

JSON only allows string for map keys.

SURF Has Real Objects

A SURF object looks like this:

*

Think: An object instance.

A SURF object can have real property assignments.

*:
  name = "Jane Doe"
;

A SURF object can have a type.

*User:
  name = "Jane Doe"
;

Think: An instance of User described as follows: …

SURF requires * for every object instance.

The separate TURF format allows object types themselves to be described, e.g. User:…;.

A Real User Object in SURF

*User:
  name = "Jane Doe"
  id = &bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832
  email = ^jane_doe@example.com
  phone = +12015550123
  usernames = ["jdoe", "janed"]
  homePage = <http://www.example.com/jdoe/>
  joined = @2016-01-23
  balance = $0.90
  charge = $0.30
;

SURF Has Collections

  • list
  • set
  • map

SURF parsers map collections to native programming language implementations.

Perfect fit for Java collection types!

Even JavaScript has real map and set types nowadays.

Lists Like You Like

*Rainbow:
  colors = ["red", "orange", "yellow", "green",
      "blue", "indigo", "violet"]
;

Just like JSON.

Maps Make a Return

*Person:
 favoriteThings = {
    5: "This person's favorite number."
    "aliquot": "This person's favorite word."
  }
;

Just like JSON… but without key type restrictions.

SURF Sets Make Sense

*User:
  name = "Jane Doe"
  id = &bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832
  email = ^jane_doe@example.com
  phone = +12015550123
  usernames = ("jdoe", "janed")
  homePage = <http://www.example.com/jdoe/>
  joined = @2016-01-23
  balance = $0.90
  charge = $0.30
;

Here a set makes more semantic sense for the usernames because it is unique and unordered.

Reference Resources with SURF Labels: Aliases and Tags

SURF Alias |foo| for Internal Referencing

*Game:
  players = [
    |doe|*User:
      name = "Jane Doe"
    ;
    |smith|*User:
      name = "John Smith"
    ;
  ]
  winner = |doe| !reference an existing instance
;

Tag |<IRI>| for External Referencing

|<urn:uuid:bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832>|*User:
  name = "Jane Doe"
  id = &bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832
  email = ^jane_doe@example.com
  phone = +12015550123
  usernames = ("jdoe", "janed")
  homePage = <http://www.example.com/jdoe/>
  joined = @2016-01-23
  balance = $0.90
  charge = $0.30
;

Short form: |<&bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832>|

Reference Tags, Too

*Game:
  players = [
    |<&bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832>|*User:
      name = "Jane Doe"
    ;
    |<&d5207ff8-f64e-46b9-8fd9-d84ce8e0ff29>|*User:
      name = "John Doe"
    ;
  ]
  winner = |<&bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832>|
;

Tags are accessible even outside of SURF documents.

SURF Names

Type names should use upper camelCase, e.g. User
Property names should use lower camelCase, e.g. homePage

SURF Namespaces (Optional)

Prevent name clashes by with namespace prefixes.

chem-salt Salt (chemistry)
*:chem-salt="monosodium glutamate";
crypto-salt Salt (cryptography)
*:crypto-salt=%Zm9vYmFy;

A SURF name with namespace prefix is a handle.

No namespace declarations in SURF.

SURF parsers need no knowledge of namespaces.

Mix Vocabularies Using Namespaces

|<&bb8e7dbe-f0b4-4d94-a1cf-46ed0e920832>|*example-User:
  name = "Jane Doe"
  email = ^jane_doe@example.com
  crypto-salt=%Zm9vYmFy
;

When creating a namespace, try to use an identifier from a domain or second-level domain you control.

The original version of this presentation was entitled From JSON to SURF in Java and was first shown at SeaJUG on 2017-05-16.

Thanks to Nimret Sandhu and all the members of SeaJUG for the opportunity and hospitality.