A semi-advanced Java data serialization library with features for reducing file sizes.
BJSL is fully documented at bjsl.kaleko.dev/docs
Parsers are how you transform text or binary data in a specific format into workable object trees.
There are currently 3 supported formats.
These can be accessed using {format}Parser.Builder
and can be configured if needed.
It is also entirely possible to write your own parser that fits your needs.
The ObjectProcessor
is the heart of BJSL.
It takes in object trees and transforms them into standard java objects and then transforms them back into object trees.
To create an object processor construct a new ObjectProcessor.Builder
. This will allow you to access a few options before building the processor.
-
ignoreNulls - Ignore nulls allows you to not have null values output into the object tree when serializing maps and objects.
-
ignoreArrayNulls - Ignore array nulls allows you to not have null values output into the object tree when serializing lists and arrays.
-
ignoreEmptyObjects - Ignore empty objects allow you to not have objects with a size of zero (maps, lists, arrays) output into the object tree.
-
ignoreDefaults - Ignore defaults allows you to not have default values output into the object tree.
This works by creating a new instance of the config type to read from. This requires a 0-args constructor to be present on the type. -
caseSensitiveEnums - Case sensitive enums allows you to enable enums to be case-sensitive.
When you are done call #build()
and you will have your ObjectProcessor
.
You start with a ParsedElement
of some kind (ParsedObject
, ParsedArray
), likely attained from a Parser
, and a class you would like to deserialize it to.
An example class could be something like:
public class User {
private double foo;
private double bar = 17.8;
public double getFoo() {
return foo;
}
public double getBar() {
return bar;
}
}
Once you have both just call #toObject(element, class)
You are then free to modify the returned value.
When you would like to re-serialize it call #toElement(object)
The only notable limitation of the ObjectProcessor
is that Map keys must be translated to a ParsedPrimitive
type. This can be done using TypeProcessor
s, just output some type of ParsedPrimitive
. This is already done for all default types, see below.
TypeProcessor
s are extensions of the ObjectProcessor
that allow you to serialize and deserialize any class in whatever way you see fit.
An example used in the default processors is as follows:
TypeProcessor uuidTypeProcessor = new TypeProcessor() {
@Override
public @NotNull ParsedElement toElement(@Nullable Object object) {
if (object == null) {
return ParsedPrimitive.fromNull();
}
if (object instanceof UUID) {
return ParsedPrimitive.fromString(object.toString());
} else {
throw new InvalidParameterException("object must be UUID");
}
}
@Override
public @Nullable Object toObject(@NotNull ParsedElement element) {
if (element.isPrimitive() && element.asPrimitive().isNull()) {
return null;
}
if (element.isPrimitive() && element.asPrimitive().isString()) {
return UUID.fromString(element.asPrimitive().asString());
} else {
throw new InvalidParameterException("object must be String");
}
}
};
These are registered when building an ObjectProcessor
with #createTypeProcessor(class, typeProcessor)
There is also a list of default type processors that can be toggled using #setEnableDefaultTypeProcessors(bool)
This includes the following:
java.util.UUID
java.net.URI
java.net.URL
java.nio.Path
java.io.File
java.net.InetAddress
java.net.InetSocketAddress
java.util.Calendar
java.util.Date
java.time.Instant
If you think of something not on this list that you think should be feel free to open an issue.
There are a couple of annotation types that can be used on serialized fields.
- @AlwaysSerialize - Always serialize this field, even if it is marked transient or to be excluded by ignores.
- @DontSerialize - Never serialize this field, does the same thing as marking the field as transient.
- @Rename - Rename a field to this value when outputting and from this when inputting. (This does not convert old data to match, intended use is for renaming a java field and not updating data)
There are also a few values that can be used to require certain conditions on deserialized values.
- @ExpectNotNull - Expect that a value is never null
- @ExpectGreaterThan - Expect that a value is greater than (or equal to) this
- @ExpectLessThan - Expect that a value is less than (or equal to) this
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import io.github.kale_ko.bjsl.elements.ParsedElement;
import io.github.kale_ko.bjsl.parsers.JsonParser;
import io.github.kale_ko.bjsl.processor.ObjectProcessor;
public class Data {
public static class User {
private double foo; // Fields can be any visibility
private double bar = 17.8;
public double getFoo() {
return foo;
}
public double getBar() {
return bar;
}
}
public Map<UUID, User> exampleUsers = new HashMap<>(); // Maps, Lists, and other Collections are supported
protected String exampleString = "hello world!"; // All primitive types including arrays are supported
@NeverSerialize
protected String excludedString = "goodbye world :("; // This won't get included in the output because it is marked to never serialize
public static void main(String[] args) throws IOException {
JsonParser parser = new JsonParser.Builder().build(); // Create the parser with default options
ObjectProcessor processor = new ObjectProcessor.Builder().build(); // Create the processor with default options
Path inputFile = Path.of("input.json"); // Define input and output files
Path outputFile = Path.of("output.json");
String inputData;
if (Files.exists(inputFile)) {
inputData = Files.readString(inputFile); // Read in the data
} else {
inputData = parser.emptyString(); // Get a parser specific empty data string (e.g. {} for json)
}
ParsedElement inputElement = parser.toElement(inputData); // Turn the data into an element tree
Data data = processor.toObject(inputElement, Data.class); // Turn the element tree into a Data object
// Modify data
ParsedElement outputElement = processor.toElement(data); // Turn the Data object into an element tree
String outputData = parser.toString(outputElement); // Turn the element tree into a string
Files.writeString(outputFile, outputData); // Write out the data
}
}