Simple — полезная Java-библиотека для XML-сериализации/десериализации объектов. В частности, её можно использовать с Android.
Зачем это нужно?
Все мы любим XML и часто его разбираем. То какой-нибудь сервер нам его пришлёт, то в само приложение зашиты какие-нибудь данные в этом формате, то ещё что-нибудь. Разбор XML всегда выливается в написание кучи унылого однообразного кода вот такого вида (ну или такого). С Simple нам нужно только задать правила отображения объектной модели на узлы XML, дальше сериализатор сделает всё сам. Экономим время, делаем меньше ошибок, получаем более читаемый и сопровождаемый код.
И что нам за это будет?
Jar весит полмегабайта, а значит, увеличивает размер выходного приложения.
А ещё Simple работает через Reflection, так что производительность тоже не блещет. Один не особенно страшный, но разветвлённый XML-файл Simple разобрал в 20 раз медленнее, чем стандартный XmlPullParser
.
Так что недостатки тоже есть, а насколько они критичные — решайте сами.
Подключение
В build.gradle
добавляем следующие волшебные строчки в dependencies:
build.gradle
dependencies { … compile('org.simpleframework:simple-xml:2.7.+') { exclude module: 'stax' exclude module: 'stax-api' exclude module: 'xpp3' } }
Собственно, про подключение я пишу только ради этих exclude module. Они вовсе не случайны, их обязательно нужно писать, иначе будет возникать такая странная ошибка:
Gradle: Execution failed for task ':SimpleXmlDemo:dexDebug'. Could not call IncrementalTask.taskAction() on task ':SimpleXmlDemo:dexDebug'
Собираем, Gradle сразу всё скачивает, и библиотеку можно использовать.
Маппинги
Собственно, основное, что надо сделать — написать аннотации к полям объектной модели. Я не буду изображать javadoc и подробно расписывать все аннотации, а просто напишу пример и обращу внимание на некоторые вещи.
Итак, пример. Допустим, есть у нас XML такого вида:
os.xml
<os_reference> <desktop> <os> <title>Windows</title> <company> <name>Microsoft</name> <website>http://www.microsoft.com</website> </company> <version year="2000">Windows ME</version> <version codename="Whistler" year="2001">Windows XP</version> <version codename="Blackcomb" year="2009">Windows 7</version> <version codename="Jupiter" year="2012">Windows 8</version> </os> <os> <title>OS X</title> <company> <name>Apple</name> <website>http://www.apple.com</website> </company> <version codename="Lion" year="2011">10.7</version> <version codename="Mountain Lion" year="2012">10.8</version> <version codename="Mavericks" year="2013">10.9</version> </os> </desktop> <mobile> <os> <title>Android</title> <company> <name>Google</name> <website>http://google.com</website> </company> <version codename="Gingerbread" year="2010">2.3.x</version> <version codename="Ice Cream Sandwich" year="2011">4.0</version> <version codename="Jelly Bean" year="2012">4.1</version> </os> <os> <title>Windows Phone</title> <company> <name>Microsoft</name> <website>http://www.microsoft.com</website> </company> <version codename="Mango" year="2010">7.5</version> <version codename="Tango" year="2011">7.8</version> <version codename="Apollo" year="2012">8</version> </os> </mobile> </os_reference>
Напишем соответствующую объектную модель с аннотациями.
OsList.java
@Root(name="operation_systems") public class OsReference { @ElementList(name="desktop", inline = false) private List<OperatingSystem> desktopSystems; @ElementList(name="mobile", inline = false) private List<OperatingSystem> mobileSystems; }
OperatingSystem.java
@Root(name = "os") public class OperatingSystem { @Element private String title; @Path("company") @Element(name="name") private String companyName; @Path("company") @Element(name="website") private String companySite; @ElementList(inline = false) private List<Version> versions; }
Version.java
@Root(name="version") public class Version { @Attribute(required = false) private String codename; @Attribute private int year; @Text private String title; }
Выглядит довольно логично и очевидно. Однако, обращу внимание на некоторые особенности.
Почему не во всех аннотациях задан параметр name?
Параметр name
имеется почти у всех аннотаций и обозначает имя тега или атрибута. Однако, в приведённом примере мало где он указан. Дело в том, что по умолчанию Simple использует имя поля класса, а у нас они почти везде совпадают с соответствующими им именами элементов XML. Если бы мы использовали гугловые правила именования (с префиксом m
), то пришлось бы указывать name
везде.
Что за параметр inline в @ElementList?
Параметр inline
показывает, где расположены элементы списка — прямо в текущем узле (true
) или в отдельном (false
). В нашем примере в OsReference
имеется два списка в отдельных тегах desktop
и mobile
(у них стоит inline=true
и указаны имена тегов). А вот список версий ОС валяется прямо в узле os
, так что вполне себе inline.
Почему при разборе может возникать ошибка "Element 'yourElement' does not have a match in class your.package.YourClass at line 10"?
Вероятно, парсер нашёл в XML-документе нечто, что ему некуда замапить. Тут одно из двух:
- То, на что парсер ругается, нужно и важно, а мы его забыли. Тогда это нам сигнал, что объектную модель необходимо срочно дописать.
- Данный элемент избыточен и в нашей модели не нужен. Тогда следует найти класс, на который ругается парсер и в аннотации
Root
указать параметрstrict=false
Что это за такой @Path?
Это такой хитрый способ заполучить данные из других узлов. Пишем сюда XPath, относительно которого будем получать элемент/атрибут/etc. В примере мы воспользовались этой возможностью фактически только для того, чтобы не создавать лишнего класса Company
.
Где здесь что-нибудь про CDATA?
Десериализатору от CDATA ни жарко и ни холодно. А вот для сериализации бывает полезно.
Для того, чтобы указать, нужно её ставить или нет, в аннотациях @Text
и @Element
имеется параметр data
. По умолчанию он false
, но, если CDATA нужна, можно поставить ему true
.
Так ли уж сильно нужен @Root?
У меня этот @Root
вызывает большие сомнения. Во нашем случае можно смело его убирать, и разбор всё равно будет работать. Даже если после этого слегка поиздеваться над входным XML и, к примеру, в списке mobile
вместо os
написать mobile_os
, а в desktop
оставить os
— и в этом случае десериализация сработает на ура.
Так что мне пока кажется, эта аннотация опциональная, и использовать её нужно только тогда, когда нужен strict=false
или чтобы задать имя тега для сериализации.
Десериализация с Simple
А вот и весь разбор XML:
private void deserializeObject(InputStream is) { Serializer serializer = new Persister(); try { OsReference result = serializer.read(OsReference.class, is); Log.d(TAG, "Deserializing OK"); } catch (Exception e) { Log.e(TAG, "Error while parsing XML", e); } }
По сравнению с километровыми XmlPullParser-ами весьма лаконично.
Сериализация с Simple
Обратная задача — сгенерировать XML из объектов — возникает в мобильной разработке куда реже. Но если уж возникла, мы сразу гуглим про XmlSerializer и пишем простыни кода не короче, чем для разбора.
А вот для сравнения весь код на Simple:
private void serializeObject() { // Создаём объект OperatingSystem debian = new OperatingSystem("Debian Linux", "Debian", "http://debian.org", new ArrayList<Version>() {{ add(new Version("Lenny", 2009, "5.0")); add(new Version("Squeeze", 2011, "6.0")); add(new Version("Wheezy", 2013, "7.0")); }}); // сериализуем объект Serializer serializer = new Persister(); StringWriter writer = new StringWriter(); try { serializer.write(debian, writer); Log.d(TAG, writer.toString()); } catch (Exception e) { Log.e(TAG, "Error while serializing", e); } }
Всё!
Simple может несколько сэкономить время на написании XML-парсеров/генераторов. Минусы тоже есть, они перечислены в самом начале статьи. Кроме того, не любой XML получится качественно замапить. Но для относительно простых случаев вполне себе ок.
Хорошая статья, спасибо =) но мне не хватило 1-ного важного пункта - порядок.
ОтветитьУдалитьИзучал официальную доку (громоздкая)...
могли бы вы добавить в вашу статью пункт про конфигурирование порядка посcтроеной XML:
" Если вам потребуется задать порядок в вашем XML, то надо использовать аннотацию
@Order(elements = [....])
с перечислением нужного вам порядка. "
Лично я столкнулся с проблемой, что порядок полей в созданной мною модели не соблюдается при построении XML