2014-05-16 7 views
4

Я хочу иметь пользовательскую GSON десериализатор таким образом, что всякий раз, когда он десериализации JSON объекта (то есть что-то внутри фигурные скобки { ... }), он будет искать для $type узла и десериализацию, используя его встроенные возможности десериализации к этому типу. Если объект $type не найден, он просто делает то, что он делает.Gson пользовательских сериализация

Так, например, я бы хотел, чтобы это работало:

{ 
    "$type": "my.package.CustomMessage" 
    "payload" : { 
     "$type": "my.package.PayloadMessage", 
     "key": "value" 
    } 
} 

public class CustomMessage { 

    public Object payload; 
} 

public class PayloadMessage implements Payload { 

    public String key; 
} 

Призвание: Object customMessage = gson.fromJson(jsonString, Object.class).

Так в настоящее время, если я изменю payload типа к интерфейсу Payload:

public class CustomMessage { 

    public Payload payload; 
} 

Тогда следующий TypeAdapaterFactory будет делать то, что я хочу:

final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); 
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class); 
final PojoTypeAdapter thisAdapter = this; 

public T read(JsonReader reader) throws IOException { 

    JsonElement jsonElement = (JsonElement)elementAdapter.read(reader); 

    if (!jsonElement.isJsonObject()) { 
     return delegate.fromJsonTree(jsonElement); 
    } 

    JsonObject jsonObject = jsonElement.getAsJsonObject(); 
    JsonElement typeElement = jsonObject.get("$type"); 

    if (typeElement == null) { 
     return delegate.fromJsonTree(jsonElement); 
    } 

    try { 
     return (T) gson.getDelegateAdapter(
       thisAdapter, 
       TypeToken.get(Class.forName(typeElement.getAsString()))).fromJsonTree(jsonElement); 
    } catch (ClassNotFoundException ex) { 
     throw new IOException(ex.getMessage()); 
    } 
} 

Однако, я хотел бы, чтобы работать, когда payload имеет тип Object или любой другой тип, и бросает какое-то исключение соответствия типа, если оно не может назначить переменную.

+0

Не могли бы вы рассказать о том, что не работает с вашим решением? –

+0

@DamianWalczak - он работает, чтобы его десериализовать в 'CustomMessage', но на самом деле он не попал в typeadapterfactory для полезной нагрузки ... он просто использует встроенную десериализацию – Cheetah

ответ

2

Глядя на источник Gson, я нашел то, что я думаю, что это вопрос:

// built-in type adapters that cannot be overridden 
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY); 
factories.add(ObjectTypeAdapter.FACTORY); 

// user's type adapters 
factories.addAll(typeAdapterFactories); 

Как вы можете видеть, ObjectTypeAdapter будет иметь приоритет над моей фабрикой.

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

2

Я не знаю, как вы можете добиться этого с помощью Gson, но у вас есть такая функция в Genson по умолчанию.

Чтобы включить это просто сделать:

Genson genson = new Genson.Builder().setWithClassMetadata(true).create(); 

Вы также можете зарегистрировать псевдонимы для ваших имен классов:

Genson genson = new Genson.Builder().addAlias("myClass", my.package.SomeClass.class).create(); 

Это, однако, имеет некоторые ограничения:

  • в данный момент вы не может изменить ключ, используемый для идентификации типа, это @class
  • он должен присутствовать в вашем JSON перед другими свойствами - но выглядит хорошо, как это имеет место в ваших примерах
  • работает только с объектами JSon и не массивы или litterals
+0

Я предполагаю, что дженерики не будут работать таким образом ? например «Map » – vach

+0

Эта функция в основном предназначена для использования в pojos с наследованием. Но, вероятно, можно было бы расширить и работу с картами (это не связано с дженериками, а скорее с представлением json). – eugen

0

Этот скелет кода работает на вашем примере но должны быть улучшены и протестированы с различными сценариями.

public class PojoTypeAdapaterFactory implements TypeAdapterFactory { 

    @Override 
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) { 
     // check types we support 
     if (type.getRawType().isAssignableFrom(CustomMessage.class) || type.getRawType().isAssignableFrom(PayloadMessage.class)) { 
      return new PojoTypeAdapter<T>(gson, type); 
     } 
     else return null; 
    } 

    private class PojoTypeAdapter<T> extends TypeAdapter<T> { 

     private Gson gson; 

     private TypeToken<T> type; 

     private PojoTypeAdapter(final Gson gson, final TypeToken<T> type) { 
      this.gson = gson; 
      this.type = type; 
     } 

     public T read(JsonReader reader) throws IOException { 
      final TypeAdapter<T> delegate = gson.getDelegateAdapter(PojoTypeAdapaterFactory.this, this.type); 
      final TypeAdapter<JsonElement> elementAdapter = this.gson.getAdapter(JsonElement.class); 
      JsonElement jsonElement = elementAdapter.read(reader); 

      if (!jsonElement.isJsonObject()) { 
       return (T) this.gson.getAdapter(JsonElement.class).fromJsonTree(jsonElement); 
      } 

      JsonObject jsonObject = jsonElement.getAsJsonObject(); 
      JsonElement typeElement = jsonObject.get("$type"); 

      if (typeElement == null) { 
       return delegate.fromJsonTree(jsonElement); 
      } 

      try { 
       final Class myClass = Class.forName(typeElement.getAsString()); 
       final Object myInstance = myClass.newInstance(); 
       final JsonObject jsonValue = jsonElement.getAsJsonObject().get("value").getAsJsonObject(); 
       for (Map.Entry<String, JsonElement> jsonEntry : jsonValue.entrySet()) { 
        final Field myField = myClass.getDeclaredField(jsonEntry.getKey()); 
        myField.setAccessible(true); 
        Object value = null; 
        if (jsonEntry.getValue().isJsonArray()) { 
         //value = ...; 
        } 
        else if (jsonEntry.getValue().isJsonPrimitive()) { 
         final TypeAdapter fieldAdapter = this.gson.getAdapter(myField.getType()); 
         value = fieldAdapter.fromJsonTree(jsonEntry.getValue()); 
        } 
        else if (jsonEntry.getValue().isJsonObject()) { 
         value = this.fromJsonTree(jsonEntry.getValue()); 
        } 
        myField.set(myInstance, value); 
       } 
       return (T) myInstance; 

      } 
      catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InstantiationException | NoSuchFieldException | SecurityException e) { 
       throw new IOException(e); 
      } 
     } 

     @Override 
     public void write(final JsonWriter out, final T value) throws IOException { 
      out.beginObject(); 
      out.name("$type"); 
      out.value(value.getClass().getName()); 
      out.name("value"); 
      final TypeAdapter<T> delegateAdapter = (TypeAdapter<T>) this.gson.getDelegateAdapter(PojoTypeAdapaterFactory.this, TypeToken.<T>get(value.getClass())); 
      delegateAdapter.write(out, value); 
      out.endObject(); 
     } 
    } 

} 

Сформированный JSON не точно такой же, хотя, так как он содержит дополнительный value запись:

{ 
    "$type": "my.package.CustomMessage", 
    "value": { 
    "payload": { 
     "$type": "my.package.PayloadMessage", 
     "value": { 
     "key": "hello" 
     } 
    } 
    } 
} 
Смежные вопросы