json

Java JSON, CBOR, Msgpack, JWT, COSE toolkit

The BFO JSON package is yet another Java JSON parser. It supports JSR353 / JSR374 (both the javax.json and the jakarta.json packages), and also has a custom API which adds support for Msgpack, CBOR and CBOR-diag (the com.bfo.json package).

simple

fast

streaming (new in v2)

correct

self-contained

Features

Building and Documentation

Design Decisions

Examples

// The basics
Json json = Json.read("{}"}; // Create a new map
json.put("a", "apples"); // Add a string
json.putPath("b.c", new Json("oranges")); // Add an intermediate map and another string
json.putPath("b.c[1]", 3}; // Replace previous string with an array, add a null then a number.
json.putPath("\"d.e\"", true); // Add a key containing a quote character
System.out.println(json); // {"a":"apples","b":{"c":[null,3]},"d.e":true}
json.write(System.out); // The same as above, but doesn't serialize to a String first.
json.write(new JsonWriter().setOutput(System.out)); // Same as above, for setting writer options
System.out.println(json.get("b").get("c").get(1).stringValue()); // "3"
System.out.println(json.get("b").get("c").get(1).intValue()); // 3
System.out.println(json.getPath("b.c[1]").stringValue()); // "3" - same as above but using path
System.out.println(json.getPath("b.c[1]").intValue()); // 3 - same as above but using path

// Types
json.put("d", "2");
json.put("e", 0);
System.out.println(json.type()); // "map"
System.out.println(json.isMap()); // true
System.out.println(json.get("d")); // "2"
System.out.println(json.get("d").type()); // "string"
System.out.println(json.get("d").intValue()); // 2 (automatically converted from string)
System.out.println(json.get("d").numberValue().getClass()); // java.lang.Integer
json.put("d", "9999999999");
System.out.println(json.get("d").numberValue().getClass()); // java.lang.Long
json.put("d", "99999999999999999999999999");
System.out.println(json.get("d").numberValue().getClass()); // java.math.BigInteger
System.out.println(new Json(2).equals(new Json(2.0)));  // true - numbers compare by value not type
System.out.println(json.get("d").booleanValue()); // true
System.out.println(json.get("e").booleanValue()); // false
json.put("e", Json.read("[]")); // Add a new list
System.out.println(json.get("e").type()); // "list"
json.putPath("e[0]", false);
System.out.println(json.get("e")); // [false] - e is a list
json.putPath("e[\"a\"]", true); // this will convert e to a map
System.out.println(json.get("e")); // {"0":false,"a":true}
json.setValue(new Json("string")); // copy value from specified object
System.out.println(json.value()); // "string"

// Serialization
String input = "{\"b\":1, /*comment*/ \"a\": 2}";
json = Json.read(input);  // Fails, comments not allowed
json = Json.read(new JsonReader().setInput(new StringReader(input)).setComments(true)); // OK
json.write(new JsonWriter().setOutput(...).setIndent(2).setSorted(true)); // pretty print and sort keys
// {
//   "a": 2,
//   "b": 1,
// }

// CBOR / Msgpack
json.put("buffer", ByteBuffer.wrap(new byte[] { ... }));   // add a ByteBuffer
System.out.println(json.get("buffer").type());      // "buffer"
System.out.println(json.get("buffer").stringValue());  // Base64 encoded value of buffer
json.setTag(20);        // Set a CBOR tag on a value
json.write(new CborWriter().setOutput(outputstream));    // serialize the same JSON object to CBOR
json = Json.read(new CborReader().setInput(inputstream));   // read CBOR from an Inputream
json = Json.readCbor(inputstream);   // shortcut for the line above
System.out.println(json.get("buffer").getTag());        // "20"
System.out.println(json.get("b").getTag());        // "-1", which means no tag
json.put("nan", Double.NaN);
json.write(new CborWriter().setOutput(outputstream));    // infinity is fine in CBOR
json.write(new JsonWriter().setOutput(writer));    // throws IOException - infinity not allowed in Json
json.write(new JsonWriter().setOutput(writer).setAllowNaN(true));  // infinity serializes as null
json.write(new MsgpackWriter().setOutput(outputstream));    // Msgpack instead of CBOR


