Efektywny algorytm ręcznego parsowania json

0

Potrzebuję parsować taki content z api:

{"full_count":12266,"version":4,"214e409c":["70C778",17.0480,54.1086,271,0,0,"0000","F-OOSA2","","",1565517192,"SLL","","",1,0,"",0,""]
,"214e409e":["70C77A",17.0332,54.0834,54,0,0,"0000","F-OOSA2","","",1565517191,"SLL","","",1,0,"",0,""]
,"216a0374":["71D423",37.4558,126.4807,180,0,0,"0000","F-RKSI1","","",1565517191,"ICN","","",1,0,"",0,""]
,"216cb337":["896E0C",24.4206,54.6598,99,0,0,"3443","T-OMAA4","","",1565517188,"","","",1,0,"TTS1",0,""] 
}

Parser w javie:

package com.hello.nope.app.Utils;

import com.hello.nope.app.Entities.FlightInfo;
import com.hello.nope.app.Entities.Radar;

import java.util.ArrayList;
import java.util.List;


public class RadarParser {

    public static Radar parseJsonToRadarObj(String jsonData) {
        int allAirPlanes = Integer.parseInt(jsonData.substring(jsonData.indexOf("\"full_count\":")+13, jsonData.indexOf(",\"")));
        int version = Integer.parseInt(jsonData.substring(jsonData.indexOf("\"version\"")+10, jsonData.indexOf(",\"", jsonData.indexOf("\"version\""))));
        String[] lines = null;

        if(jsonData.endsWith("] }"))
        {
            jsonData = jsonData.substring(jsonData.indexOf(",\"", jsonData.indexOf(",\"") + 1) + 1, jsonData.indexOf("] }")).replace(":[", ",");
            lines = jsonData.split("] ,");
        }
        return buildRadarObj(allAirPlanes, version, lines);
    }

    private static Radar buildRadarObj(int allAirPlanes, int version, String[] lines) {
        List<FlightInfo> flightInfoList = new ArrayList<>();
        String[] params;
        for(String l : lines) {
            params = l.split(",");
            for(int i=0;i<params.length; ++i) {
                params[i] = params[i].replace("\"\"", "0");
                params[i] = params[i].replace("\"", "");
            }

            flightInfoList.add(new FlightInfo(params[0], params[1], Double.parseDouble(params[2]), Double.parseDouble(params[3]),
                    Integer.parseInt(params[4]), Integer.parseInt(params[5]), Integer.parseInt(params[6]), Integer.parseInt(params[7]),
                    params[8], params[9], params[10], Integer.parseInt(params[11]),
                    params[12], params[13], params[14], Integer.parseInt(params[15]),
                    Integer.parseInt(params[16]), params[17], Integer.parseInt(params[18]), params[19]));
        }
        return new Radar(allAirPlanes, version, flightInfoList);
    }
}

Radar.java

package com.hello.nope.app.Entities;

import java.util.List;

public class Radar {
    private int allAirPlanes;
    private int version;
    private List<FlightInfo> flightInfoList;

    public Radar(int allAirPlanes, int version, List<FlightInfo> flightInfoList) {
        this.allAirPlanes = allAirPlanes;
        this.version = version;
        this.flightInfoList = flightInfoList;
    }

    public int getAllAirPlanes() {
        return allAirPlanes;
    }

    public void setAllAirPlanes(int allAirPlanes) {
        this.allAirPlanes = allAirPlanes;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public List<FlightInfo> getFlightInfoList() {
        return flightInfoList;
    }

    public void setFlightInfoList(List<FlightInfo> flightInfoList) {
        this.flightInfoList = flightInfoList;
    }

    @Override
    public String toString() {
        return "Radar{" +
                "allAirPlanes=" + allAirPlanes +
                ", version=" + version +
                ", flightInfoList=" + flightInfoList +
                '}';
    }
}

FlightInfo.java

package com.hello.nope.app.Entities;

public class FlightInfo {
    private String id;
    private String icao24bitAddr;
    private Double actualLatitude;
    private Double actualLongitude;
    private Integer trackDirectionAircraft;
    private Integer altitude;
    private Integer speed;
    private Integer someValue1;
    private String someValue2;
    private String airCraftType;
    private String registrationNumber;
    private Integer someValue3;
    private String countryFrom;
    private String countryTo;
    private String trackFlight;
    private Integer someValue4;
    private Integer someValue5;
    private String airLine;
    private Integer someValue6;
    private String airLineShortName;

