Sunday, June 13, 2010

JSOG - JavaScript Object Graph

So I had a project where I was creating a user preferences service. Because the environment in which the preferences service was to be operating is so chaotic (lots of changes are expected), I decided to use JSON as a way to store the user preferences. The service is a glorified document database, if you will.

Now, working with JavaScript objects and arrays in JavaScript is pretty easy. There's a lot of implicit coercion taking place, it's a dynamic language after all. That doesn't translate to Java very well, however. I looked at JSON-lib, FlexJSON, Jackson, and the json.org library. None of them provided the functionality I was looking for. I want to easily navigate a JSON object graph, and manipulate the values within.

Let's look at my particular use case for example. I had a JSON object for a user. Within that JSON object, I basically had the following: (Formatted for readability)


{
"services": {
"xyz": {
// XYZ specific preferences
}
}
}


Except since things are so chaotic, I don't know if the "services" node exists, or if "xyz" exists.

I'll demonstrate with Jackson here, but the process is basically the same for the other libraries I looked at.



In this example, we create the object from scratch:

ObjectNode prefs = nf.objectNode();

ObjectNode services = nf.objectNode();
prefs.put("services", services);

ObjectNode xyz = nf.objectNode();
services.put("xyz", xyz);

xyz.put("foo", "bar");


What does that same code look like in JSOG?

JSOG jsog = JSOG.createObjectNode();
jsog
.get("services")
.get("xyz")
.put("foo", "bar");


A little more straight forward, but Jackson wasn't that bad. What about a more complex example?



Instead of creating everything from scratch, what if we need to parse the existing preferences and add a value? If the JSON object doesn't contain "services", or "xyz" (which we're not sure of), we need to create those nodes.

Let's parse a string, empty in this case, and build up the same object, and add a value to "xyz" called "baz" with value 42.

Here's what you're facing with Jackson:

ObjectMapper om = new ObjectMapper();
JsonNodeFactory nf = om.getNodeFactory();

// Parse the JSON string
JsonNode json = om.readTree("");

// Get or create the prefs container object
ObjectNode prefs;
if (json.isObject()) {
prefs = (ObjectNode) json;
} else {
prefs = nf.objectNode();
json = prefs;
}

// Get or create the services object
JsonNode servicesNode = prefs.path("services");
ObjectNode services;
if (servicesNode.isObject()) {
services = (ObjectNode) servicesNode;
} else {
services = nf.objectNode();
prefs.put("services", services);
}

// Get or create the xyz object
JsonNode xyzNode = services.path("xyz");
ObjectNode xyz;
if (xyzNode.isObject()) {
xyz = (ObjectNode) xyzNode;
} else {
xyz = nf.objectNode();
prefs.put("xyz", xyz);
}

// Set the value
xyz.put("baz", 42);


And here's JSOG doing the same thing:

JSOG jsog = JSOG.parse("");
jsog
.get("services")
.get("xyz")
.put("baz", 42);


Now we're talking! JSOG pulls off in 5 clean, clear, concise lines of code what took Jackson 38 LOC to do. JSOG does the implicit object creation or coercion for you, and to make things even easier, you get builder-style syntax!

I want to point out that I'm not picking on Jackson here, this is a failing of all the libraries I looked at. I think Jackson is actually one of the easier libraries to use, which is why I chose to use it for JSOG's serialization and deserialization.

JSOG is Unlicensed. I've committed it to the public domain. I would love to see JSOG or an equivalent find it's way into each JSON library.

Visit the JSOG site, hosted on SourceForge.net and give it a whirl. I suspect people working with CouchDB will *really* appreciate JSOG.

2 comments:

cowtowncoder said...

Thank you for sharing this! Many good ideas, it is good to see people working to improve existing tools, try out new approaches.

Jeff Rodriguez said...

@cowtowncoder

JSOG has a *lot* more features now, they're just not very well documented. I need to get on that ;)

Perhaps another blog post in the mean time..