Java tips: a flexible JSON extraction tool

1. Problem scenario

The usual way for Java to process JSON data is to convert it into a Java class object through a third-party library, but this will lead to a lot of temporary classes that have nothing to do with the product business. In JavaScript, you can directly extract, such as obj user. Name, very convenient. But in Java, if you don't convert to class objects, you have to write code like this carefully:

try {
    JsonElement element = JsonParser.parseString(jsonStr);
    if (element.isJsonObject()) {
        JsonObject userJson = element.getAsJsonObject().getAsJsonObject("user");
        if (userJson != null) {
            JsonPrimitive nameJson = userJson.getAsJsonPrimitive("name");
            if (nameJson != null && nameJson.isString()) {
                String name = nameJson.getAsString();
            }
        }
    }
} catch (JsonParseException e) {
    e.printStackTrace();
}

This method is very cumbersome, and it is full of defensive code, which is wordy. In fact, the key information is only jsonstr user. Name is like this. This article will introduce a tool class - JsonExtractor, which does not convert JSON data into a Java Bean object to extract JSON data content.

2. Function introduction

This class is relatively simple, but there are many methods. Therefore, I divide the methods in this class into two categories

2.1 construction method

For example, the method used to build JsonExtractor. It is not recommended to call the construction method directly here, but the following methods to build it

  1. Static method Builder from(String json): this method is the beginning of the whole process. The incoming parameter is the JSON string to be parsed, and the returned object is the builder used to build the JsonExtractor.
  2. Builder method jsonextractor < integer, jsonarray > forjsonarray(): this method specifies that the JSON array to be processed is an array type and returns the corresponding extractor to process the JSON array.
  3. Builder method jsonextractor < void, jsonnull > fornull(): this method specifies that the JSON data to be processed is null, which is basically not used.
  4. Builder method jsonextractor < void, jsonprimitive > forprimitive(): this method specifies that the JSON data to be processed is the original type (for example, int, String, etc.), and returns the extractor that processes the original type.
  5. Builder method jsonextractor < string, jsonobject > forjsonobject(): this method specifies that the JSON data to be processed is a JSON object and returns the extractor that processes the JSON object, which is commonly used.

2.2 extraction method

The parameters of these methods are determined according to the generic K of JsonExtractor. If the current object is an array, K is Integer, indicating the index of the array; If it is an object, K is String, indicating the key value corresponding to the attribute; If it is the original type or null, it is Void because there is no next-level property or content.

  1. JsonArray getArray(K key): extract the attribute of the corresponding key value as an array.
  2. Optional < jsonarray > optarray (k key): optional version of getArray.
  3. int getInt(K key): the attribute to extract the corresponding key value is int, otherwise 0 will be returned.
  4. Optional < integer > optint (k key): optional version of getInt.
  5. boolean getBool(K key): the attribute that extracts the corresponding key value is boolean. If not, false is returned.
  6. Optional < Boolean > optbool (k key): the optional version of getBool.
  7. long getLong(K key): the attribute to extract the corresponding key value is long. If not, 0 will be returned.
  8. Optional < long > optlong (k key): optional version of getLong.
  9. String getString(K key): extract the attribute of the corresponding key value as a string. If not, an empty string will be returned.
  10. Optional < string > optstring (k key): optional version of getString.
  11. JsonElement get(K key) and < T > t get (k key, class < T > typeclass): extract the attribute of the specified key value as JsonElement or specify Java type.
  12. Optional < jsonelement > opt (k key) and < T > optional < T > opt (k key, class < T > typeclass): the optional version of get.
  13. Builder into(K key): extract the attributes of the specified key value in the next step, such as data user. Multi level extraction such as name.
  14. Void for each (biconsumer < K, jsonelement > entryconsumer): traverses the contents of JSON data.

3. Use examples

For example, the json we want to extract is as follows:

