Design (LLD)  JSON parser - Machine Coding

Design (LLD) JSON parser - Machine Coding

Designing a JSON parser involves several components and considerations. Below is a basic outline of features, design patterns, and a simplified code implementation in Java. Note that JSON parsing is a broad topic, and the implementation can vary based on specific requirements.

Features Required:

  1. Parse JSON: Read and interpret JSON data.

  2. Handle Different Types: Support various data types (string, number, boolean, array, object, null).

  3. Error Handling: Gracefully handle syntax errors and unexpected input.

  4. Nested Structures: Support nested JSON structures.

  5. Flexible Parsing: Allow parsing of partial JSON data.

  6. Object Mapping: Convert JSON data into a usable object structure.

  7. Configurability: Allow configuration for handling specific JSON structures.

Design Patterns Involved or Used:

  1. Interpreter Pattern: For parsing JSON syntax.

  2. Composite Pattern: For handling nested structures.

  3. Factory Method Pattern: To create different types of JSON elements.

  4. Builder Pattern: Construct complex JSON objects step by step.

  5. Strategy Pattern: Different strategies for parsing different types of JSON elements.

Code

import java.util.*;
import java.util.stream.Collectors;

interface JsonElement {
    Object getValue();
}

class JsonString implements JsonElement {
    private String value;

    public JsonString(String value) {
        this.value = value;
    }

    public Object getValue() {
        return value;
    }
}

class JsonNumber implements JsonElement {
    private Number value;

    public JsonNumber(Number value) {
        this.value = value;
    }

    public Object getValue() {
        return value;
    }
}

class JsonBoolean implements JsonElement {
    private Boolean value;

    public JsonBoolean(Boolean value) {
        this.value = value;
    }

    public Object getValue() {
        return value;
    }
}

class JsonArray implements JsonElement {
    private List<JsonElement> elements;

    public JsonArray(List<JsonElement> elements) {
        this.elements = elements;
    }

    public Object getValue() {
        return elements.stream().map(JsonElement::getValue).collect(Collectors.toList());
    }
}

class JsonObject implements JsonElement {
    private Map<String, JsonElement> properties;

    public JsonObject(Map<String, JsonElement> properties) {
        this.properties = properties;
    }

    public Object getValue() {
        Map<String, Object> result = new HashMap<>();
        properties.forEach((key, value) -> result.put(key, value.getValue()));
        return result;
    }
}

public class JsonParser {
    private int index;
    private String json;

    // Constants
    private static final char OPEN_CURLY_BRACE = '{';
    private static final char CLOSE_CURLY_BRACE = '}';
    private static final char OPEN_SQUARE_BRACKET = '[';
    private static final char CLOSE_SQUARE_BRACKET = ']';
    private static final char DOUBLE_QUOTE = '"';
    private static final char COLON = ':';
    private static final char COMMA = ',';

    public JsonElement parse(String jsonString) {
        this.index = 0;
        this.json = jsonString;
        skipWhitespace();
        return parseValue();
    }

    private JsonElement parseValue() {
        char currentChar = json.charAt(index);

        if (currentChar == OPEN_CURLY_BRACE) {
            return parseObject();
        } else if (currentChar == OPEN_SQUARE_BRACKET) {
            return parseArray();
        } else if (currentChar == DOUBLE_QUOTE) {
            return parseString();
        } else if (Character.isDigit(currentChar) || currentChar == '-') {
            return parseNumber();
        } else if (currentChar == 't' || currentChar == 'f') {
            return parseBoolean();
        } else if (currentChar == 'n') {
            return parseNull();
        }

        throw new RuntimeException("Invalid JSON");
    }

    private JsonObject parseObject() {
        Map<String, JsonElement> properties = new HashMap<>();

        // Consume the opening curly brace
        consume(OPEN_CURLY_BRACE);

        skipWhitespace();
        while (json.charAt(index) != CLOSE_CURLY_BRACE) {
            // Parse property name
            String propertyName = parseString().getValue().toString();
            skipWhitespace();

            // Consume the colon
            consume(COLON);
            skipWhitespace();

            // Parse property value
            JsonElement propertyValue = parseValue();
            properties.put(propertyName, propertyValue);

            skipWhitespace();

            // Check for a comma, indicating more properties
            if (json.charAt(index) == COMMA) {
                consume(COMMA);
                skipWhitespace();
            }
        }

        // Consume the closing curly brace
        consume(CLOSE_CURLY_BRACE);

        return new JsonObject(properties);
    }

    private JsonArray parseArray() {
        List<JsonElement> elements = new ArrayList<>();

        // Consume the opening square bracket
        consume(OPEN_SQUARE_BRACKET);

        skipWhitespace();
        while (json.charAt(index) != CLOSE_SQUARE_BRACKET) {
            // Parse array element
            JsonElement element = parseValue();
            elements.add(element);

            skipWhitespace();

            // Check for a comma, indicating more elements
            if (json.charAt(index) == COMMA) {
                consume(COMMA);
                skipWhitespace();
            }
        }

        // Consume the closing square bracket
        consume(CLOSE_SQUARE_BRACKET);

        return new JsonArray(elements);
    }

    private JsonString parseString() {
        // Consume the opening double quote
        consume(DOUBLE_QUOTE);

        StringBuilder sb = new StringBuilder();
        while (json.charAt(index) != DOUBLE_QUOTE) {
            sb.append(json.charAt(index));
            index++;
        }

        // Consume the closing double quote
        consume(DOUBLE_QUOTE);

        return new JsonString(sb.toString());
    }

    private JsonNumber parseNumber() {
        int startIndex = index;

        // Consume digits and optional decimal point
        while (Character.isDigit(json.charAt(index)) || json.charAt(index) == '.') {
            index++;
        }

        String numberStr = json.substring(startIndex, index);
        if (numberStr.contains(".")) {
            return new JsonNumber(Double.parseDouble(numberStr));
        } else {
            return new JsonNumber(Long.parseLong(numberStr));
        }
    }

    private JsonBoolean parseBoolean() {
        String boolStr = consumeWord();
        if (boolStr.equals("true")) {
            return new JsonBoolean(true);
        } else if (boolStr.equals("false")) {
            return new JsonBoolean(false);
        }

        throw new RuntimeException("Invalid boolean value");
    }

    private JsonElement parseNull() {
        consumeWord(); // Consume "null"
        return null;
    }

    private String consumeWord() {
        StringBuilder sb = new StringBuilder();
        while (Character.isLetter(json.charAt(index))) {
            sb.append(json.charAt(index));
            index++;
        }
        return sb.toString();
    }

    private void consume(char expected) {
        if (json.charAt(index) == expected) {
            index++;
        } else {
            throw new RuntimeException("Expected: " + expected);
        }
    }

    private void skipWhitespace() {
        while (Character.isWhitespace(json.charAt(index))) {
            index++;
        }
    }

    public static void main(String[] args) {
        String jsonString = "{ \"name\": \"John\", \"age\": 30, \"city\": \"New York\", \"isAdmin\": true, \"scores\": [10, 20, 30] }";

        JsonParser jsonParser = new JsonParser();
        JsonElement jsonElement = jsonParser.parse(jsonString);

        System.out.println(jsonElement.getValue());
    }
}

We can use Grammar class also to make this solution more general.

Did you find this article valuable?

Support Subhahu Jain by becoming a sponsor. Any amount is appreciated!