A library to aid in the abstraction of encoding/decoding objects to/from multiple data formats.
-
How do I import the library?
The library is published on Maven, and should be added using Gradle.
repositories { maven { name = "vini2003.dev" url = "https://maven.vini2003.dev/" } } dependencies { implementation 'dev.vini2003:blueprint:0.1.12' }Default parsers are available for the following environments:
-
Fabric (BufParser, NbtParser, JsonParser)
implementation 'dev.vini2003:blueprint-fabric:0.1.12' -
Paper (BufParser, NbtParser, JsonParser)
implementation 'dev.vini2003:blueprint-paper:0.1.12' -
GSON (JsonParser)
implementation 'dev.vini2003:blueprint-gson:0.1.12' -
Netty (BufParser)
implementation 'dev.vini2003:blueprint-netty:0.1.12'
-
-
How do I use the library?
The library allows encoding/decoding objects through what are called Blueprints. A Blueprint describes how an object should be encoded and decoded in an abstract way, utilizing a Serializer or a Deserializer.
The submodules of the library provide Serializer and Deserializer implementations for varying formats.
- How does a Blueprint work?
Blueprints, by default, have a key, getter and a setter.
-
The
keyis responsible for the name of the property when encoding/decoding. Can be set usingBlueprint#key. The existing blueprint is not mutated. -
The
getteris responsible for retrieving the value of the property from the source object when encoding. Can be set usingBlueprint#getter. The existing blueprint is not mutated. -
The
setteris responsible for setting the value of the property on the destination object when decoding. Can be set usingBlueprint#setter. The existing blueprint is not mutated. -
The
keycan be set usingBlueprint#key(...). -
The
gettercan be set usingBlueprint#getter(...). -
The
settercan be set usingBlueprint#setter(...). -
The
getterand thesettercan be set at the same time usingBlueprint#field(...).
There are also multiple types of blueprints, which can be used to better represent data types:
- CompoundBlueprints are blueprints used to describe an arbitrary object with up to 12 fields. They are most useful when mapping an external object, since they allow complete control over how each field is encoded/decoded.
- CompoundBlueprints take a list of Blueprints as their constructor parameter, and require a constructor with the same number of parameters, where the decoded values are mapped to an object.
- Can be created with
Blueprint#compound(...), with up to 12Blueprintparameters, which are the blueprints used to encode/decode each field.
- MapBlueprints are blueprints used to describe a
Map<K, V>, whereKis the key type andVis the value type. - CollectionBlueprints are blueprints used to describe a
Collection<T>, whereTis the value type.- Can be created with
Blueprint#list(Blueprint<T> valueBlueprint), wherevalueBlueprintis the blueprint used to encode/decode the values. - Can be created with
Blueprint#set(Blueprint<T> valueBlueprint), wherevalueBlueprintis the blueprint used to encode/decode the values. - Can be created with
Blueprint#queue(Blueprint<T> valueBlueprint), wherevalueBlueprintis the blueprint used to encode/decode the values. - Can also be created by calling
Blueprint#list()on an existing blueprint. - Can also be created by calling
Blueprint#set()on an existing blueprint. - Can also be created by calling
Blueprint#queue()on an existing blueprint.
- Can be created with
- PairBlueprints are blueprints used to describe a
Pair<T, U>, whereTis the first value type andUis the second value type.- Can be created with
Blueprint#pair(Blueprint<T> firstBlueprint, Blueprint<U> secondBlueprint), wherefirstBlueprintandsecondBlueprintare the blueprints used to encode/decode the elements.
- Can be created with
- OptionalBlueprints are blueprints used to describe an
Optional<T>, whereTis the value type.- Can be created with
Blueprint#optional(Blueprint<T> valueBlueprint), wherevalueBlueprintis the blueprint used to encode/decode the value. - Can also be created by calling
Blueprint#optional()on an existing blueprint.
- Can be created with
- GeneratedBlueprints are blueprints used to describe an arbitrary object with any number of fields, and are generated automatically. However, they require that a blueprint already exists for fields in the object, or that one can be created using existing blueprints, and that a getter and/or a setter is present for the fields it will encompass - fields without them are ignored. This process is recursive.
- GeneratedBlueprints are generated by annotating a class with
@Blueprintable, and accessed by usingBlueprint#of(T t)/Blueprint#of(Class<T> clazz).
- GeneratedBlueprints are generated by annotating a class with
- Custom blueprints can be registered using
Blueprint#register(Class<T>, Blueprint). - Custom blueprints can be created by
xmap'ing existing blueprints. A blueprint for a UUID can be created as follows:-
The existing blueprint is not mutated.
public static final Blueprint<UUID> UUID = Blueprint.STRING.xmap(UUID::fromString, UUID::toString);
-
- One default blueprint for a class can be registered by annotating one static field
static Blueprint<T>(whereTis the current class) with@DefaultBlueprint, if one cannot be generated automatically.
For example, in order to serialize the following object:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}The following Blueprint should be used:
public static final Blueprint<Person> BLUEPRINT = Blueprint.compound(
Blueprint.STRING.key("name").getter(Person::getName).setter(Person::setName),
Blueprint.INTEGER.key("age").getter(Person::getAge).setter(Person::setAge),
Person::new
);However, since its fields have existing associated blueprints, or can have a blueprint created using existing blueprints, the class may be annotated with @Blueprintable, and a blueprint for it will automatically be generated, and can be obtained using Blueprint#ofValue/Blueprint#ofClass.
For example, in order to demonstrate automatic generation of a blueprint, follow the code snippets below.
Firstly, we create a Job and a Person class. The Job class has a String name and an int wage, and the Person class has a String name, int age, and a Job job. We annotate both with @Blueprintable.
@Blueprintable
public static class Job {
private String name;
private int wage;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getWage() {
return wage;
}
public void setWage(int wage) {
this.wage = wage;
}
}
@Blueprintable
public static class Person {
private String name;
private int age;
private Job job;
public Person() {
}
public Person(String name, int age, Job job) {
this.name = name;
this.age = age;
this.job = job;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Job getJob() {
return job;
}
public void setJob(Job job) {
this.job = job;
}
}Then, we run the following code to confirm that the blueprints were generated correctly:
var job = new Job();
job.setName("Programmer");
job.setWage(3000);
System.out.println("Created Job!");
System.out.println("Job[Name[" + job.getName() + "], Wage[" + job.getWage() + "]]");
var person = new Person();
person.setName("John Doe");
person.setAge(27);
person.setJob(job);
System.out.println("Created Person");
System.out.println("Person[Name[" + person.getName() + "], Age[" + person.getAge() + "], Job[" + person.getJob() + "]]");
var personBlueprint = Blueprint.of(person);
System.out.println("Created Person Blueprint!");
System.out.println(personBlueprint);
var personJsonObject = new JsonObject();
personBlueprint.encode(JsonParser.INSTANCE, person, personJsonObject);
System.out.println("Encoded Person to JSON!");
System.out.println(personJsonObject);
person = personBlueprint.decode(JsonParser.INSTANCE, personJsonObject);
System.out.println("Decoded Person from JSON!");
System.out.println("Person[Name[" + person.getName() + "], Age[" + person.getAge() + "], Job[" + person.getJob() + "]]");By doing that, we obtain the following result:
Created Person Blueprint!
GeneratedBlueprint[None, [Blueprint[name], Blueprint[age], Blueprint[job]]]
Encoded Person to JSON!
{"name":"John Doe","age":27,"job":{"name":"Programmer","wage":3000}}
Decoded Person from JSON!
Person[Name[John Doe], Age[27], Job[null]]Therefore, serialization was successful.