Разбора и создания в JSON пакетов


Несколько из моих любимых проектов, мне пришлось интерпретировать и/или отправить в JSON, WebSocket, которая и ответов HTTP в Java. Я сделал некоторые исследования и собирали библиотеки Gson для использования. В каждом проекте я реализовал пользовательский десериализатор для того, чтобы правильно интерпретировать различные типы пакетов.

Например:

{"type": "set-name", "data":{"name":"daniel"}}
{"type": "set-timeperiod", "data":{"start":"2017-03-16", "end":"2017-03-17"}}

В этом случае я бы использовал два класса, SetName и SetTimePeriod:

public class SetName implements Data {
    private String name;

    @Override
    public void handle(Foo foo) throws InvalidPacketException {
        if(name == null) throw new InvalidPacketException();
        foo.doThingWithName(name);
    }

    @Override
    public String getType() { return "set-name"; }
}

Foo представляет каких-либо произвольных ресурсов, пакет, возможно, придется выполнять свои функции. Интерфейс достаточно прост:

public interface Data {
    void handle(Foo foo);
    String getType();
}

В Data объекты упакованы в пакет, представляющий целиком:

public void Packet {
    private String type;
    private Data data;

    public Packet(Data data) {
        this.data = data;
        type = data.getType();
    }

    public void handle(Foo foo) throws InvalidPacketException { data.handle(foo); }

    public Packet initData(Data data) {
        if(this.data != null) throw new IllegalStateException("Data already initialised.");
        this.data = data;
        return this;
    }
}

И, наконец, пакет будет преобразован из сырые JSON к экземпляру Packet используя пользовательский десериализатор.

public class PacketDeserializer implements JsonDeserializer<Packet> {

    @Override
    public Packet deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
        throws JsonParseException {
        JsonObject jsonObject = json.getAsJsonObject();
        // Remove data object, GSON won't know what to turn it into.
        JsonObject dataObject = jsonObject.remove("data").getAsJsonObject();
        // Determine what type of data object we need.
        Class dataClass = typeToClass(jsonObject.get("type").getAsString());
        // Construct and return a Packet of the correct type.
        Data data = context.deserialize(dataObject, dataClass);
        return new Gson().fromJson(jsonObject, Packet.class).initData(data);
    }

    private Class<? extends Data> typeToClass(String type) throws JsonParseException {
        switch(type) {
            case("set-name"): return SetName.class;
            case("set-timeperiod"): return SetTimePeriod.class;
            default: throw new JsonParseException("Type " + type + " is invalid.");
        }
    }
}

Это означает ДСЫН теперь десериализовать объект в Packet а затем вызвать handle() делать все, что необходимо сделать с этого пакета:

Packet packet = new GsonBuilder()
    .registerTypeAdapter(Packet.class, new PacketDeserializer())
    .create()
    .fromJson(message, Packet.class);
packet.handle(foo);

Кроме того, Packet экземпляр может быть преобразован в JSON без каких-либо пользовательский сериализатор. Как только эта структура построена, это очень легко добавлять новые типы пакетов или отправить/разбираем пакеты.

Это выглядит как элегантное решение для меня, но у меня есть некоторые опасения:

  • Это на самом деле эффективный? Это было бы намного быстрее, чтобы парсить JSON напрямую?
  • Я в конечном итоге с много подклассов Data поэтому мне кажется, что там может быть лучший способ делать это
  • Если это хорошее решение, было бы хорошо использовать там, где я разбора JSON объект с различным содержанием, как data?

Я придумал это решение лет назад, когда я довольно новыми для программирования, и не он сильно изменился, поэтому я чувствую, что я должен быть делаешь что-то неправильно.



312
7
задан 16 марта 2018 в 08:03 Источник Поделиться
Комментарии
2 ответа

У меня есть несколько замечаний:


  • Передача данных объектов, что Дсын работы, поля могут быть final и удерживайте nulls по умолчанию (с некоторыми другими замечаниями к примитивным типам, которые требуют своего рода жульничество в javac).

  • Эти объекты передачи данных также может иметь личное конструкторы, потому что Дсын не нуждается. Этот и предыдущий пункты просто меньше ошибок (вы не можете ни создать такой входящий ДТО вручную, ни изменение ее состояния).

  • Вам не нужно Packet.initData.

  • SetName и SetTimePeriod сделать подобные вещи, и может быть преобразована в общий супер-класс, который реализует шаблонный метод шаблон.

  • У меня есть ощущение, что messsage является строкой. Предпочитаете поток-ориентированных классов, как InputStream, Reader и JsonReader для того, чтобы избежать ненужных буферизации (не очень подходят для вашего случая, ведь у вас еще есть JsonElement (это буфер на ЮВ) в свой десериализатор) - это будет прекрасно работать для больших объектов.

  • Также рассмотрите возможность извлечения Gson экземпляр просто static final поля. Gson является неизменяемым и потокобезопасным, поэтому он может быть легко совместно использоваться несколькими потоками.

  • PacketDeserializer нет ни одной государственной и сделал синглтон без каких-либо негативных последствий. Рассмотреть как можно больше прячет и конструкторов.

  • Имея это в виду, вы можете вернуться самый супер-типа, как вы можете: public static JsonDeserializer<Packet> get() { ... }. Это позволит вам не беспокоиться за конкретных типов, используемых в Call-сайте.

  • Изменения аргументов-это плохая практика делает много сюрпризов для тех, кто не ожидал такого поведения: jsonObject.remove("data") (кроме того, это просто лишнее и не влияет на вас).

  • Вы не должны создавать Gson случаи, в сериализаторы и десериализаторы и делать все такие вещи через контексты только. Во-первых, Gson экземпляры являются относительно дорогими, чтобы инстанцировать. Кроме того, такой Gson объект может иметь совершенно разные конфигурации, другие, что "глобальный" Gson экземпляр был. Однако, я подозреваю, что вы сделали этот выбор для того, чтобы избежать бесконечной рекурсии.

  • PacketDeserializer.typeToClass могут быть статическими.

  • switchES с ( и ) caseы выглядят необычно.

  • Кстати, строку switchЭс совершенно нормально для такого рода сопоставления.

  • Packet не нужно type.

