Skip to content

Commit

Permalink
Merge pull request #4 from LaFriska/dev
Browse files Browse the repository at this point in the history
Finished implementation of first release
  • Loading branch information
LaFriska authored Dec 1, 2024
2 parents 9abebdd + 6f0d8eb commit ce82df1
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 138 deletions.
60 changes: 25 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,32 +84,27 @@ will print the corresponding JSON string into the console.

### Ignoring fields

In order to tell Kompakt to ignore certain fields, override `JSONSerialisable#ignoredFields()` to return
an array of field names to ignore. Using the example above, if we wish to ignore `isHappy`,
then we would write the class as follows
In order to tell Kompakt to ignore certain fields, label the fields to be ignored using the annotation `@Ignored`.
Using the example above, if we wish to ignore `isHappy`, then

```java
import com.friska.kompakt.JSONSerialisable;
import com.friska.kompakt.annotations.Ignored;

public class Person implements JSONSerialisable {

String name;
String name;

int age;
int age;

boolean isHappy;

public Person(String name, int age, boolean isHappy) {
this.name = name;
this.age = age;
this.isHappy = isHappy;
}
@Ignored
boolean isHappy;

//Ignores the "isHappy" field.
@Override
public String[] ignoredFields() {
return new String[]{"isHappy"};
}
public Person(String name, int age, boolean isHappy) {
this.name = name;
this.age = age;
this.isHappy = isHappy;
}
}
```

Expand Down Expand Up @@ -161,30 +156,26 @@ The resulting JSON is
}
```

In order to allow Kompakt to serialise inherited fields. we override
`JSONSerialisable#deepSerialise()` and return true, as follows.
In order to allow Kompakt to serialise inherited fields, we annotate the class of the object being serialised with
the annotation `@DeepSerialise`.

```java
import com.friska.kompakt.JSONSerialisable;
import com.friska.kompakt.annotations.DeepSerialise;

@DeepSerialise
public class Student extends Person implements JSONSerialisable {

private int grades;
private int grades;

public Student(String name, int age, boolean isHappy, int grades) {
super(name, age, isHappy);
this.grades = grades;
}

//Allows inherited fields to be serialised.
@Override
public boolean deepSerialise() {
return true;
}
public Student(String name, int age, boolean isHappy, int grades) {
super(name, age, isHappy);
this.grades = grades;
}
}
```

Now, the resulting JSON is
Now, the resulting JSON contains all fields.

```json
{
Expand Down Expand Up @@ -293,11 +284,10 @@ clumsy manner. However, with the override, the resulting JSON is

### Other features

Some less important yet notable features
Some less important yet notable features for serialisation:

1. `JSONSerialisable#serialiseIterablesAsArrays()`, which by default is set to
true, it serialises any fields inheriting Java's `Iterable` class into a JSON
array. To disable this feature, as usual, override it and return false.
1. Annotating a field with `@SerialiseAsString` will force Kompakt to serialise the field as a string by calling
`toString()` on the given object.
2. `JSONSerialisable#setIndentSize(int)`, which globally sets the size of an indentation
in a serialised JSON string.

Expand Down
3 changes: 1 addition & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.friska</groupId>
<artifactId>kompakt</artifactId>
<version>1.0.1-alpha</version>
<version>v1.1.0</version>

<properties>
<maven.compiler.source>23</maven.compiler.source>
Expand All @@ -15,7 +15,6 @@
</properties>