    public FlightInfo(String id, String icao24bitAddr, Double actualLatitude, Double actualLongitude, Integer trackDirectionAircraft, Integer altitude, Integer speed, Integer someValue1, String someValue2, String airCraftType, String registrationNumber, Integer someValue3, String countryFrom, String countryTo, String trackFlight, Integer someValue4, Integer someValue5, String airLine, Integer someValue6, String airLineShortName) {
        this.id = id;
        this.icao24bitAddr = icao24bitAddr;
        this.actualLatitude = actualLatitude;
        this.actualLongitude = actualLongitude;
        this.trackDirectionAircraft = trackDirectionAircraft;
        this.altitude = altitude;
        this.speed = speed;
        this.someValue1 = someValue1;
        this.someValue2 = someValue2;
        this.airCraftType = airCraftType;
        this.registrationNumber = registrationNumber;
        this.someValue3 = someValue3;
        this.countryFrom = countryFrom;
        this.countryTo = countryTo;
        this.trackFlight = trackFlight;
        this.someValue4 = someValue4;
        this.someValue5 = someValue5;
        this.airLine = airLine;
        this.someValue6 = someValue6;
        this.airLineShortName = airLineShortName;
    }

    public String getIcao24bitAddr() {
        return icao24bitAddr;
    }

    public void setIcao24bitAddr(String icao24bitAddr) {
        this.icao24bitAddr = icao24bitAddr;
    }

    public Double getActualLatitude() {
        return actualLatitude;
    }

    public void setActualLatitude(Double actualLatitude) {
        this.actualLatitude = actualLatitude;
    }

    public Double getActualLongitude() {
        return actualLongitude;
    }

    public void setActualLongitude(Double actualLongitude) {
        this.actualLongitude = actualLongitude;
    }

    public Integer getTrackDirectionAircraft() {
        return trackDirectionAircraft;
    }

    public void setTrackDirectionAircraft(Integer trackDirectionAircraft) {
        this.trackDirectionAircraft = trackDirectionAircraft;
    }

    public Integer getAltitude() {
        return altitude;
    }

    public void setAltitude(Integer altitude) {
        this.altitude = altitude;
    }

    public Integer getSpeed() {
        return speed;
    }

    public void setSpeed(Integer speed) {
        this.speed = speed;
    }

    public Integer getSomeValue1() {
        return someValue1;
    }

    public void setSomeValue1(Integer someValue1) {
        this.someValue1 = someValue1;
    }

    public String getSomeValue2() {
        return someValue2;
    }

    public void setSomeValue2(String someValue2) {
        this.someValue2 = someValue2;
    }

    public String getAirCraftType() {
        return airCraftType;
    }

    public void setAirCraftType(String airCraftType) {
        this.airCraftType = airCraftType;
    }

    public String getRegistrationNumber() {
        return registrationNumber;
    }

    public void setRegistrationNumber(String registrationNumber) {
        this.registrationNumber = registrationNumber;
    }

    public Integer getSomeValue3() {
        return someValue3;
    }

    public void setSomeValue3(Integer someValue3) {
        this.someValue3 = someValue3;
    }

    public String getCountryFrom() {
        return countryFrom;
    }

    public void setCountryFrom(String countryFrom) {
        this.countryFrom = countryFrom;
    }

    public String getCountryTo() {
        return countryTo;
    }

    public void setCountryTo(String countryTo) {
        this.countryTo = countryTo;
    }

    public String getTrackFlight() {
        return trackFlight;
    }

    public void setTrackFlight(String trackFlight) {
        this.trackFlight = trackFlight;
    }

    public Integer getSomeValue4() {
        return someValue4;
    }

    public void setSomeValue4(Integer someValue4) {
        this.someValue4 = someValue4;
    }

    public Integer getSomeValue5() {
        return someValue5;
    }

    public void setSomeValue5(Integer someValue5) {
        this.someValue5 = someValue5;
    }

    public String getAirLine() {
        return airLine;
    }

    public void setAirLine(String airLine) {
        this.airLine = airLine;
    }

    public Integer getSomeValue6() {
        return someValue6;
    }

    public void setSomeValue6(Integer someValue6) {
        this.someValue6 = someValue6;
    }

    public String getAirLineShortName() {
        return airLineShortName;
    }

    public void setAirLineShortName(String airLineShortName) {
        this.airLineShortName = airLineShortName;
    }

