Как сериализовать класс с интерфейсом? - PullRequest
34 голосов
/ 25 января 2011

Я никогда не делал ничего с сериализацией, но пытаюсь использовать gson от Google для сериализации объекта Java в файл.Вот пример моей проблемы:

public interface Animal {
    public String getName();
}


 public class Cat implements Animal {

    private String mName = "Cat";
    private String mHabbit = "Playing with yarn";

    public String getName() {
        return mName;
    }

    public void setName(String pName) {
        mName = pName;
    }

    public String getHabbit() {
        return mHabbit;
    }

    public void setHabbit(String pHabbit) {
        mHabbit = pHabbit;
    }

}

public class Exhibit {

    private String mDescription;
    private Animal mAnimal;

    public Exhibit() {
        mDescription = "This is a public exhibit.";
    }

    public String getDescription() {
        return mDescription;
    }

    public void setDescription(String pDescription) {
        mDescription = pDescription;
    }

    public Animal getAnimal() {
        return mAnimal;
    }

    public void setAnimal(Animal pAnimal) {
        mAnimal = pAnimal;
    }

}

public class GsonTest {

public static void main(String[] argv) {
    Exhibit exhibit = new Exhibit();
    exhibit.setAnimal(new Cat());
    Gson gson = new Gson();
    String jsonString = gson.toJson(exhibit);
    System.out.println(jsonString);
    Exhibit deserializedExhibit = gson.fromJson(jsonString, Exhibit.class);
    System.out.println(deserializedExhibit);
}
}

Так что это хорошо сериализуется - но по понятным причинам отбрасывает информацию о типе на Animal:

{"mDescription":"This is a public exhibit.","mAnimal":{"mName":"Cat","mHabbit":"Playing with yarn"}}

Это вызывает реальные проблемы при десериализации, хотя:

Exception in thread "main" java.lang.RuntimeException: No-args constructor for interface com.atg.lp.gson.Animal does not exist. Register an InstanceCreator with Gson for this type to fix this problem.

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

Ответы [ 4 ]

79 голосов
/ 04 марта 2012

Вот общее решение, которое работает во всех случаях, когда статически известен только интерфейс.

  1. Создать сериализатор / десериализатор:

    final class InterfaceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> {
        public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
            final JsonObject wrapper = new JsonObject();
            wrapper.addProperty("type", object.getClass().getName());
            wrapper.add("data", context.serialize(object));
            return wrapper;
        }
    
        public T deserialize(JsonElement elem, Type interfaceType, JsonDeserializationContext context) throws JsonParseException {
            final JsonObject wrapper = (JsonObject) elem;
            final JsonElement typeName = get(wrapper, "type");
            final JsonElement data = get(wrapper, "data");
            final Type actualType = typeForName(typeName); 
            return context.deserialize(data, actualType);
        }
    
        private Type typeForName(final JsonElement typeElem) {
            try {
                return Class.forName(typeElem.getAsString());
            } catch (ClassNotFoundException e) {
                throw new JsonParseException(e);
            }
        }
    
        private JsonElement get(final JsonObject wrapper, String memberName) {
            final JsonElement elem = wrapper.get(memberName);
            if (elem == null) throw new JsonParseException("no '" + memberName + "' member found in what was expected to be an interface wrapper");
            return elem;
        }
    }
    
  2. заставьте Gson использовать его для выбранного вами типа интерфейса:

    Gson gson = new GsonBuilder().registerTypeAdapter(Animal.class, new InterfaceAdapter<Animal>())
                                 .create();
    
13 голосов
/ 25 января 2011

Поместите животное как transient, тогда оно не будет сериализовано.

Или вы можете сериализовать его самостоятельно, внедрив defaultWriteObject(...) и defaultReadObject(...) (я думаю, так они назывались ...)

РЕДАКТИРОВАТЬ См. Часть о «Написание создателя экземпляра» здесь .

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

4 голосов
/ 06 февраля 2015

@ Решение Maciek отлично работает, если объявленная переменная-член типа является интерфейсом / абстрактным классом. Это не будет работать, если объявленный тип является подклассом / субинтерфейсом / субабстрактным классом, если мы не зарегистрируем их все через registerTypeAdapter(). Мы можем избежать регистрации по одному с использованием registerTypeHierarchyAdapter, но я понимаю, что это вызовет StackOverflowError из-за бесконечного цикла. (Пожалуйста, прочитайте справочный раздел ниже)

Короче говоря, мое обходное решение выглядит немного бессмысленным, но оно работает без StackOverflowError.

@Override
public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
    final JsonObject wrapper = new JsonObject();
    wrapper.addProperty("type", object.getClass().getName());
    wrapper.add("data", new Gson().toJsonTree(object));
    return wrapper;
}

Я использовал другой новый Gson экземпляр работы в качестве сериализатора / десериализатора по умолчанию, чтобы избежать бесконечного цикла. Недостатком этого решения является то, что вы также потеряете другие TypeAdapter, если у вас есть настраиваемая сериализация для другого типа, и он появляется в объекте, он просто потерпит неудачу.

Тем не менее, я надеюсь на лучшее решение.

Ссылки

Согласно документации Gson 2.3.1 для JsonSerializationContext и JsonDeserializationContext

Вызывает сериализацию по умолчанию для указанного объекта, передавая информацию определенного типа. Он никогда не должен вызываться для элемента, полученного в качестве параметра метода JsonSerializer.serialize (Object, Type, JsonSerializationContext). Это приведет к бесконечному циклу, поскольку Gson, в свою очередь, снова вызовет пользовательский сериализатор.

и

Вызывает десериализацию по умолчанию для указанного объекта. Он никогда не должен вызываться для элемента, полученного в качестве параметра метода JsonDeserializer.deserialize (JsonElement, Type, JsonDeserializationContext). Это приведет к бесконечному циклу, поскольку Gson, в свою очередь, снова вызовет пользовательский десериализатор.

Отсюда следует, что приведенная ниже реализация вызовет бесконечный цикл и в конечном итоге приведет к StackOverflowError.

@Override
public JsonElement serialize(Animal src, Type typeOfSrc,
        JsonSerializationContext context) {
    return context.serialize(src);
}
0 голосов
/ 12 июля 2017

У меня была такая же проблема, за исключением того, что мой интерфейс был примитивного типа (CharSequence), а не JsonObject:

if (elem instanceof JsonPrimitive){
    JsonPrimitive primitiveObject = (JsonPrimitive) elem;

    Type primitiveType = 
    primitiveObject.isBoolean() ?Boolean.class : 
    primitiveObject.isNumber() ? Number.class :
    primitiveObject.isString() ? String.class :
    String.class;

    return context.deserialize(primitiveObject, primitiveType);
}

if (elem instanceof JsonObject){
    JsonObject wrapper = (JsonObject) elem;         

    final JsonElement typeName = get(wrapper, "type");
    final JsonElement data = get(wrapper, "data");
    final Type actualType = typeForName(typeName); 
    return context.deserialize(data, actualType);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...