CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-net-minidev--json-smart

JSON Small and Fast Parser - A lightweight, high-performance JSON processing library for Java

Pending
Overview
Eval results
Files

customization.mddocs/

Custom Serialization Framework

JSON-Smart provides a powerful extension framework through JsonReader and JsonWriter interfaces that enables custom serialization and deserialization logic for any Java type.

Overview

The customization framework allows you to:

  • Define custom serialization logic for any Java class
  • Create custom deserialization mappers for complex types
  • Configure field name mapping between JSON and Java
  • Handle special data types (dates, enums, custom objects)
  • Implement domain-specific JSON transformations

JsonWriterI - Custom Serialization

Interface Definition

public interface JsonWriterI<T> {
    <E extends T> void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException;
}

Implement custom JSON serialization for any type.

Basic Custom Writer

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"}

Complex Object Writer

// 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"]}

Enum Writer with Custom Values

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"

JsonReader - Registry and Factory

The JsonReader class manages the registry of custom deserializers and provides factory methods.

JsonReader Class

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);
}

Global Registration Methods

// 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");

JsonReaderI - Custom Deserialization

Abstract Base Class

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);
}

Simple Type Reader

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);

Complex Object Reader

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);

Collection Reader

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);

Field Name Mapping

Simple Field Mapping

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"

Advanced Field Mapping in Custom Reader

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 objects

JsonWriter - Serialization Registry

The JsonWriter class manages custom serializers.

JsonWriter Class

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);
}

Built-in Writers

public static final JsonWriterI<JSONStreamAwareEx> JSONStreamAwareWriter;

Access built-in serialization writers.

Advanced Customization Examples

Polymorphic Serialization

// 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 Serialization

// 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());

Conditional Serialization

// 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

docs

configuration.md

core-api.md

customization.md

index.md

navigation.md

parsing.md

tile.json