    public String getId() {
        return id;
    }

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

    @Override
    public String toString() {
        return "FlightInfo{" +
                "id='" + id + '\'' +
                ", icao24bitAddr='" + icao24bitAddr + '\'' +
                ", actualLatitude=" + actualLatitude +
                ", actualLongitude=" + actualLongitude +
                ", trackDirectionAircraft=" + trackDirectionAircraft +
                ", altitude=" + altitude +
                ", speed=" + speed +
                ", someValue1=" + someValue1 +
                ", someValue2='" + someValue2 + '\'' +
                ", airCraftType='" + airCraftType + '\'' +
                ", registrationNumber='" + registrationNumber + '\'' +
                ", someValue3=" + someValue3 +
                ", countryFrom='" + countryFrom + '\'' +
                ", countryTo='" + countryTo + '\'' +
                ", trackFlight='" + trackFlight + '\'' +
                ", someValue4=" + someValue4 +
                ", someValue5=" + someValue5 +
                ", airLine='" + airLine + '\'' +
                ", someValue6=" + someValue6 +
                ", airLineShortName='" + airLineShortName + '\'' +
                '}';
    }
}

Wszystko działa bez zarzutów, ale czy jest to efektywne?
Proszę o pomoc co mógłbym tutaj zmienić.
W niedalekiej przyszłości dodam builder pattern do klasy FlightInfo żeby takiego byka nie było na końcu więc na to new FlightInfo(...) proszę patrzeć z przymrużeniem oka.

3

Ja bym teraz porównał to z istniejacymi szybkimi parserami jsona. Bo przeczuwam że nie będą specjalnie wolniejsze, a nie trzeba ich utrzymywać.

0
Shalom napisał(a):

Ja bym teraz porównał to z istniejacymi szybkimi parserami jsona. Bo przeczuwam że nie będą specjalnie wolniejsze, a nie trzeba ich utrzymywać.

Jakie byś polecił?
Używałem gson, ale nie radził sobie z tym (albo ja nie radziłem sobie z tym żeby go ogarnąć)
https://data-live.flightradar24.com/zones/fcgi/feed.js?bounds=
Gdzie lista się zmienia, czasem jest więcej samolotów czasem mniej, czasem nie ma w ogóle

0
Charles_Ray napisał(a):

Spróbuj https://www.baeldung.com/jackson-object-mapper-tutorial

Dzięki, spróbuję, ale nie wiem czy uda mi się sparsować tak z automatu to, bo te id'ki są różne, w sensie full_count i version pozostają te same, ale
214e409c
214e409e
to są identyfikatory lotów i nie mają one stałych nazw, mogą być inne w zależności od aktualnej lokalizacji, a może ich wcale nie być

5555.png

0

Wcześniej miałem coś takiego (mega brzydki kod)

            String json = document.body().text();

           radar = new Gson().fromJson(json, Radar.class);
           if(!StringOperations.isJsonEmpty(json)) {
               List<String> flightInfoList = StringOperations.parseArray(json);
               Map<String, JSONArray> flightInfoArrayList = new HashMap<>();
               List<FlightInfo> flightInfoListObject = new ArrayList<>();
               Gson gson = new Gson();
               JSONObject obj = new JSONObject(json);

               for (String flightNumber : flightInfoList)
                   flightInfoArrayList.put(flightNumber, obj.getJSONArray(flightNumber));

               for (Map.Entry<String, JSONArray> entry : flightInfoArrayList.entrySet()){
                   String[] info = gson.fromJson(entry.getValue().toString(), String[].class);

                   FlightInfo flightInfo = new FlightInfo(entry.getKey(), info[0], Double.parseDouble(info[1]),
                           Double.parseDouble(info[2]), Integer.parseInt(info[3]), Integer.parseInt(info[4]),
                           Integer.parseInt(info[5]), Integer.parseInt(info[6]), info[7], info[8],
                           info[9], Integer.parseInt(info[10]), info[11], info[12], info[13], Integer.parseInt(info[14]),
                           Integer.parseInt(info[15]), info[16], Integer.parseInt(info[17]), info[18]);
                   flightInfoListObject.add(flightInfo);
               }
               radar.setFlightInfoList(flightInfoListObject);
           }

ale potrzebowałem dopisać StringOperations.parseArray żeby to śmigało

import java.util.ArrayList;
import java.util.List;

public class StringOperations {
    public static List<String> parseArray(String jsonData){
        String word = ",\"";
        String word2 = "\"] ,\"";
        int j=-1;

        for(int i=0;i<2;i++){
            j = jsonData.indexOf(word, j+1);
        }
        List<String> resultList = new ArrayList<>();
        resultList.add(jsonData.substring(j+2, j+10));

        for (int i = -1; (i = jsonData.indexOf(word2, i + 1)) != -1; i++) {
            resultList.add(jsonData.substring(i+5, i+13));
        }
        return resultList;
    }

