JSON Small and Fast Parser - A lightweight, high-performance JSON processing library for Java
—
JSON-Smart provides a powerful extension framework through JsonReader and JsonWriter interfaces that enables custom serialization and deserialization logic for any Java type.
The customization framework allows you to:
public interface JsonWriterI<T> {
<E extends T> void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException;
}Implement custom JSON serialization for any type.
import net.minidev.json.reader.JsonWriterI;
import net.minidev.json.JSONStyle;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
// Custom writer for LocalDate
public class LocalDateWriter implements JsonWriterI<LocalDate> {
private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
@Override
public <E extends LocalDate> void writeJSONString(E date, Appendable out, JSONStyle compression)
throws IOException {
if (date == null) {
out.append("null");
} else {
compression.writeString(out, date.format(formatter));
}
}
}
// Register the custom writer
JSONValue.registerWriter(LocalDate.class, new LocalDateWriter());
// Use with automatic serialization
LocalDate today = LocalDate.now();
String json = JSONValue.toJSONString(today); // "2023-10-15"
// Works in objects too
JSONObject event = new JSONObject()
.appendField("name", "Meeting")
.appendField("date", today);
String eventJson = JSONValue.toJSONString(event); // {"name":"Meeting","date":"2023-10-15"}// Custom writer for a complex business object
public class PersonWriter implements JsonWriterI<Person> {
@Override
public <E extends Person> void writeJSONString(E person, Appendable out, JSONStyle compression)
throws IOException {
out.append('{');
boolean first = true;
// Write ID
if (!first) out.append(',');
JSONObject.writeJSONKV("id", person.getId(), out, compression);
first = false;
// Write full name (computed from first + last)
if (!first) out.append(',');
String fullName = person.getFirstName() + " " + person.getLastName();
JSONObject.writeJSONKV("fullName", fullName, out, compression);
// Write age only if adult
if (person.getAge() >= 18) {
out.append(',');
JSONObject.writeJSONKV("age", person.getAge(), out, compression);
}
// Write roles as array
if (!person.getRoles().isEmpty()) {
out.append(',');
JSONObject.writeJSONKV("roles", person.getRoles(), out, compression);
}
out.append('}');
}
}
// Usage
JSONValue.registerWriter(Person.class, new PersonWriter());
Person person = new Person(1, "John", "Doe", 25, Arrays.asList("admin", "user"));
String json = JSONValue.toJSONString(person);
// {"id":1,"fullName":"John Doe","age":25,"roles":["admin","user"]}public enum Status {
ACTIVE, INACTIVE, PENDING, SUSPENDED
}
public class StatusWriter implements JsonWriterI<Status> {
@Override
public <E extends Status> void writeJSONString(E status, Appendable out, JSONStyle compression)
throws IOException {
if (status == null) {
out.append("null");
return;
}
String value = switch (status) {
case ACTIVE -> "A";
case INACTIVE -> "I";
case PENDING -> "P";
case SUSPENDED -> "S";
};
compression.writeString(out, value);
}
}
// Register and use
JSONValue.registerWriter(Status.class, new StatusWriter());
Status status = Status.ACTIVE;
String json = JSONValue.toJSONString(status); // "A"The JsonReader class manages the registry of custom deserializers and provides factory methods.
public class JsonReader {
public JsonReaderI<JSONAwareEx> DEFAULT;
public JsonReaderI<JSONAwareEx> DEFAULT_ORDERED;
public JsonReader();
public <T> void remapField(Class<T> type, String fromJson, String toJava);
public <T> void registerReader(Class<T> type, JsonReaderI<T> mapper);
public <T> JsonReaderI<T> getMapper(Class<T> type);
public <T> JsonReaderI<T> getMapper(Type type);
}// In JSONValue class
public static <T> void registerReader(Class<T> type, JsonReaderI<T> mapper);
public static <T> void remapField(Class<T> type, String fromJson, String toJava);Register custom deserializers globally.
// Register custom reader for LocalDate
JSONValue.registerReader(LocalDate.class, new LocalDateReader());
// Configure field name mapping
JSONValue.remapField(Person.class, "first_name", "firstName");
JSONValue.remapField(Person.class, "last_name", "lastName");public abstract class JsonReaderI<T> {
public JsonReaderI(JsonReader base);
// Override as needed:
public JsonReaderI<?> startObject(String key) throws ParseException, IOException;
public JsonReaderI<?> startArray(String key) throws ParseException, IOException;
public void setValue(Object current, String key, Object value) throws ParseException, IOException;
public Object getValue(Object current, String key);
public Type getType(String key);
public void addValue(Object current, Object value) throws ParseException, IOException;
public Object createObject();
public Object createArray();
public abstract T convert(Object current);
}import net.minidev.json.writer.JsonReaderI;
import net.minidev.json.writer.JsonReader;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class LocalDateReader extends JsonReaderI<LocalDate> {
public LocalDateReader() {
super(new JsonReader());
}
@Override
public LocalDate convert(Object current) {
if (current == null) {
return null;
}
if (current instanceof String) {
String dateStr = (String) current;
try {
return LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE);
} catch (Exception e) {
// Try alternative formats
try {
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("MM/dd/yyyy"));
} catch (Exception e2) {
throw new RuntimeException("Cannot parse date: " + dateStr, e2);
}
}
}
throw new RuntimeException("Cannot convert " + current.getClass() + " to LocalDate");
}
}
// Register and use
JSONValue.registerReader(LocalDate.class, new LocalDateReader());
// Parse various date formats
LocalDate date1 = JSONValue.parse("\"2023-10-15\"", LocalDate.class);
LocalDate date2 = JSONValue.parse("\"10/15/2023\"", LocalDate.class);public class PersonReader extends JsonReaderI<Person> {
public PersonReader() {
super(new JsonReader());
}
@Override
public Person convert(Object current) {
if (!(current instanceof Map)) {
throw new RuntimeException("Expected JSON object for Person");
}
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) current;
// Extract fields with defaults and validation
Integer id = getAsInteger(map, "id");
if (id == null) {
throw new RuntimeException("Person ID is required");
}
String firstName = getAsString(map, "firstName", "");
String lastName = getAsString(map, "lastName", "");
Integer age = getAsInteger(map, "age", 0);
// Handle roles array
List<String> roles = new ArrayList<>();
Object rolesObj = map.get("roles");
if (rolesObj instanceof List) {
@SuppressWarnings("unchecked")
List<Object> rolesList = (List<Object>) rolesObj;
for (Object role : rolesList) {
if (role instanceof String) {
roles.add((String) role);
}
}
}
return new Person(id, firstName, lastName, age, roles);
}
private String getAsString(Map<String, Object> map, String key, String defaultValue) {
Object value = map.get(key);
return value instanceof String ? (String) value : defaultValue;
}
private Integer getAsInteger(Map<String, Object> map, String key) {
return getAsInteger(map, key, null);
}
private Integer getAsInteger(Map<String, Object> map, String key, Integer defaultValue) {
Object value = map.get(key);
if (value instanceof Number) {
return ((Number) value).intValue();
}
if (value instanceof String) {
try {
return Integer.parseInt((String) value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
return defaultValue;
}
}
// Register and use
JSONValue.registerReader(Person.class, new PersonReader());
String json = """
{
"id": 1,
"firstName": "John",
"lastName": "Doe",
"age": "25",
"roles": ["admin", "user"]
}
""";
Person person = JSONValue.parse(json, Person.class);import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class PersonListReader extends JsonReaderI<List<Person>> {
public PersonListReader() {
super(new JsonReader());
}
@Override
public List<Person> convert(Object current) {
if (!(current instanceof List)) {
throw new RuntimeException("Expected JSON array for Person list");
}
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) current;
List<Person> persons = new ArrayList<>();
PersonReader personReader = new PersonReader();
for (Object item : list) {
Person person = personReader.convert(item);
persons.add(person);
}
return persons;
}
}
// Create custom type for registration
public class PersonList extends ArrayList<Person> {}
// Register and use
JSONValue.registerReader(PersonList.class, new PersonListReader());
String json = """
[
{"id": 1, "firstName": "Alice", "lastName": "Johnson"},
{"id": 2, "firstName": "Bob", "lastName": "Smith"}
]
""";
PersonList persons = JSONValue.parse(json, PersonList.class);public static <T> void remapField(Class<T> type, String fromJson, String toJava);Map JSON field names to Java field names.
public class User {
private String firstName;
private String lastName;
private String emailAddress;
// getters and setters...
}
// Configure field mapping
JSONValue.remapField(User.class, "first_name", "firstName");
JSONValue.remapField(User.class, "last_name", "lastName");
JSONValue.remapField(User.class, "email", "emailAddress");
// Now JSON with snake_case is automatically mapped
String json = """
{
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com"
}
""";
User user = JSONValue.parse(json, User.class);
System.out.println(user.getFirstName()); // "John"public class FlexiblePersonReader extends JsonReaderI<Person> {
public FlexiblePersonReader() {
super(new JsonReader());
}
@Override
public Person convert(Object current) {
if (!(current instanceof Map)) {
throw new RuntimeException("Expected JSON object");
}
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) current;
// Try multiple field name variations
Integer id = findValue(map, Integer.class, "id", "ID", "personId", "person_id");
String firstName = findValue(map, String.class, "firstName", "first_name", "fname", "given_name");
String lastName = findValue(map, String.class, "lastName", "last_name", "lname", "family_name", "surname");
Integer age = findValue(map, Integer.class, "age", "years", "yearsOld");
return new Person(id, firstName, lastName, age, Collections.emptyList());
}
@SuppressWarnings("unchecked")
private <T> T findValue(Map<String, Object> map, Class<T> type, String... keys) {
for (String key : keys) {
Object value = map.get(key);
if (value != null) {
if (type.isInstance(value)) {
return (T) value;
} else if (type == Integer.class && value instanceof Number) {
return (T) Integer.valueOf(((Number) value).intValue());
} else if (type == String.class) {
return (T) value.toString();
}
}
}
return null;
}
}
// This reader handles multiple JSON formats:
String json1 = """{"id": 1, "firstName": "John", "lastName": "Doe", "age": 30}""";
String json2 = """{"ID": 1, "first_name": "John", "family_name": "Doe", "years": 30}""";
String json3 = """{"personId": 1, "fname": "John", "surname": "Doe", "yearsOld": 30}""";
JSONValue.registerReader(Person.class, new FlexiblePersonReader());
Person p1 = JSONValue.parse(json1, Person.class);
Person p2 = JSONValue.parse(json2, Person.class);
Person p3 = JSONValue.parse(json3, Person.class);
// All produce equivalent Person objectsThe JsonWriter class manages custom serializers.
public class JsonWriter {
public JsonWriter();
public <T> void remapField(Class<T> type, String fromJava, String toJson);
public JsonWriterI<?> getWriterByInterface(Class<?> clazz);
public JsonWriterI getWrite(Class cls);
}public static final JsonWriterI<JSONStreamAwareEx> JSONStreamAwareWriter;Access built-in serialization writers.
// Base class
public abstract class Shape {
protected String type;
protected String color;
// getters and setters...
}
public class Circle extends Shape {
private double radius;
// constructors, getters, setters...
}
public class Rectangle extends Shape {
private double width;
private double height;
// constructors, getters, setters...
}
// Polymorphic writer
public class ShapeWriter implements JsonWriterI<Shape> {
@Override
public <E extends Shape> void writeJSONString(E shape, Appendable out, JSONStyle compression)
throws IOException {
out.append('{');
// Always include type discriminator
JSONObject.writeJSONKV("type", shape.getClass().getSimpleName().toLowerCase(), out, compression);
out.append(',');
JSONObject.writeJSONKV("color", shape.getColor(), out, compression);
// Type-specific fields
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
out.append(',');
JSONObject.writeJSONKV("radius", circle.getRadius(), out, compression);
} else if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle) shape;
out.append(',');
JSONObject.writeJSONKV("width", rect.getWidth(), out, compression);
out.append(',');
JSONObject.writeJSONKV("height", rect.getHeight(), out, compression);
}
out.append('}');
}
}
// Polymorphic reader
public class ShapeReader extends JsonReaderI<Shape> {
public ShapeReader() {
super(new JsonReader());
}
@Override
public Shape convert(Object current) {
if (!(current instanceof Map)) {
throw new RuntimeException("Expected JSON object");
}
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) current;
String type = (String) map.get("type");
String color = (String) map.get("color");
switch (type) {
case "circle":
Circle circle = new Circle();
circle.setColor(color);
circle.setRadius(((Number) map.get("radius")).doubleValue());
return circle;
case "rectangle":
Rectangle rect = new Rectangle();
rect.setColor(color);
rect.setWidth(((Number) map.get("width")).doubleValue());
rect.setHeight(((Number) map.get("height")).doubleValue());
return rect;
default:
throw new RuntimeException("Unknown shape type: " + type);
}
}
}
// Register polymorphic handlers
JSONValue.registerWriter(Shape.class, new ShapeWriter());
JSONValue.registerReader(Shape.class, new ShapeReader());
// Usage
Circle circle = new Circle();
circle.setColor("red");
circle.setRadius(5.0);
String json = JSONValue.toJSONString(circle);
// {"type":"circle","color":"red","radius":5.0}
Shape parsed = JSONValue.parse(json, Shape.class);
// Returns Circle instance// Custom collection that maintains insertion order and prevents duplicates
public class OrderedSet<T> extends LinkedHashSet<T> {
// Custom collection implementation
}
public class OrderedSetWriter implements JsonWriterI<OrderedSet<?>> {
@Override
public <E extends OrderedSet<?>> void writeJSONString(E set, Appendable out, JSONStyle compression)
throws IOException {
// Serialize as array with metadata
out.append('{');
JSONObject.writeJSONKV("type", "orderedSet", out, compression);
out.append(',');
JSONObject.writeJSONKV("size", set.size(), out, compression);
out.append(',');
out.append("\"items\":");
// Write items as array
JSONArray.writeJSONString(new ArrayList<>(set), out, compression);
out.append('}');
}
}
public class OrderedSetReader extends JsonReaderI<OrderedSet<Object>> {
public OrderedSetReader() {
super(new JsonReader());
}
@Override
public OrderedSet<Object> convert(Object current) {
if (!(current instanceof Map)) {
throw new RuntimeException("Expected JSON object");
}
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) current;
String type = (String) map.get("type");
if (!"orderedSet".equals(type)) {
throw new RuntimeException("Expected orderedSet type");
}
Object itemsObj = map.get("items");
if (!(itemsObj instanceof List)) {
throw new RuntimeException("Expected items array");
}
@SuppressWarnings("unchecked")
List<Object> items = (List<Object>) itemsObj;
OrderedSet<Object> result = new OrderedSet<>();
result.addAll(items);
return result;
}
}
// Register custom collection handlers
JSONValue.registerWriter(OrderedSet.class, new OrderedSetWriter());
JSONValue.registerReader(OrderedSet.class, new OrderedSetReader());// Writer that excludes null and empty values
public class CleanObjectWriter implements JsonWriterI<Object> {
@Override
public <E> void writeJSONString(E obj, Appendable out, JSONStyle compression)
throws IOException {
if (obj == null) {
out.append("null");
return;
}
// Use reflection to get fields
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
out.append('{');
boolean first = true;
for (Field field : fields) {
field.setAccessible(true);
try {
Object value = field.get(obj);
// Skip null values, empty strings, empty collections
if (shouldSkipValue(value)) {
continue;
}
if (!first) {
out.append(',');
}
String fieldName = field.getName();
JSONObject.writeJSONKV(fieldName, value, out, compression);
first = false;
} catch (IllegalAccessException e) {
// Skip inaccessible fields
}
}
out.append('}');
}
private boolean shouldSkipValue(Object value) {
if (value == null) return true;
if (value instanceof String && ((String) value).isEmpty()) return true;
if (value instanceof Collection && ((Collection<?>) value).isEmpty()) return true;
if (value instanceof Map && ((Map<?, ?>) value).isEmpty()) return true;
return false;
}
}
// Register for specific classes that need clean serialization
JSONValue.registerWriter(MyDataClass.class, new CleanObjectWriter());Install with Tessl CLI
npx tessl i tessl/maven-net-minidev--json-smart