Подтвердить что ты не робот

Создать экземпляр дочернего объекта с параметрами десериализации родительского объекта с помощью GSON и использовать дженерики?

У меня примерно следующая структура

class MyDeserialParent<T extends MyChildInterface> {

     MyChildInterface mSerialChild;
     ... //some other fields (not 'type')

}

Но это десериализовано из грязной структуры JSON, два свойства потомка возвращаются на родительский узел, как показано ниже.

{
    "myDeserialParents" : [
        {
            ... //some parent properties
            "type": "value", //used in a TypeAdapter to choose child implementation
            "childProp1": "1",
            "childProp2": "2",
         },
         ... //more in this list
     ]
}

Очевидно, это мешает мне просто аннотировать mSerialChild с помощью SerializedName и позволить TypeAdapter чудеса. Итак, я надеюсь, что при десериализации MyDeserialParent использовать "тип", чтобы найти правильный конкретный класс MyChildInterface и создать новый, используя childProp1 и childProp2 качестве параметров для конструктора. Я не знаю, как это сделать.

Я могу представить себе использование TypeAdapter (JsonDeserializer) для MyDeserialParent и в deserialize получить поле типа (а также два дочерних свойства), а затем создать экземпляр правильного бетона для MyChildInterface самостоятельно.

Это значит, что мне нужно создать свой класс MyDeserialParentcontext.deserialize(json, MyDeserialParent.class)) и вызвать сеттер с экземпляром MyChildInterface. Это неправильно, как будто я что-то упустил. Есть ли способ лучше?

Есть ли способ указать универсальные (T на MyDeserialParent), если я вручную создаю родительский объект? или Type Erasure означает, что нет способа сделать это? (Этот вопрос менее важен, потому что я знаю, что могу получить безопасность типов, если использую определенные подтипы MyDeserialParent, которые вместо этого уже выводят T, но я бы хотел этого избежать)

4b9b3361

Ответ 1

Вам явно нужен пользовательский TypeAdapter. Но сложными являются:

  • ваш родительский класс является общим
  • mSerialChild не относится к типу T, а относится к типу MyChildInterface
  • мы хотим избежать разбора вручную json для каждого дочернего класса и иметь возможность добавлять свойства к родительскому, не изменяя весь код.

Помня об этом, я получил следующее решение.

public class MyParentAdapter implements JsonDeserializer<MyDeserialParent>{

    private static Gson gson = new GsonBuilder().create();
    // here is the trick: keep a map between "type" and the typetoken of the actual child class
    private static final Map<String, Type> CHILDREN_TO_TYPETOKEN;

    static{
        // initialize the mapping once
        CHILDREN_TO_TYPETOKEN = new TreeMap<>();
        CHILDREN_TO_TYPETOKEN.put( "value", new TypeToken<MyChild1>(){}.getType() );
    }


    @Override
    public MyDeserialParent deserialize( JsonElement json, Type t, JsonDeserializationContext
            jsonDeserializationContext ) throws JsonParseException{
        try{
            // first, get the parent
            MyDeserialParent parent = gson.fromJson( json, MyDeserialParent.class );
            // get the child using the type parameter
            String type = ((JsonObject)json).get( "type" ).getAsString();
            parent.mSerialChild = gson.fromJson( json, CHILDREN_TO_TYPETOKEN.get( type ) );
            return parent;

        }catch( Exception e ){
            e.printStackTrace();
        }
        return null;
    }
}

замечания:

  • пользовательский адаптер должен быть зарегистрирован на gsonBuilder
  • Если вам нужны пользовательские свойства gson для ваших детей, вы можете передать объект Gson в конструкторе MyParentAdapter, так как сейчас он использует значение по умолчанию;
  • дети и родительский должны иметь атрибуты с разными именами;
  • каждый новый тип должен быть добавлен к карте с соответствующим классом.

Полный пример

Главная:

public class DeserializeExample{

    MyDeserialParent[] myDeserialParents;

    static String json = "{\n" +
            "    \"myDeserialParents\" : [\n" +
            "        {\n" +
            "            \"otherProp\": \"lala\"," +
            "            \"type\": \"value\", //used in a TypeAdapter to choose child implementation\n" +
            "            \"childProp1\": \"1\",\n" +
            "            \"childProp2\": \"2\"\n" +
            "         }\n" +
            "     ]\n" +
            "}";


    public static void main( String[] args ){
        Gson gson = new GsonBuilder().registerTypeAdapter( MyDeserialParent.class, new MyParentAdapter() ).create();
        DeserializeExample result = gson.fromJson( json, DeserializeExample.class );
        System.out.println( gson.toJson( result ));
        // output: 
        // {"myDeserialParents":[{"mSerialChild":{"childProp1":"1","childProp2":"2"},"otherProp":"lala"}]}
    }//end main

}//end class

Родитель:

class MyDeserialParent<T extends MyChildInterface>{

    MyChildInterface mSerialChild;
    //some other fields (not 'type')
    String otherProp;
}

ребенок:

public class MyChild1 implements MyChildInterface {
    String childProp1;
    String childProp2;
}//end class