    public static boolean isJsonEmpty(String json){
        if(json.endsWith("\"version\":4}"))
            return true;
        else return false;
    }
}

lepsze jest to z gson + zalążek ręcznego parsowania czy samo ręczne parsowanie, czy może spróbować z https://www.baeldung.com/jackson-object-mapper-tutorial ?

1

Teraz się zorientowałem, że klucze są dynamiczne. Zastanów się, jak zachowa się Twój program, kiedy dostaniesz na wejściu ogromnego jsona zawierającego bardzo dużo kluczy. Jeśli chcesz być wydajny (i nie wywalić się na OOME) zastanów się:

  1. Czy nie da rady tego stronicować?
  2. Przetwarzaj dokument strumieniowo, zamiast ładować całość do pamięci i parsować. https://www.baeldung.com/jackson-streaming-api
0
Charles_Ray napisał(a):

Teraz się zorientowałem, że klucze są dynamiczne. Zastanów się, jak zachowa się Twój program, kiedy dostaniesz na wejściu ogromnego jsona zawierającego bardzo dużo kluczy. Jeśli chcesz być wydajny (i nie wywalić się na OOME) zastanów się:

  1. Czy nie da rady tego stronicować?
  2. Przetwarzaj dokument strumieniowo, zamiast ładować całość do pamięci i parsować. https://www.baeldung.com/jackson-streaming-api
  1. Mam pewność, że kluczy nie będzie wiele, bo pobierane są samoloty z okolicy najbliższych powiedzmy 100 km, na tym obszarze raczej nie wiem czy byłaby taka sytuacja gdzie w powietrzu znajdowało by się ze 50 samolotów

  2. Chyba parsowanie ręczne mnie nie mija, bo i tak muszę wyciągać jakoś klucze (jak źlę mówie to proszę mnie poprawić).

0

Tak, IMO trzeba narzeźbić. Przynajmniej ja nie umiem lepiej :)

1

Parsowanie JSONa moża zrobić dwufazowo - najpierw sparsować do JSON AST, a potem dopiero do np anemicznych klas POJO. Gotowe rozwiązanie (korzystam z klasy FlightInfo którą podałeś):

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;

import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class JacksonTwoPhase {
    public static void main(String[] args) throws Exception {
        URL resource =
                JacksonTwoPhase.class.getResource("/json/flight_radar.json");
        String input = IOUtils.toString(resource, StandardCharsets.UTF_8);
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(input);
        int fullCount = jsonNode.get("full_count").intValue();
        System.out.println("fullCount = " + fullCount);
        int version = jsonNode.get("version").intValue();
        System.out.println("version = " + version);
        List<FlightInfo> flightInfos = new ArrayList<>();
        jsonNode.fields().forEachRemaining(entry -> {
            String key = entry.getKey();
            try {
                // jeśli key da się sparsować to znaczy, że value to FlightInfo
                int id = Integer.parseInt(key, 16);
                Iterator<JsonNode> elems = entry.getValue().elements();
                FlightInfo flightInfo = new FlightInfo(
                        key,
                        elems.next().textValue(),
                        elems.next().doubleValue(),
                        elems.next().doubleValue(),
                        elems.next().intValue(),
                        elems.next().intValue(),
                        elems.next().intValue(),
                        elems.next().intValue(),
                        elems.next().textValue(),
                        elems.next().textValue(),
                        elems.next().textValue(),
                        elems.next().intValue(),
                        elems.next().textValue(),
                        elems.next().textValue(),
                        elems.next().textValue(),
                        elems.next().intValue(),
                        elems.next().intValue(),
                        elems.next().textValue(),
                        elems.next().intValue(),
                        elems.next().textValue()
                );
                flightInfos.add(flightInfo);
            } catch (NumberFormatException nfe) {
                //
            }
        });
        flightInfos.forEach(System.out::println);
    }
}

Uwaga: kod tylko w celu demonstracyjnym! Proszę się na nim nie wzorować :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1