<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/com/friska/kompakt/Attribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,15 @@
*
* @param name name of the attribute.
* @param val value being stored.
* @param serialiseAsString whether this attribute should be serialised as a string when processed by
* {@link JSONSerialisable}. In most circumstances this parameter can be ignored, and the constructor with just
* its name and value as an input will by default set it to false.
*/
public record Attribute(@NotNull String name, @Nullable Object val) {
public record Attribute(@NotNull String name, @Nullable Object val, boolean serialiseAsString) {

public Attribute(@NotNull String name, @Nullable Object val){
this(name, val, false);
}

/**
* Equivalence is defined purely based on the name of two attributes, since attributes are always
Expand Down
149 changes: 65 additions & 84 deletions src/main/java/com/friska/kompakt/JSONSerialisable.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.friska.kompakt;

import java.lang.reflect.AccessFlag;
import java.lang.reflect.Field;
import com.friska.kompakt.annotations.DeepSerialise;
import com.friska.kompakt.annotations.Ignored;
import com.friska.kompakt.annotations.SerialiseAsString;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
Expand All @@ -11,27 +13,28 @@
/**
* Classes implementing this interface allows Kompakt to search through field variables and serialise them into a
* JSON string. This process is done by calling {@link JSONSerialisable#serialise()}. There are configurations available
* in the form of trivial methods with a default return value, but should be overridden if such configurations should
* be modified. Below are such configurations. (Read their documentation for more detail.)
* in the form annotations, or of methods with a default return value, but should be overridden if such configurations should
* be modified. Below are such configurations.
* <ul>
* <li>
* {@link JSONSerialisable#ignoredFields()}, returns an array of field names that should be ignored from
* serialisation.
* Ignoring fields - to ignore fields from the serialisation process, annotate the given field with {@link Ignored}
* </li>
* <li>
* Deep serialise - to serialise inherited fields, annotate the class with {@link DeepSerialise}.
* </li>
* <li>
* {@link JSONSerialisable#deepSerialise()}, returns whether fields from super classes should be serialised.
* Custom JSON attributes - to serialise the class into a custom set of attributes, override
* {@link JSONSerialisable#jsonAttributes()}.
* </li>
* <li>
* {@link JSONSerialisable#serialiseIterablesAsArrays()}, returns whether fields that inherit {@link Iterable}
* should be serialised as JSON arrays.
* Serialising an object by its toString value - this configuration may occasionally be useful, simply annotate
* a field with {@link SerialiseAsString}.
* </li>
* <li>
* {@link JSONSerialisable#jsonAttributes()}, returns a list of attributes to be serialised.
* Custom indent sizes - to customise the JSON indent size, call the static setter
* {@link JSONSerialisable#setIndentSize(int)}.
* </li>
* </ul>
* Another configuration that does not come in the form of a default non-static method is
* {@link JSONSerialisable#setIndentSize(int)}, which sets the global indent size of JSON serialisation using a single
* static setter.
*/
public interface JSONSerialisable {

Expand All @@ -51,7 +54,7 @@ static void setIndentSize(int newSize) {
* @return a list of attributes representing these fields.
*/
private static List<Attribute> fetchFieldsAsAttributes(Object obj) {
return fieldToAttributes(obj.getClass().getDeclaredFields(), obj);
return JSONUtils.fieldToAttributes(obj.getClass().getDeclaredFields(), obj);
}

/**
Expand All @@ -62,7 +65,6 @@ private static List<Attribute> fetchFieldsAsAttributes(Object obj) {
* @param omitted a set of names of omitted field variables.
* @param <T> an arbitrary type that extends {@link JSONSerialisable}.
* @return a string representation of the serialised JSON object.
* @throws IllegalAccessException
*/
private static <T extends JSONSerialisable> String serialise(T obj, int currSize, Set<String> omitted)
throws IllegalAccessException {
Expand All @@ -81,7 +83,7 @@ private static <T extends JSONSerialisable> String serialise(T obj, int currSize
Object val = attribute.val();
if (!omitted.contains(name)) {
indent(sb, currSize + JSONUtils.INDENT_SIZE, s -> s.append(wrap(name)).append(": "));
serialiseItem(currSize, val, sb, false);
serialiseItem(currSize, val, sb, false, attribute.serialiseAsString());
sb.append(",").append("\n");
}
}
Expand All @@ -92,24 +94,6 @@ private static <T extends JSONSerialisable> String serialise(T obj, int currSize
return sb.toString();
}

private static List<Attribute> fieldToAttributes(Field[] fields, Object obj) {
try {
ArrayList<Attribute> attributes = new ArrayList<>();
for (Field field : fields) {
if (!field.accessFlags().contains(AccessFlag.STATIC)) {
boolean canAccess = field.canAccess(obj);
field.setAccessible(true);
attributes.add(new Attribute(field.getName(), field.get(obj)));
field.setAccessible(canAccess);
}
}
return attributes;
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("An error occurred.");
}
}

/**
* Fetches all attributes that will be serialised into the JSON string. By default, this means every non-static
* field of an object. However, one may override {@link JSONSerialisable#jsonAttributes()} to explicitly denote
Expand All @@ -126,7 +110,7 @@ private static List<Attribute> fieldToAttributes(Field[] fields, Object obj) {
getAttributes(T obj, Class<?> clazz, List<Attribute> list, boolean getFieldDeep) {
if (!getFieldDeep) {
if (clazz.equals(obj.getClass())) list.addAll(obj.jsonAttributes());
else list.addAll(fieldToAttributes(clazz.getDeclaredFields(), obj));
else list.addAll(JSONUtils.fieldToAttributes(clazz.getDeclaredFields(), obj));
return;
}

Expand All @@ -148,48 +132,55 @@ private static List<Attribute> fieldToAttributes(Field[] fields, Object obj) {
* @param item the item to serialise.
* @param sb current string builder used in the serialisation.
*/
private static void serialiseItem(int currSize, Object item, StringBuilder sb, boolean indentAlways) {
//Recursive call
private static void serialiseItem(int currSize, Object item, StringBuilder sb, boolean indentAlways, boolean asString) {

if (item instanceof JSONSerialisable s)
sb.append(s.serialise(currSize + JSONUtils.INDENT_SIZE, s.ignoredFields()));

//Base cases
else if (item == null)
if (indentAlways)
indent(sb, currSize + JSONUtils.INDENT_SIZE, s -> s.append("null"));
else
sb.append("null");
handle(currSize, sb, indentAlways, "null");
else if (asString)
handle(currSize, sb, indentAlways, wrap(item.toString()));
else if (item instanceof Number || item instanceof Boolean)
if (indentAlways)
indent(sb, currSize + JSONUtils.INDENT_SIZE, s -> s.append(item));
else
sb.append(item);
else if (item.getClass().isArray()) {
sb.append("[").append("\n");
Object[] array = (Object[]) item;
for (int i = 0; i < array.length; i++) {
serialiseItem(currSize + JSONUtils.INDENT_SIZE, array[i], sb, true);
if (i != array.length - 1) sb.append(",");
handle(currSize, sb, indentAlways, item.toString());
else if (item.getClass().isArray())
handleArray(currSize, (Object[]) item, sb);
else if (item instanceof Iterable<?> iterable)
handleIterable(currSize, sb, iterable);
else
handle(currSize, sb, indentAlways, wrap(item.toString()));
}

private static void handleArray(int currSize, Object[] item, StringBuilder sb) {
sb.append("[").append("\n");
Object[] array = item;
for (int i = 0; i < array.length; i++) {
serialiseItem(currSize + JSONUtils.INDENT_SIZE, array[i], sb, true, false);
if (i != array.length - 1) sb.append(",");
sb.append("\n");
}
indent(sb, currSize + JSONUtils.INDENT_SIZE, s -> s.append("]"));
}

private static void handleIterable(int currSize, StringBuilder sb, Iterable<?> iterable) {
sb.append("[").append("\n");
boolean flag = false;
for (Object o : iterable) {
if (flag) {
sb.append(",");
sb.append("\n");
}
indent(sb, currSize + JSONUtils.INDENT_SIZE, s -> s.append("]"));
} else if (item instanceof Iterable<?> iterable) {
sb.append("[").append("\n");
boolean flag = false;
for (Object o : iterable) {
if (flag) {
sb.append(",");
sb.append("\n");
}
serialiseItem(currSize + JSONUtils.INDENT_SIZE, o, sb, true);
flag = true;
}
indent(sb, currSize + JSONUtils.INDENT_SIZE, s -> s.append("]"));
} else if (indentAlways)
indent(sb, currSize + JSONUtils.INDENT_SIZE,
s -> s.append(wrap(item.toString())));
serialiseItem(currSize + JSONUtils.INDENT_SIZE, o, sb, true, false);
flag = true;
}
indent(sb, currSize + JSONUtils.INDENT_SIZE, s -> s.append("]"));
}

private static void handle(int currSize, StringBuilder sb, boolean indentAlways, String str) {
if (indentAlways)
indent(sb, currSize + JSONUtils.INDENT_SIZE, s -> s.append(str));
else
sb.append(wrap(item.toString()));
sb.append(str);
}

private static void indent(StringBuilder sb, int indentSize, Consumer<StringBuilder> action) {
Expand All @@ -214,25 +205,15 @@ default String[] ignoredFields() {
}

/**
* By default, this interface only serialises non-inherited fields in the child class. In order to serialise
* inherited ones too, this method should be overridden to return true, in which case every non-static fields,
* including private ones, unless omitted by {@link JSONSerialisable#ignoredFields()}, will be serialised.
* By default, Kompakt only serialises non-inherited fields in the child class. In order to serialise
* inherited ones too, this method should be overridden to return true, or the class of the object being serialised
* should be annotated with {@link DeepSerialise}, in which case every non-static fields,
* including private ones, unless ignored, will be serialised.
*
* @return whether inherited fields should be serialised.
*/
default boolean deepSerialise() {
return false;
}

/**
* By default, fields that are instances of the {@link Iterable} interface will be serialised as JSON arrays.
* Some examples of childrens of {@link Iterable} are {@link HashSet}, and {@link ArrayList}. If for whatever
* reason this feature should be disabled, override this method and return false.
*
* @return whether instances of {@link Iterable} are serialised as arrays.
*/
default boolean serialiseIterablesAsArrays() {
return true;
return this.getClass().isAnnotationPresent(DeepSerialise.class);
}

/**
Expand Down
Loading

0 comments on commit ce82df1

Please sign in to comment.