{
  "id": 1,
  "name": "Li Lei",
  "pet": {
    "name": "huahua",
    "type": "dog",
    "id": 11
  },
  "live": true,
  "age": 20,
  "friends": [
    "James",
    "Andy",
    "Tom"
  ]
}

The corresponding Java beans are as follows:

class Pet {
    private String name;
    private String type;
    private long id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }
}

class User {
    private long id;
    private String name;
    private Pet pet;
    private boolean live;
    private int age;
    private String[] friends;

    public String[] getFriends() {
        return friends;
    }

    public void setFriends(String[] friends) {
        this.friends = friends;
    }

    public boolean isLive() {
        return live;
    }

    public void setLive(boolean live) {
        this.live = live;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Pet getPet() {
        return pet;
    }

    public void setPet(Pet pet) {
        this.pet = pet;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Then the extracted code is:

String json = "{\"id\":1,\"name\":\"Li Lei\",\"pet\":{\"name\":\"huahua\",\"type\":\"dog\",\"id\":11},\"live\":true,\"age\":20,\"friends\":[\"James\",\"Andy\",\"Tom\"]}";
JsonExtractor<String, JsonObject> extractor = JsonExtractor.from(json).forJsonObject();
//Get properties directly
long id = extractor.getLong("id");
//Get properties by basic style
extractor.optString("name").ifPresent((String name) -> {
    System.out.println("name:" + name);
});
extractor.optString("name").ifPresent(System.out::println);
//Get the attribute in the Optional style and convert it to the specified class manually
extractor.opt("pet").ifPresent((JsonElement element) -> {
    System.out.println("pet json:" + element);
    Pet pet = new Gson().fromJson(element, Pet.class);
    System.out.println("pet bean 1:" + pet.getName());
});
//Get the attribute in the Optional style and automatically convert it to the specified class
extractor.opt("pet", Pet.class).ifPresent((Pet pet) -> {
    System.out.println("pet bean 2:" + pet.getName());
});
//Extract the attribute directly to the specified type
Pet pet = extractor.get("pet", Pet.class);

//Extract multi-level fields, similar to js user pet. Name's style
extractor.into("pet").forJsonObject()
        .optString("name").ifPresent((String name) -> {
            System.out.println("pet.name:" + name);
        });
//Extract and traverse the array
extractor.into("friends").forJsonArray()
        .forEach((Integer index, JsonElement element) -> {
            System.out.println(index + ":" + element.getAsString());
        });

4. Complete realization

import com.google.gson.*;

import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;

public abstract class JsonExtractor<K, V> {
    private static final Gson gson = new Gson();

    protected V jsonElement;

    JsonExtractor(V jsonElement) {
        this.jsonElement = jsonElement;
    }

    public V getJsonElement() {
        return jsonElement;
    }

    public static class Builder {
        private JsonElement element;

        protected Builder() {

        }

        public Builder(JsonElement element) {
            this.element = element;
        }

        public JsonExtractor<Void, JsonNull> forNull() {
            try {
                return new NullExtractor(element.getAsJsonNull());
            } catch (RuntimeException e) {
                return new ErrorExtractor<>(e);
            }
        }

        public JsonExtractor<Void, JsonPrimitive> forPrimitive() {
            try {
                return new PrimitiveExtractor(element.getAsJsonPrimitive());
            } catch (RuntimeException e) {
                return new ErrorExtractor<>(e);
            }
        }

        public JsonExtractor<String, JsonObject> forJsonObject() {
            try {
                return new ObjectExtractor(element.getAsJsonObject());
            } catch (RuntimeException e) {
                return new ErrorExtractor<>(e);
            }
        }

        public JsonExtractor<Integer, JsonArray> forJsonArray() {
            try {
                return new ArrayExtractor(element.getAsJsonArray());
            } catch (RuntimeException e) {
                return new ErrorExtractor<>(e);
            }
        }
    }

    private static class ErrorBuilder extends Builder {
        private final Exception exception;

        public ErrorBuilder(Exception exception) {
            this.exception = exception;
        }

        @Override
        public JsonExtractor<Integer, JsonArray> forJsonArray() {
            return new ErrorExtractor<>(exception);
        }

        @Override
        public JsonExtractor<Void, JsonNull> forNull() {
            return new ErrorExtractor<>(exception);
        }

        @Override
        public JsonExtractor<Void, JsonPrimitive> forPrimitive() {
            return new ErrorExtractor<>(exception);
        }

        @Override
        public JsonExtractor<String, JsonObject> forJsonObject() {
            return new ErrorExtractor<>(exception);
        }
    }

    public static Builder from(String json) {
        try {
            return new Builder(JsonParser.parseString(json));
        } catch (Exception e) {
            return new ErrorBuilder(e);
        }
    }

    public Builder into(K key) {
        return new ErrorBuilder(new RuntimeException("not implements!"));
    }

    public Optional<JsonElement> opt(K key) {
        return Optional.ofNullable(get(key));
    }

    public <T> Optional<T> opt(K key, Class<T> typeClass) {
        return Optional.empty();
    }

    public JsonElement get(K key) {
        return null;
    }

    public <T> T get(K key, Class<T> typeClass) {
        return opt(key, typeClass).orElse(null);
    }


    public JsonArray getArray(K key) {
        return null;
    }

    public Optional<JsonArray> optArray(K key) {
        return Optional.ofNullable(getArray(key));
    }

    public int getInt(K key) {
        return optInt(key).orElse(0);
    }

    public Optional<Integer> optInt(K key) {
        return Optional.empty();
    }

    public double getDouble(K key) {
        return optDouble(key).orElse(0d);
    }

    public Optional<Double> optDouble(K key) {
        return Optional.empty();
    }

    public boolean getBool(K key) {
        return optBool(key).orElse(false);
    }

    public Optional<Boolean> optBool(K key) {
        return Optional.empty();
    }

    public long getLong(K key) {
        return optLong(key).orElse(0L);
    }

    public Optional<Long> optLong(K key) {
        return Optional.empty();
    }

    public String getString(K key) {
        return optString(key).orElse("");
    }

    public Optional<String> optString(K key) {
        return Optional.empty();
    }


    public void forEach(BiConsumer<K, JsonElement> entryConsumer) {

    }

    private static class ObjectExtractor extends JsonExtractor<String, JsonObject> {
        private ObjectExtractor(JsonObject jsonObject) {
            super(jsonObject);
        }

        @Override
        public Builder into(String key) {
            JsonElement element = this.jsonElement.get(key);
            return element == null ? new Builder(JsonNull.INSTANCE) : new Builder(element);
        }

        @Override
        public JsonElement get(String key) {
            return this.jsonElement.getAsJsonObject(key);
        }

        @Override
        public Optional<JsonArray> optArray(String key) {
            return Optional.ofNullable(this.jsonElement.getAsJsonArray(key));
        }

        @Override
        public Optional<Integer> optInt(String key) {
            return Optional.ofNullable(this.jsonElement.getAsJsonPrimitive(key)).map(JsonPrimitive::getAsInt);
        }

        @Override
        public Optional<Double> optDouble(String key) {
            return Optional.ofNullable(jsonElement.getAsJsonPrimitive(key)).map(JsonPrimitive::getAsDouble);
        }

        @Override
        public Optional<Boolean> optBool(String key) {
            return Optional.ofNullable(jsonElement.getAsJsonPrimitive(key)).map(JsonPrimitive::getAsBoolean);
        }

        @Override
        public Optional<Long> optLong(String key) {
            return Optional.ofNullable(jsonElement.getAsJsonPrimitive(key)).map(JsonPrimitive::getAsLong);
        }

        @Override
        public Optional<String> optString(String key) {
            return Optional.ofNullable(jsonElement.getAsJsonPrimitive(key)).map(JsonPrimitive::getAsString);
        }

        @Override
        public <T> Optional<T> opt(String key, Class<T> typeClass) {
            try {
                return Optional.ofNullable(jsonElement.get(key)).map(e -> gson.fromJson(e, typeClass));
            } catch (JsonSyntaxException e) {
                return Optional.empty();
            }
        }

        @Override
        public void forEach(BiConsumer<String, JsonElement> entryConsumer) {
            Set<Map.Entry<String, JsonElement>> entrySet = jsonElement.entrySet();
            for (Map.Entry<String, JsonElement> entry : entrySet) {
                entryConsumer.accept(entry.getKey(), entry.getValue());
            }
        }
    }

    private static class ArrayExtractor extends JsonExtractor<Integer, JsonArray> {
        private ArrayExtractor(JsonArray array) {
            super(array);
        }

        @Override
        public Builder into(Integer index) {
            try {
                return new Builder(getJsonElement().get(index));
            } catch (IndexOutOfBoundsException e) {
                return new ErrorBuilder(e);
            }
        }

        @Override
        public JsonElement get(Integer index) {
            try {
                return getJsonElement().get(index);
            } catch (IndexOutOfBoundsException e) {
                return null;
            }
        }

        @Override
        public Optional<JsonArray> optArray(Integer index) {
            return Optional.ofNullable(getJsonElement().get(index)).map(JsonElement::getAsJsonArray);
        }

        @Override
        public Optional<Integer> optInt(Integer index) {
            try {
                return Optional.ofNullable(getJsonElement().get(index)).map(JsonElement::getAsInt);
            } catch (IndexOutOfBoundsException e) {
                return Optional.empty();
            }
        }

        @Override
        public Optional<Double> optDouble(Integer index) {
            try {
                return Optional.ofNullable(getJsonElement().get(index)).map(JsonElement::getAsDouble);
            } catch (IndexOutOfBoundsException e) {
                return Optional.empty();
            }
        }

        @Override
        public Optional<Boolean> optBool(Integer index) {
            try {
                return Optional.ofNullable(getJsonElement().get(index)).map(JsonElement::getAsBoolean);
            } catch (IndexOutOfBoundsException e) {
                return Optional.empty();
            }
        }

        @Override
        public Optional<Long> optLong(Integer index) {
            try {
                return Optional.ofNullable(getJsonElement().get(index)).map(JsonElement::getAsLong);
            } catch (IndexOutOfBoundsException e) {
                return Optional.empty();
            }
        }

        @Override
        public Optional<String> optString(Integer index) {
            try {
                return Optional.ofNullable(getJsonElement().get(index)).map(JsonElement::getAsString);
            } catch (IndexOutOfBoundsException e) {
                return Optional.empty();
            }
        }

        @Override
        public <T> Optional<T> opt(Integer index, Class<T> typeClass) {
            try {
                return Optional.ofNullable(getJsonElement().get(index)).map(e -> gson.fromJson(e, typeClass));
            } catch (JsonSyntaxException e) {
                return Optional.empty();
            }
        }

        @Override
        public void forEach(BiConsumer<Integer, JsonElement> entryConsumer) {
            for (int i = 0; i < getJsonElement().size(); i++) {
                entryConsumer.accept(i, getJsonElement().get(i));
            }
        }
    }

    private static class NullExtractor extends JsonExtractor<Void, JsonNull> {
        NullExtractor(JsonNull jsonElement) {
            super(jsonElement);
        }
    }

    private static class PrimitiveExtractor extends JsonExtractor<Void, JsonPrimitive> {
        PrimitiveExtractor(JsonPrimitive jsonElement) {
            super(jsonElement);
        }
    }

    private static class ErrorExtractor<K, V> extends JsonExtractor<K, V> {
        private final Exception exception;

        private ErrorExtractor(Exception exception) {
            super(null);
            this.exception = exception;
        }

        @Override
        public Builder into(K key) {
            return new ErrorBuilder(exception);
        }
    }
}

Keywords: Java JSON gson

Added by vomitbomb on Sun, 06 Feb 2022 06:09:16 +0200