// Events
json.addListener(new JsonListener() {
    public void jsonEvent(Json root, JsonEvent event) {
        if (event.after == null) {
            System.out.println("Removed " + root.find(event.before));
        } else if (event.before == null) {
            System.out.println("Added " + root.find(event.after));
        } else {
            System.out.println("Changed " + root.find(event.after) + " from " + event.before+" to " + event.after);
        }
    }
});

// Paths as keys
json.putPath("a.b", true);  // "Added a.b"
json.getPath("a.b").put("c", true);  // "Added a.b.c"
json.getPath("a.b").put("c", false);  // "Changed a.b.c" from true to false
json.removePath("a.b"); // "Removed a.b"

// Conversion to/from JSR374 - the representations are independent, not shared
javax.json.JsonValue jsrvalue = javax.json.Json.createReader(...).readValue(); // object read via JSR374
bfovalue = new Json(jsrvalue);   // convert from JSR374 to BFO
jsrvalue = javax.json.Json.createObjectBuilder(bfovalue.mapValue()).build(); // convert from BFO to JSR374

// Factories for type conversion are simple
JsonFactory factory = new JsonFactory() {
    public Json toJson(Object o) {
        if (o instanceof URL) {
            return "[url] " + o;
        }
        return null;
    }
    public Object fromJson(Json o) {
        if (o.isString() && o.startsWith("[url] ")) {
            return new URL(o.stringValue().substring(6));
        }
        return null;
    }
};
Json j = new Json(new URL(...), factory).toString(); // "[url] ..."
Json j = Json.read("[url] ...");    // read as normal
j.setFactory(factory);              // ... then set factory on the tree.
j.object();                         // ... factory ensures object is a URL.
new Json(Collections.<String,URL>singletonMap(key, url), factory); // collections work with factories too

JWK and COSE

In addition the JWT class adds basic support for Java Web Tokens. With only a handful of methods it is trivial to use, but supports all JWT signing methods. Encryption with JWE is not supported

JWT jwt = new JWT(Json.parse("{\"iss\":\"foo\"}"));
byte[] secretkeydata = ...
SecretKey key = new JWK(secretkeydata, "HS256").getSecretKey();
jwt.sign(key);                       // Sign using a symmetric key
jwt = new JWT(jwt.toString());       // Encode then decode
assert jwt.verify(key);              // Verify using the same symmetric key

PublicKey pubkey = ...
PrivateKey prikey = ...
jwt.getHeader().put("x5u", ...);     // Add custom content to header
jwt.sign(prikey);                    // Sign using a assymmetric key
assert jwt.verify(pubkey);           // Verify using corresponding key

jwt.getPayload().clear();            // Modify the payload
assert !jwt.verify(pubkey);          // Signature is no longer valid

COSE was added in version 5. Signing with single or multiple signatures are supported, but counter-signing (for timestamps) is pending and encryption support is not currently planned.

// Signing
COSE cose = new COSE();
cose.setPayload(ByteBuffer.wrap("Hello, World".getBytes("UTF-8")));
cose.sign(privateKey);               // Sign using a private key, eg ES256
cose.writeCBOR(..., null);           // Write COSE to stream, or...
ByteBuffer buf = cose.toCbor();      // Write COSE to ByteBuffer

// Verifying
Json json = Json.readCBOR(..., null);     // Reload COSE
cose = new COSE(json);
String s = new String(cose.getPayload().array(), "UTF-8"); // Hello, World
assert jwt.verify(publicKey) == 0;   // Verify with the public key

For both JWT and COSE, the JWK utility class can convert between the Java PublicKey, PrivateKey and SecretKey implementations and their JWK or COSE-key representations.

EdDSA requires Java 15+, ML-DSA requires Java 24+ or BouncyCastle 1.79+. However these are handled with reflection so the library can be compiled and run on Java 11 or later without any problems; the new key types will throw an error if they’re unsupported when encountered.

Related projects