Как результат:

final class InvalidPacketException
extends IOException {
}

interface Data {

void handle(Foo foo)
throws InvalidPacketException;

String getType();

}

abstract class AbstractData
implements Data {

private transient String type;

protected AbstractData(final String type) {
this.type = type;
}

protected abstract boolean isValid();

protected abstract void doHandle(Foo foo);

@Override
public final void handle(final Foo foo)
throws InvalidPacketException {
if ( !isValid() ) {
throw new InvalidPacketException();
}
doHandle(foo);
}

@Override
public final String getType() {
return type;
}

}

final class SetName
extends AbstractData {

private final String name = null;

private SetName() {
super("set-name");
}

@Override
protected boolean isValid() {
return name != null;
}

@Override
protected void doHandle(final Foo foo) {
foo.doThingWithName(name);
}

}

final class SetTimePeriod
extends AbstractData {

private final Date start = null;
private final Date end = null;

private SetTimePeriod() {
super("set-timeperiod");
}

@Override
protected boolean isValid() {
return start != null && end != null;
}

@Override
protected void doHandle(final Foo foo) {
foo.doThingWithStartAndEnd(start, end);
}

}

final class Packet {

private final Data data;

private Packet(final Data data) {
this.data = data;
}

static Packet of(final Data data) {
return new Packet(data);
}

void handle(final Foo foo)
throws InvalidPacketException {
data.handle(foo);
}

}

final class PacketDeserializer
implements JsonDeserializer<Packet> {

private static final JsonDeserializer<Packet> instance = new PacketDeserializer();

private PacketDeserializer() {
}

static JsonDeserializer<Packet> get() {
return instance;
}

@Override
public Packet deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
final JsonElement dataJsonElement = jsonObject.get("data");
final Type dataType = resolveType(jsonObject.get("type").getAsString());
final Data data = context.deserialize(dataJsonElement, dataType);
return Packet.of(data);
}

private static Type resolveType(final String type)
throws JsonParseException {
switch ( type ) {
case "set-name":
return SetName.class;
case "set-timeperiod":
return SetTimePeriod.class;
default:
throw new JsonParseException("Type " + type + " is invalid.");
}
}

}

final class Foo {

void doThingWithName(final String name) {
System.out.println("NAME: " + name);
}

void doThingWithStartAndEnd(final Date start, final Date end) {
System.out.println("START: " + start + "; END:" + end);
}

}

private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Packet.class, PacketDeserializer.get())
.create();

public static void main(final String... args)
throws IOException {
final Foo foo = new Foo();
for ( final String name : ImmutableList.of("name.json", "timeperiod.json") ) {
// I'm just reading these as JsonReader-ed resources from the package the Q189789 class is nested at
try ( final JsonReader jsonReader = Resources.getPackageResourceJsonReader(Q189789.class, name) ) {
final Packet packet = gson.fromJson(jsonReader, Packet.class);
packet.handle(foo);
}
}
}

Пример вывода:

NAME: daniel
START: Thu Mar 16 00:00:00 EET 2017; END:Fri Mar 17 00:00:00 EET 2017

Дсын также имеет очень удобный тип адаптер для вашего случая. Обратите внимание, что это пример и адаптер не в комплекте с Gson. Вы могли просто включить его в свой код.

2
ответ дан 2 мая 2018 в 03:05 Источник Поделиться

Быстрый мозгу свалка:


  • PacketDeserializer#deserialize Это overcommented. Большинство комментариев повторяется то, о чем говорится в Кодексе. Попробуйте оставить как можно меньше, но столько, сколько надо.

  • PacketDeserializer можно воспользоваться не хардкодить сопоставление между типом строки и классы. Рассмотрим прохождение Map<String, Class<? extends Data>> как параметр конструктора и замена typeToClass С map.get

  • Наконец, вы должны также избегать rawtypes. Class без дженериков спецификация является rawtype.

Продолжая эту мысль, вы действительно хотите другое имя, чем Foo для "PacketHandler" вы проходите в Packet#handle. Вы должны рассмотреть так далеко, чтобы извлечь на себя ответственность обработки пакета в отдельный класс. Пакет не должен знать, как обращаться с себя. Это изначально просто данные-держатель, ничего умного :)

4
ответ дан 18 марта 2018 в 12:03 Источник Поделиться