Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fastest way to parse JSON from String when format is known

I want to parse a String into an internal JSON object (or equivalent) in Java. The usual libraries, Gson and Jackson, are way too slow for my needs (> 100us for each String to Json parse, according to my benchmarks). I know there are slightly faster libraries, but looking at the benchmarks online, the gains available will be small (less than an order of magnitude improvement).

If I know the format of the JSON in advance, is there a way I can parse it much faster? For example, I know the String will be a JSON of the format:

{
   "A" : 1.0 ,
   "B" : "X"
}

i.e., I know the two keys will be "A" and "B", and the values will be a double and a string, respectively. Given this advanced knowledge of the format, is there a library or some approach to parse the JSON much faster than usual?

like image 966
ABC Avatar asked Oct 09 '19 12:10

ABC


People also ask

What is the best and quickest way to parse JSON from the network?

You can use Jackson for parsing Json because it's fast and due to that most of Vendors uses Jakson for parsing JSON ie.

How do you parse a string of JSON response?

Example - Parsing JSON parse() to convert text into a JavaScript object: const obj = JSON. parse('{"name":"John", "age":30, "city":"New York"}'); Make sure the text is in JSON format, or else you will get a syntax error.

Can you parse a string to JSON?

You can try parse() to get JSON data and stringify() to convert a string to JSON.


1 Answers

If you know a JSON payload structure you can use Streaming API to read data. I created 4 different methods to read given JSON payload:

  1. Default Gson - use Gson class.
  2. Gson Adapter - use JsonReader from Gson library.
  3. Default Jackson - use ObjectMapper from Jackson.
  4. Jackson streaming API - use JsonParser class.

To make it comparable all these methods take JSON payload as String and return Pojo object which represents A and B properties. Below graph represents differences: enter image description here

As you can notice, Jackson's Streaming API is the fastest way to deserialise your JSON payload from these 4 approaches.

To generate above graph, below data were used:

1113 547 540 546 544 552 547 549 547 548 avg 603.3
940 455 452 456 465 459 457 458 455 455 avg 505.2
422 266 257 262 260 267 259 262 257 259 avg 277.1
202 186 184 189 185 188 182 186 187 183 avg 187.2

Benchmark code:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

public class JsonApp {

    private static final String json = "{\"A\" : 1.0 ,\"B\" : \"X\"}";

    private static final int MAX = 1_000_000;

    private static List<List<Duration>> values = new ArrayList<>();

    static {
        IntStream.range(0, 4).forEach(i -> values.add(new ArrayList<>()));
    }

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            int v = 0;
            values.get(v++).add(defaultGson());
            values.get(v++).add(gsonAdapter());
            values.get(v++).add(defaultJackson());
            values.get(v).add(jacksonJsonFactory());
        }
        values.forEach(list -> {
            list.forEach(d -> System.out.print(d.toMillis() + " "));
            System.out.println(" avg " + list.stream()
                    .mapToLong(Duration::toMillis)
                    .average().getAsDouble());
        });
    }

    static Duration defaultGson() {
        Gson gson = new Gson();

        long start = System.nanoTime();
        for (int i = MAX; i > 0; i--) {
            gson.fromJson(json, Pojo.class);
        }

        return Duration.ofNanos(System.nanoTime() - start);
    }

    static Duration gsonAdapter() throws IOException {
        PojoTypeAdapter adapter = new PojoTypeAdapter();

        long start = System.nanoTime();
        for (int i = MAX; i > 0; i--) {
            adapter.fromJson(json);
        }

        return Duration.ofNanos(System.nanoTime() - start);
    }

    static Duration defaultJackson() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

        long start = System.nanoTime();
        for (int i = MAX; i > 0; i--) {
            mapper.readValue(json, Pojo.class);
        }

        return Duration.ofNanos(System.nanoTime() - start);
    }

    static Duration jacksonJsonFactory() throws IOException {
        JsonFactory jfactory = new JsonFactory();

        long start = System.nanoTime();
        for (int i = MAX; i > 0; i--) {
            readPartially(jfactory);
        }
        return Duration.ofNanos(System.nanoTime() - start);
    }

    static Pojo readPartially(JsonFactory jfactory) throws IOException {
        try (JsonParser parser = jfactory.createParser(json)) {

            Pojo pojo = new Pojo();

            parser.nextToken(); // skip START_OBJECT - {
            parser.nextToken(); // skip A name
            parser.nextToken();
            pojo.A = parser.getDoubleValue();
            parser.nextToken(); // skip B name
            parser.nextToken();
            pojo.B = parser.getValueAsString();

            return pojo;
        }
    }
}

class PojoTypeAdapter extends TypeAdapter<Pojo> {

    @Override
    public void write(JsonWriter out, Pojo value) {
        throw new IllegalStateException("Implement me!");
    }

    @Override
    public Pojo read(JsonReader in) throws IOException {
        if (in.peek() == com.google.gson.stream.JsonToken.NULL) {
            in.nextNull();
            return null;
        }

        Pojo pojo = new Pojo();

        in.beginObject();
        in.nextName();
        pojo.A = in.nextDouble();
        in.nextName();
        pojo.B = in.nextString();

        return pojo;
    }
}

class Pojo {

    double A;
    String B;

    @Override
    public String toString() {
        return "Pojo{" +
                "A=" + A +
                ", B='" + B + '\'' +
                '}';
    }
}

Note: if you need really precise data try to create benchmark tests using excellent JMH package.

like image 131
Michał Ziober Avatar answered Nov 14 '22 22:11

Michał Ziober