Argo is a free, open source JSON parsing and generating library for Java.
Argo is compliant with RFC 8259. It works with Java 5 onwards, and includes JPMS module information for Java 9 onwards. Argo has no dependencies. It’s licensed under the Apache 2 License.
The project is hosted on GitHub.
Downloads
Argo is published on Maven Central.
-
Gradle (Kotlin)
-
Gradle (Groovy)
-
Maven
dependencies {
implementation(group = "net.sourceforge.argo", name = "argo", version = "7.6")
}
dependencies {
implemenation group: 'net.sourceforge.argo', name: 'argo', version: '7.6'
}
<dependency>
<groupId>net.sourceforge.argo</groupId>
<artifactId>argo</artifactId>
<version>7.6</version>
</dependency>
Usage
Let’s generate a JSON document to represent a blog entry.
String blogEntry = new JsonGenerator().generate(
object( (1)
field("title", string("How to use Argo")),
field("version", number(1))
)
);
1 | Static factory methods in argo.jdom.JsonNodeFactories model JSON |
Our blog entry JSON looks like this:
{
"title": "How to use Argo",
"version": 1
}
To retrieve the title from the blog entry JSON we produced, we parse it, and then extract the value of the title
field.
String title = new JsonParser().parse(blogEntry).getStringValue("title");
Streaming
Suppose we want to add an array of comments retrieved from a data source to our blog entry JSON. To avoid reading all the comments into memory at once, and to begin outputting JSON text as early as possible, we can use streaming.
Iterable<String> comments = queryComments(); (1)
StringWriter stringWriter = new StringWriter(); (2)
new JsonGenerator().generate(stringWriter, (WriteableJsonObject) objectWriter -> {
objectWriter.writeField("title", string("How to use Argo")); (3)
objectWriter.writeField("version", number(2));
objectWriter.writeField("comments", (WriteableJsonArray) arrayWriter -> {
for (String comment : comments) { (4)
arrayWriter.writeElement(string(comment));
}
});
});
1 | Get a source of comments |
2 | A StringWriter for demonstration purposes, but could be any Writer |
3 | At this point, the StringWriter has already received:
|
4 | The Iterable of comments can be unbounded; our code only keeps one comment in memory at a time |
To retrieve the comments from the JSON we just made without having to hold it all in memory, we can use streaming again.
int commentCount = 0;
StringReader stringReader = new StringReader(stringWriter.toString()); (1)
Deque<String> stack = new ArrayDeque<>(); (2)
Iterator<JsonStreamElement> jsonIterator = new JsonParser().parseStreaming(stringReader);
while (jsonIterator.hasNext()) {
JsonStreamElement next = jsonIterator.next();
switch (next.jsonStreamElementType()) {
case START_FIELD:
StringWriter fieldNameWriter = new StringWriter();
try (Reader fieldNameReader = next.reader()) { (3)
fieldNameReader.transferTo(fieldNameWriter);
}
stack.push(fieldNameWriter.toString());
break;
case END_FIELD:
stack.pop();
break;
case STRING:
if (List.of("comments").equals(List.copyOf(stack))) {
commentCount++;
}
break;
default:
// ignore other element types
}
}
1 | A StringReader for demonstration purposes, but could be any Reader |
2 | The stack will keep track of the parents of the current element |
3 | Nodes' text is also streamed, so we can have unbounded strings, numbers, and field names |
Performance tuning
Optional whitespace
The computational cost of parsing a JSON document correlates well with its length. Reducing or eliminating optional whitespace from input JSON reduces its size and results in a roughly proportional reduction in parsing cost.
Similarly, generating JSON with optional whitespace included incurs a processing cost. By default, Argo generates JSON with optional whitespace to improve readability. If performance is a greater concern than readability, optional whitespace can be switched off:
JsonGenerator jsonGenerator = new JsonGenerator().style(JsonGeneratorStyle.COMPACT);
Buffering
Argo does not add its own buffering to a Reader
it parses from, or to a Writer
it generates to.
Parsing from an unbuffered Reader
may be improved by wrapping it in an appropriately sized BufferedReader
if the underlying Reader
performs poorly when reading single characters and small arrays of characters.
Likewise, generating JSON to an unbuffered Writer
might be improved by wrapping it in a BufferedWriter
if the underlying Writer
is unsuited to single character and short String
writes.
Position tracking
By default, Argo keeps track of the line and column it’s currently parsing, so that it can specify the position of syntax errors. Keeping track of position has a computational cost, but it can be switched off, in exchange for less informative error messages:
JsonParser jsonParser = new JsonParser().positionTracking(PositionTracking.DO_NOT_TRACK);
Node interning
When parsing a document without streaming, if Argo encounters a string or number equal to one it has previously encountered in the same document, by default it will use the same object for them. This strategy trades a reduction in memory for a small increase in computational cost. If the input is known to contain very little repetition, or if computational cost is prioritised over memory efficiency, it might be effective to disable object reuse:
JsonParser jsonParser = new JsonParser().nodeInterning(NodeInterningStrategy.INTERN_NOTHING);
Bear in mind that disabling object reuse will result in more object instantiation, which itself has a parse-time cost, and a garbage collection cost.
Further details
In-depth details of the API are available in the online javadoc.
Limitations
parse | streaming parse | generate | streaming generate | |
---|---|---|---|---|
Characters in a string |
|
Unlimited |
|
Unlimited |
Characters in a number |
|
Unlimited |
|
Unlimited |
Elements in an array |
|
Unlimited |
|
Unlimited |
Fields in an object |
|
Unlimited |
|
Unlimited |
Nesting depth |
|
|
Limited by JVM stack (>2900 using 1MB stack) |
Limited by JVM stack (>4000 using 1MB stack) |