- Привет, Амиго!
- Привет, Билаабо!
- Рад тебя видеть. Сегодня у нас небольшое, но очень познавательное занятие. Сегодня я расскажу тебе о языке JavaScript.
- Новый язык? Как интересно…
- JavaScript сейчас популярен благодаря интернету. Дело в том, что это единственный язык, который умеют исполнять все браузеры. Если ты хочешь добавить анимацию или некоторую логику в свою интернет-страницу, то ты можешь сделать это с помощью JavaScript.
- А правда, что JavaScript – самый популярный язык?
- Да, но тут правильнее было бы сказать, что он самый популярный «второй язык». Программисты на C++, Java, C#, PHP вынуждены писать небольшие скрипты на JavaScript, чтобы оживить свои интернет-страницы. В то же время, людей, которые пишут только на JavaScript гораздо меньше.
- А почему у него такое название – JavaScript? Звучит почти как Java.
- На самом деле сначала он назывался LiveScript, но когда Java начала набирать популярность, его переименовали в JavaScript.
Java и JavaScript - это два совершенно разных языка, не стоит их путать.
JavaScript, в отличие от Java, не имеет классов, не поддерживает статическую типизацию, многопоточность и много чего еще. И если Java – это скорее большой набор строительных инструментов, то JavaScript – это швейцарский нож. JavaScript создан для решения маленьких задач, а Java – для больших и очень больших.
Вот тебе несколько фактов о JavaScript:
Факт первый – в JavaScript есть функции, но нет классов.
Вы просто пишите логику программы в нескольких функциях и все. Пример:
JavaScript | Java |
---|---|
function min(a, b) { return a<b ? a: b; } |
public static int min(int a, int b) { return a<b ? a: b; } |
Новые функции объявляются с помощью конструкции «function+имя».
Еще пример:
JavaScript | Java |
---|---|
function min(a, b) { return a<b ? a: b; } function main() { var s = 3; var t = 5; var min = min(s, t); } |
public static int min(int a, int b) { return a<b ? a: b; } public static void main() { int s = 3; int t = 5; int min = min(s,t); } |
Факт второй – в JavaScript есть переменные, но у них нет типов.
JavaScript – это язык с динамической типизацией. Это значит, что у переменных, по факту, нет типов. Любой переменной можно присвоить значение любого типа (у значений типы есть). Пример:
JavaScript | Java |
---|---|
function main() { var s = "Bender"; var k = 1; var n = s.length; } |
public static void main() { String s ="Bender"; int k = 1; int n = s.length(); } |
Но динамическая типизация увеличивает риск ошибок во время работы программы:
JavaScript | Java |
---|---|
function main() { var s = "Bender"; var k = 1; var n = k.length; } |
public static void main() { String s ="Bender"; int k = 1; int n = k.length(); } |
В примере выше, мы подставили вместо s (строки), переменную k(число). В случае с Java ошибка будет обнаружена на этапе компиляции, в случае с JavaScript – позже: уже во время исполнения этого кода.
Если хочешь объявить переменную в JavaScript, надо написать «var+имя». Типов у переменных нет, так же нет типов у методов/функций и их аргументов.
В JavaScript очень мало строгих правил и очень много анархии.
Можно объявить функцию с 5 аргументами, а вызвать с двумя – остальные будут равны null. Можно объявить функцию с двумя аргументами, а при вызове передать пять. Три просто отпросятся. Контроль за ошибками, опечатками, изменениями – минимальный.
Факт третий – в JavaScript есть if, for, while
В JavaScript есть if, for, while и это – хорошая новость. Смотрим примеры:
JavaScript | Java |
---|---|
function main() { var s = "Bender"; var result = ""; for(var i=0;i<s.length;i++) { result = s[i]+ ""; } if(result.length>10) { alert (result); } else { while(result <=10) { result +=""; } alert(result); } } |
public static void main() { String s = "Bender"; char[] s2 = s.toCharArray(); String result = ""; for(int i=0;i<s.length();i++) { result = s2[i]+ ""; } if(result.length()>10) { System.out.println(result); } else { while(result <=10) { result +=""; } System.out.println(result); } } |
- Довольно похоже. Думаю, я смог бы разобраться, как работает код, написанный на JavaScript.
- Оптимизм – это хорошо.
Факт четверный – в JavaSctipt есть try-catch-finally
В JavaScript есть исключения (Error) и это хорошо. checked исключений нет, только unchecked – аналог RuntimeException. try-catch-finally работает так же, как и в Java. Пример:
JavaScript | Java |
---|---|
function main() { try { var s = null; var n = s.length; } catch(e) { alert(e); } } |
public static void main() { try { String s = null; int n = s.length(); } catch(Exception e) { System.out.println(e); } } |
При попытке узнать длину строки будет выкинуто исключение, т.к. переменная ссылается на null.
Факт пятый – в JavaScript есть массивы
Хорошая новость. В JavaScript есть массивы. Плохая – там нет ни списков, ни коллекций.
Еще одна хорошая новость – массивы могут динамически растягиваться, при добавлении новых элементов и уменьшаться при их удалении. Больше похоже на гибрид массива и списка.
Пример:
JavaScript | Java |
---|---|
function main() { var m = [1,3,18, 45, 'c', "roma", null]; alert(m.length); //7 m.push("end"); alert(m.length); //8 for (var i=0;i<m.length;i++) { alert(m[i]); } } |
public static void main() { List m = Arrays.asList(1,3,18, 45,'c', "roma", null); System.out.println(m.size());//7 m.add("end"); System.out.println(m.size());//8 for (int i=0;i<m.size();i++) { System.out.println(m.get(i)); } } |
- А что это за квадратные скобки при объявлении массива?
- Это и есть «объявление массива». Чтобы объявить массив надо написать квадратные скобки, а между ними перечислить элементы массива. Пустой массив объявляется просто парой скобок.
Пример |
---|
var m = []; |
Факт шестой – в JavaSctipt есть объекты
В JavaScript есть объекты. На самом деле все в JavaScript является объектом, примитивные типы тоже. Каждый объект представлен в виде набора пар «ключ-значение». Грубо говоря, каждый объект JavaScript – это аналог HashMap в Java. Пример объявления объекта:
JavaScript | Java |
---|---|
function main() { var m = { first_name : "Bill", last_name: "Gates", age: 54, weight: 67, children: ["Emma", "Catrin"], wife: { first_name : "Melinda", last_name: "Gates", age: 45, } }; alert(m.first_name); // Bill alert(m.age); // 54 alert(m.wife.first_name);// Melinda m.age = 45; m.age++; m["first_name"] = "Stive"; m["wife"] = null; |
public static void main() { HashMap m = new HashMap(); m.put("first_name", "Bill"); m.put("last_name", "Gates"); m.put("age", 54); m.put("weight", 67); String[] children = {"Emma","Catrin"}; m.put("children", children); HashMap wife = new HashMap(); wife.put("first_name", "Melinda"); wife.put("last_name", "Gates"); wife.put("age", 45); m.put("wife", wife); System.out.println(m.get("first_name")); System.out.println(m.get("age")); HashMap w = ((HashMap)m.get("wife")); System.out.println(w.get("first_name"))); m.put("age", 45); m.put("age", ((Integer)m.get("age"))+1); m.put("first_name","Stive"); m.put("wife", null); |
Чтобы создать новый объект достаточно написать две фигурные скобки «{}».
Внутри скобок можно указать данные объекта в формате «ключ, двоеточие, значение, запятая».
К полям объекта можно обращаться двумя способами:
Эквивалентные записи |
---|
m.age = 45; |
m[“age”] = 45; |
Если указанного поля нет, оно создается.
Что-то мой газовый пузырь переполнился, думаю надо сделать перерыв.
Напоследок - если хочешь изучить JavaScript подробнее, могу посоветовать отличный онлайн курс. Именно он когда-то вдохновил Джона на создание JavaRush:
Факт седьмой – JavaScript создан, чтобы работать внутри web-страниц.
- Ложная тревога – с пузырем все в порядке.
Продолжим. Сегодня я хочу рассказать тебе, что такое JSON.
- Да, я много раз слышал это слово, что же это такое?
- С развитием web-а, HTML-страницы с JavaScript начали активно взаимодействовать с серверами и загружать с сервера данные. Для облегчения этого процесса, был придуман стандарт обмена сообщениями между сервером и программой, написанной на JavaScript. Этот стандарт называется JSON (JavaScript Object Notation).
- И что же это за стандарт?
- О, тут самое интересное. В качестве стандарта было взято… объявление объекта в JavaScript!
Вот тебе пример сообщений в формате JSON:
Сообщения в формате JSON |
---|
{ "name": "oleg", "last": "eremenko" } |
{ "name": "batman", "enemies": [1,4,6,7,8,4,3,90] } |
{ "name": "grandpa", "children": [ { "name" = "Bob", "children": ["Emma", "Nikol"] }, { "name" = "Devid", "children": ["Jesica", "Pamela"] } ] } |
{ "12 45": { "__++": [], "1":"2" } } |
{} |
- Т.е. они просто пересылают данные, которые фактически является объектами JavaScript?
- Ага. И это очень удобно по двум причинам:
Во-первых, не нужно конвертировать данные из «формата пересылки» в набор объектов JavaScript.
Во-вторых, такой формат очень нагляден: легко читается и редактируется человеком.
Конечно, есть некоторые ограничения – не все можно представить в виде набора объектов, массивов, текста и чисел.
Объект Date, например, пересылается в строковом виде: «2012-04-23T18:25:43.511Z»
Но все равно этот способ пересылки информации колоссально удобный, читабельный, достаточно легкий и содержит минимум лишней информации. Поэтому он завоевал очень большую популярность.
- Как по мне - JSON очень простой формат, тут все очевидно и понятно.
Да и сам JavaScript не очень сложный.
- Язык-то простой, зато программы – сложные.
Или, как говорил мой дядя, в человеческом английском 26 букв, а вот научиться на нем правильно говорить – это не такая уж и простая задача для лобстера.
- Гм. Ты прав, Билаабо, буду иметь это в виду. Спасибо за интересную лекцию.
- Привет, Амиго!
- Привет, Элли!
- Раз уж ты познакомился с JSON, давай поговорим о нем сегодня подробнее.
- Ок. А где обычно он используется?
- Обычно дело выглядит так. Кто-то (клиент) запрашивает у Java-программы (сервера) данные. Программа создает Java-объекты и заполняет их информацией из базы данных. Затем преобразовывает их в формат понятный запрашивающему (клиенту), например JSON, и отсылает их обратно.
Давай я тебе расскажу, как работать с ним из Java. Собственно нам понадобится только две вещи – сериализовать Java-объекты в JSON-формат и десериализовать Java-объекты из формата JSON.
Т.е. JSON – это стандарт транспортировки сообщений/данных от одной программы до другой. Таких стандартов довольно много. Но если программа написана на JavaScript, она обычно старается работать с JSON.
- Ок. Я готов.
- Отлично. Тогда начнем.
Как ты уже знаешь, в Java есть встроенные стандартные средства сериализации. Но JSON к ним не относится. Поэтому если тебе надо использовать сериализацию объекта в JSON, ты можешь использовать один из популярных фреймворков(библиотек), которые это умеют.
- А чем отличаются различные фреймворки?
- Обычно они отличаются степенью сложности: есть фреймворки, которые умеют делать только самое необходимое, но они очень маленькие и простые. А есть и большие сложные фреймворки, которые могут делать гораздо больше.
Одним из популярных фреймворков считается Jackson. Мы рассмотрим работу с JSON на его примере.
Для начала тебе надо скачать этот фреймворк и добавить его себе в проект. Делать это надо в Intellij IDEA само собой. Скачать проект можно по ссылке.
- Готово.
- Отлично. Тогда продолжим.
Сконвертировать Java-объект в JSON примерно так же просто, как и сериализовать его. Для этого есть специальный класс ObjectMapper (com.fasterxml.jackson.databind.ObjectMapper).
Давай я покажу тебе рабочий пример, а потом мы его разберем:
Конвертация объекта в JSON | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public static void main(String[] args) throws IOException { //создание объекта для сериализации в JSON Cat cat = new Cat(); cat.name = "Murka"; cat.age = 5; cat.weight = 4; //писать результат сериализации будем во Writer(StringWriter) StringWriter writer = new StringWriter(); //это объект Jackson, который выполняет сериализацию ObjectMapper mapper = new ObjectMapper(); // сама сериализация: 1-куда, 2-что mapper.writeValue(writer, cat); //преобразовываем все записанное во StringWriter в строку String result = writer.toString(); System.out.println(result); } |
Класс Cat, объект которого конвертирует в JSON | |
@JsonAutoDetect class Cat { public String name; public int age; public int weight; Cat() { } } |
|
Результат сериализации и вывода на экран: | |
{ "name"="Murka", "age"=5, "weight"=4} |
Вот как все было:
В строках 5-8 мы создаем объект класса Cat и заполняем его данными.
Строка 11 – создаем объект Writer, куда будем писать строку - JSON представление объекта.
Строка 14 – создаем объект ObjectMapper, который и выполняет всю сериализацию.
Строка 17 – пишем JSON-представление объекта cat во writer.
Строки 20-21 – выводим результат на экран.
Все выглядит довольно просто. Не сложнее родной сериализации в Java.
- А как будет выглядеть десериализация?
- Да почти так же, только короче:
Конвертация объекта из JSON | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
public static void main(String[] args) throws IOException { String jsonString = "{ \"name\"=\"Murka\", \"age\"=5, \"weight\"=4}"; StringReader reader = new StringReader(jsonString); ObjectMapper mapper = new ObjectMapper(); Cat cat = mapper.readValue(reader, Cat.class); } |
Класс, объект которого десериализуется из JSON-формата | |
@JsonAutoDetect class Cat { public String name; public int age; public int weight; Cat() { } } |
Тут еще проще. Берем ObjectMapper и передаем в него строку с JSON или StringReader, а также класс объекта, который надо десериализовать. Вызываем метод readValue, и на выходе получаем готовый Java-объект со всеми данными.
- Ну, точно, как десериализация в Java.
- Почти. К объектам, которые сериализуются/десериализуются в JSON есть несколько требований:
1) поля должны быть видимые: или public или иметь getter’ы и setter’ы
2) должен быть конструктор по умолчанию (без параметров)
- Ясно. Ожидаемо, в общем. Хотя Java отлично сериализовала и private поля.
- Так то - Java. У нее есть доступ к скрытым данным. От себя не утаишь.
Тут есть еще третий аспект. Надеюсь, ты обратил внимание на аннотацию @JsonAutoDetect в классе Cat?
- Ага. Как раз хотел спросить – что это такое.
- Это аннотации – служебная информация для фреймворка Jackson. Можно очень гибко управлять результатом сериализации в JSON формат, расставляя правильные аннотации.
- Круто! А что за аннотации есть?
- Вот тебе несколько:
Аннотация | Описание |
---|---|
@JsonAutoDetect | Ставится перед классом. Помечает класс как готовый к сериализациив JSON. |
@JsonIgnore | Ставится перед свойством. Свойство игнорируется при сериализации. |
@JsonProperty | Ставится перед свойством или getter’ом или steer’ом. Позволяет задать другое имя поля при сериализации. |
@JsonWriteNullProperties | Ставится перед классом. Поля объекта, которые равны null не будет игнорироваться. |
@JsonPropertyOrder | Ставится перед классом. Позволяет задать порядок полей для сериализации. |
- Как интересно. А есть еще?
- Есть много. Но не сейчас. Сейчас давай немного переделаем наш первый пример:
Конвертация объекта в JSON | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public static void main(String[] args) throws IOException { Cat cat = new Cat(); cat.name = "Murka"; cat.age = 5; cat.weight = 4; StringWriter writer = new StringWriter(); ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(writer, cat); String result = writer.toString(); System.out.println(result); } |
Класс, объект которого конвертирует в JSON | |
@JsonAutoDetect class Cat { @JsonProperty(name="alias") public String name; public int age; @JsonIgnore public int weight; Cat() { } } |
|
Результат сериализации и вывода на экран: | |
{ "alias"="Murka", "age"=5} |
Код остался тот же, но я поменял аннотации: указал другое имя полю name - имя alias. А так же отметил поле weight как Ignore, в результате JSON объекта поменялся.
- Хорошо, что можно так всего настраивать – думаю, мне это обязательно пригодится.
А десериализация поймет, как с этим работать? При десериализации из JSON в Java-объект, значение поля alias будет занесено в name объекта Cat?
- Да, десериализация будет работать как надо. Она умная.
- Что не может не радовать.
Спасибо за такую интересную лекцию, Элли.
- Привет, дружище!
- Здорова, Диего.
- Я тут смотрю, тебя познакомили с азами сериализации в JSON?
- Почему с азами? Я уже много знаю!
- Святая простота. Да ты и половины не знаешь. Процентов 10 от силы.
- Ух ты. А что там еще осталось?
- Десериализация иерархии объектов (полиморфизм при десериализации), десериализация коллекций, еще много всего. Jackson – большой и мощный фреймворк, а ты с ним познакомился чуть-чуть, прямо говоря.
- Ладно. Тогда расскажи мне про что-нибудь из этого, а я – послушаю.
Приятно становиться умнее с каждой лекцией!
- Как не помочь другу-роботу? Кто если не я?
Готов? Тогда слушай.
Как ты уже убедился, аннотации используются не только при сериализации, но и при десериализации. На практике для сериализации надо гораздо меньше информации, чем для десериализации. Пример:
Java class | JSON |
---|---|
class Cat { public String name = "murka"; public Cat[] cats = new Cat[0]; } |
{ "name": "murka", "cats": [] } |
class Cat { public String name = "murka"; public List<Cat> cats = new ArrayList<Cat>(); } |
{ "name": "murka", "cats": [] } |
class Cat { public String name = "murka"; public List<Cat> cats = new LinkedList<Cat>(); } |
{ "name": "murka", "cats": [] } |
Объекты типов Array(массив), ArrayList, LinkedList,… заменяются на массив в JSON-формате.
А вот при десериализации неясно, какой объект создать ArrayList или LinkedList?
- Согласен, если у класса есть поле, и тип поля – это интерфейс (как в случае с public List<Cat> cats), то совсем не ясно, какой именно объект ему присваивать.
- Можно добавить этому полю дополнительные аннотации или указать нужные классы прямо во время десериализации объекта. Смотри пример:
Конвертация объекта в JSON | |
---|---|
1 2 3 4 5 6 7 |
public static void main(String[] args) throws IOException { String jsonString = "{ \"name\"=\"Murka\", \"age\"=5, \"weight\"=4}"; StringReader reader = new StringReader(jsonString); ObjectMapper mapper = new ObjectMapper(); Cat cat = mapper.readValue(reader, TypeFactory.collectionType(ArrayList.class, Cat.class)); } |
Класс, объект которого десериализуется из JSON-формата | |
@JsonAutoDetect class Cat { public String name; public List<Cat> cats = new ArrayList<Cat>(); Cat() { } } |
Т.е. мы в метод mapper.readValue вторым параметром передаем список классов, которые будут использоваться при десериализации.
- А мне нравится. Удобно. Можно где надо десериализовать «JSON массив» как ArrayList, а где надо, как LinkedList.
Ты еще говорил, что аннотациями можно. Это как?
- Да ничего сложного. Пример:
Конвертация объекта в JSON | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
public static void main(String[] args) throws IOException { String jsonString = "{ \"name\"=\"Murka\", \"age\"=5, \"weight\"=4}"; StringReader reader = new StringReader(jsonString); ObjectMapper mapper = new ObjectMapper(); Cat cat = mapper.readValue(reader, Cat.class); } |
Класс, объект которого десериализуется из JSON-формата | |
11 12 13 14 15 16 17 18 19 20 21 22 |
@JsonAutoDetect class Cat { public String name; @JsonDeserialize(as=ArrayList.class) public List<Cat> cats = new ArrayList<Cat>(); Cat() { } } |
В строке 16 мы просто добавили аннотацию @JsonDeserialize(as=ArrayList.class), где указали, какую реализацию интерфейса List использовать.
- Ага. Ясно. Действительно – довольно просто.
- Но и это еще не все. Теперь представь, что тип данных в List тоже интерфейс! Что ты будешь делать?
- У нас есть аннотация и на этот случай?
- Да, причем, та же самая. В ней можно указать еще и тип параметр. Выглядеть это будет вот так:
Тип коллекции | Как задать тип данных |
---|---|
List | @JsonDeserialize(contentAs=ValueTypeImpl.class) |
Map | @JsonDeserialize(keyAs=KeyTypeImpl.class) |
- Круто. Действительно, много нужных аннотация для разных случаев, о которых заранее и не догадаешься.
- И это еще не все. Сейчас будет самое вкусное. В реальных проектах, классы данных очень часто унаследованы от одного базового класса или интерфейса, который используется практически везде. И вот представь, тебе надо десериализовать структуру данных, которая содержит такие классы. Пример:
Конвертация объекта в JSON | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public static void main(String[] args) throws IOException { Cat cat = new Cat(); cat.name = "Murka"; cat.age = 5; Dog dog = new Dog(); dog.name = "Killer"; dog.age = 8; dog.owner = "Bill Jeferson"; ArrayList<Pet> pets = new ArrayList<Pet>(); pets.add(cat); pets.add(dog); StringWriter writer = new StringWriter(); ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(writer, pets); System.out.println(writer.toString()); } |
Класс, объект которого конвертирует в JSON | |
@JsonAutoDetect class Pet { public String name; } @JsonAutoDetect class Cat extends Pet { public int age; } @JsonAutoDetect class Dog extends Pet { public int age; public String owner; } |
|
Результат сериализации и вывода на экран: | |
[ { "name" : "Murka", "age" : 5}, { "name": "Killer", "age" : 8 , "owner" : "Bill Jeferson"} ] |
Обрати внимание на результат сериализации.
Мы не сможем провести десериализацию этих данных обратно в Java-объекты, т.к. они фактически неразличимы.
- Немного различимы - у Dog есть поле owner.
- Да, но это поле может быть равно null или вообще пропускаться при сериализации.
- А разве мы не можем задать тип данных с помощью известных нам аннотаций?
- Нет. В одной коллекции после десериализации должны хранится различные объекты типа Cat, Dog и еще пары десятков классов, которые можно унаследовать от Pet.
- И что же можно тут сделать?
- Тут применяют две вещи.
Во-первых, выделяют некоторое поле, которое используется для того, чтобы отличать один тип от другого. Если его нет – его заводят.
Во-вторых, есть специальные аннотации, которые позволяют управлять процессом «полиморфной десериализации». Вот что можно сделать:
Конвертация объекта в JSON | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public static void main(String[] args) throws IOException { Cat cat = new Cat(); cat.name = "Murka"; cat.age = 5; Dog dog = new Dog(); dog.name = "Killer"; dog.age = 8; dog.owner = "Bill Jeferson"; House house = new House(); house.pets.add(dog); house.pets.add(cat); StringWriter writer = new StringWriter(); ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(writer, house); System.out.println(writer.toString()); } |
Класс, объект которого конвертирует в JSON | |
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property="type") @JsonSubTypes({ @JsonSubTypes.Type(value=Cat.class, name="cat"), @JsonSubTypes.Type(value=Dog.class, name="dog") }) class Pet { public String name; } class Cat extends Pet { public int age; } class Dog extends Pet { public int age; public String owner; } class House { public List<Pet> pets = new ArrayList<Pet>(); } |
|
Результат сериализации и вывода на экран: | |
{ "pets" : [ {"type" : "dog","name" : "Killer", "age" : 8, "owner" : "Bill Jeferson"}, {"type" : "cat","name" : "Murka", "age" : 5} ] } |
С помощью аннотаций мы указываем, что JSON-представление будет содержать специальное поле type, которое будет хранить значение cat, для класса Cat и значение dog, для класса Dog. Этой информации достаточно, чтобы выполнить корректную десериализацию объекта: при десериализации по значению поля type будет определяться тип объекта, который надо создать.
Иногда в качестве значения поля type используют имя класса (например, "com.example.entity.Cat.class"), но это не очень хорошо. Зачем стороннему приложению, которому мы пересылаем JSON, знать, как называются наши классы? К тому же, классы иногда переименовывают. Использование некоего уникального имени для обозначения конкретного класса – предпочтительнее.
- Круто! А я и не знал, что десериализация такая сложная вещь. И что столько всего можно настраивать.
- Ага. Это действительно новые для тебя вещи, но именно благодаря таким практическим знаниям, ты скоро станешь крутым программистом.
- Амиго – крутой программист. Круто!
- Ладно. Иди, отдыхай.
Задачи |
---|
1. Первая сериализация в JSON НЕОБХОДИМО: подключенные библиотеки Jackson Core, Bind и Annotation версии 2.6.1 1) В программе не выполнено основное требование к сериализации в JSON. Найдите ошибку и исправьте. 2) Расставьте правильно Json аннотации у классов. Все данные должны сериализоваться. |
2. Вторая сериализация в JSON НЕОБХОДИМО: подключенные библиотеки Jackson Core, Bind и Annotation версии 2.6.1 Расставьте Json аннотации так, чтобы результат работы метода main был следующим {"wildAnimal":"Murka","over":3} |
3. Десериализация JSON объекта НЕОБХОДИМО: подключенные библиотеки Jackson Core, Bind и Annotation версии 2.6.1 В метод convertFromJsonToNormal первым параметром приходит имя файла, который содержит один ДЖЕЙСОН объект. Вторым параметром приходит имя класса, объект которого находится в файле. Метод convertFromJsonToNormal должен вычитать объект из файла, преобразовать его из JSON и вернуть его. |
4. Конвертация из одного класса в другой используя JSON НЕОБХОДИМО: подключенные библиотеки Jackson Core, Bind и Annotation версии 2.6.1 Два класса имеют одинаковые поля, но не имеют общий суперкласс. Пример, классы First и Second. Реализовать логику метода convertOneToAnother, который должен возвращать объект класса resultClassObject, значения полей которого равны значениям полей в объекте one. Известно, что у классов есть Json аннотация, у которой значение проперти равно имени класса в нижнем регистре. На примере класса First, это className="first" Классы First и Second не участвуют в тестировании, они предоставлены в качестве тестовых данных. |
5. Определение имени класса НЕОБХОДИМО: подключенные библиотеки Jackson Core, Bind и Annotation версии 2.6.1 Расставьте Json аннотации так, чтобы результат выполнения метода main был следующий: { "className" : ".Parking", "name" : "Super Parking", "city" : "Kyiv", "autos" : [ { "className" : "com.javarush.test.level33.lesson05.home05.RaceBike", "name" : "Simba", "owner" : "Peter", "age" : 2 }, { "className" : "com.javarush.test.level33.lesson05.home05.Motorbike", "name" : "Manny", "owner" : null }, { "className" : "com.javarush.test.level33.lesson05.home05.Car" } ] } Подсказка: это всего два класса |
- Как делишки?
- Отлично. Не жалуюсь. Сегодня Билаабо рассказал про JavaScript. Не все, конечно, но тоже немало. Я конечно еще на JS ничего не писал, но думаю, что это не сложно.
А Элли рассказала про сериализацию в JSON. И ты рассказывал о Jackson framework и как настраивать «полиморфную десериализацию» с помощью аннотаций.
- Нифига себе, ты умный теперь, Амиго! Серьезный мужик!
- А то!
- Ладно. Давай к работе. Сегодня новая и интересная тема – XML.
XML – это стандарт представления данных, которые легко могут быть прочитаны человеком, и еще легче – программой. Пример xml-файла:
XML |
---|
<data> <owner first="Nikolay" last="Ivanovich"> <address>Moscow</address> </owner> <cat name="Murka" age="15"/> </data> |
Основа XML – это теги. Тег – это слово в треугольных скобках (знаки меньше и больше).
Теги бывают открывающие и закрывающие. Одному открывающему всего соответствует один закрывающий тег. Открывающие теги могут иметь атрибуты.
В тег можно складывать другие теги, получая, таким образом, дерево элементов. Тег верхнего уровня называют корнем (root): у него есть дочерние теги, у них свои дочерние теги.
Примеры:
Тег | Описание |
---|---|
<data> | Открывающий тег «data» |
</data> | Закрывающий тег «data» |
<cat name="Murka"age="15"> | Тег с атрибутами. Значения атрибутов берутся в кавычки |
<data> <owner> <cat name="Murka"/> </owner> </data> |
Вложенные теги. |
<cat name="Murka" age="15"/> | Автозакрывающийся тег. Такому тегу не нужен закрывающий. Не может содержать дочерние теги. |
<info> Тут может быть любая информация </info> |
Тег может содержать текстовые данные |
<info> Тут может <data xxx="yyy"> </data> быть любая <data 2xxx="yyy"/> информация </info> |
Тег может содержать текстовые данные и другие теги вперемешку. |
- Выглядит несложно. А какие теги бывают?
- Любые. Никаких зарезервированных тегов нет. XML – это язык, для описания любых данных. Люди сами придумывают теги для своих нужд и договариваются, как их использовать.
Фактически, XML – это способ записать данные в виде дерева элементов, понятный компьютеру.
- Вроде ясно. Кстати, у меня вопрос.
Вот JSON используется для передачи данных с браузера на сервер, а где используется XML?
- Да там же, где и JSON: для хранения и передачи данных.
Ладно, продолжим.
Представь, что есть один общий XML-файл, который хранит данные программы, которую пишут двадцать человек. Тогда каждый из них придумывает теги для себя и очень скоро они начинают мешать друг другу.
Чтобы гарантировать уникальность тегов, им были придуманы префиксы. Выглядит это так:
Теги | Описание |
---|---|
<animal:cat> | Тег cat с префиксом animal |
<animal:cat> </animal:cat> <zoo:cat> </zoo:cat> |
Два тега cat с разными префиксами. |
<animal:cat zoo:name="MX"> | Тег cat с префиксом animal, атрибут name с префиксом zoo. |
Префиксы еще называют namespace – пространство имен. Тогда последнее предложение в таблице будет звучать так «Тег cat из пространства имен animal с атрибутом name из пространства имен zoo».
Кстати, помнишь в Java у класса есть короткое имя, а есть длинное уникальное имя, в которое входит название пакета, его еще указывают при импорте?
- Ага.
- Так вот, у префиксов тоже есть уникальное длинное имя и его тоже указывают при импорте:
Пример |
---|
<data xmlns:soap="http://cxf.apache.org/bindings/soap"> <soap:item> <soap:info/> </soap:item> </data> |
«xmlns:soap» значит «XML-namespace SOAP»
Более того, если есть теги без префикса, можно задать и его уникальное имя:
Пример |
---|
<data xmlns="http://www.springframework.org/schema/beans" xmlns:soap="http://cxf.apache.org/bindings/soap" xmlns:task="http://www.springframework.org/schema/task"> <soap:item> <soap:info/> <task:info/> </soap:item> </data> |
«xmlns=…» задает namespace для пустого префикса – т.е. для тегов без префикса, таких как data.
В документе может быть сколько угодно namespace, но у каждого должно быть свое уникальное имя.
- Ясно. А почему такие странные «уникальные имена» у этих namespace?
- Обычно там указывают URL, по которому находится документ, который описывает этот namespace и/или его XML-теги.
- Не так уж и мало на меня сегодня вылили информации. Что еще?
- Еще немного осталось.
Во-первых, у XML есть заголовок – специальная строка, которая описывает версию этого XML и кодировку файла. Выглядит она обычно так:
Выглядит она обычно так:
Пример |
---|
<?xml version="1.0" encoding="UTF-8"?> <data xmlns:soap="http://cxf.apache.org/bindings/soap"> <soap:item> <soap:info/> </soap:item> </data> |
Еще в XML можно вставлять комментарии. Для начала комментария используют «<!--», а для конца «-->».
Пример |
---|
<?xml version="1.0" encoding="UTF-8"?> <data xmlns:soap="http://cxf.apache.org/bindings/soap"> <soap:item> <!-- <soap:info/> --> </soap:item> <!-- это тоже комментарий --> </data> |
- Пока понятно.
- Из-за того, что в XML используются символы « < > “ &», их нельзя использовать в других местах. Для их описания используют так называемые «эскейп последовательности» - набор символов для представления других символов/символа. Вот список некоторых из них:
Escape-последовательность | Символ, который она заменяет |
---|---|
& | & |
" | " |
< | < |
> | > |
' | ‘ |
А вот пример кода, который хранится в XML:
Java-код | Он же, в XML |
---|---|
if (a<b) System.out.println("a is minimum"); |
<code> if (a < b) System.out.println("a is minimum"); </code> |
- М-да. Выглядит не очень красиво.
- А ты помнишь, в Java тоже экранируются некоторые символы - например «\». И тоже для его написания в строке приходится писать его дважды. Так что это часто встречаемое явление.
- Ок.
- У меня на сегодня все.
- Ура. Наконец-то я отдохну.
- Не так быстро, молодой человек! У меня для тебя еще две лекции!
- Две? Ого. Ну, ладно. Чего не сделаешь ради собственной крутости. Я готов слушать.
- XML, как и JSON, часто применяется при пересылке данных между разными программами и компьютерами. И так же есть несколько фреймворков, которые значительно упрощают жизнь Java-программистов при работе с XML. Сегодня я познакомлю тебя с одним из них.
JAXB – это отличный многофункциональный фреймворк, для работы с XML.
JAXB – это часть JDK, поэтому скачивать его отдельно не требуется.
Давай я сначала покажу тебе пример работы с ним, а после мы его разберем. Пример:
Конвертация объекта в XML | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public static void main(String[] args) throws JAXBException { //создание объекта для сериализации в XML Cat cat = new Cat(); cat.name = "Murka"; cat.age = 5; cat.weight = 4; //писать результат сериализации будем в Writer(StringWriter) StringWriter writer = new StringWriter(); //создание объекта Marshaller, который выполняет сериализацию JAXBContext context = JAXBContext.newInstance(Cat.class); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); // сама сериализация marshaller.marshal(cat, writer); //преобразовываем в строку все записанное в StringWriter String result = writer.toString(); System.out.println(result); } |
Класс, объект которого конвертирует в XML | |
@XmlType(name = "cat") @XmlRootElement class Cat { public String name; public int age; public int weight; Cat() { } } |
|
Результат сериализации и вывода на экран: | |
<cat> <name>Murka</name> <age>5</age> <weight>4</weight> </cat> |
- Что-то мне этот код один в один напоминает сериализацию в JSON. Тоже аннотации, но там был ObjectMapper, тут Context & Marshaller.
- Ага. Действительно они очень похожи. Jackson писался по образцу JAXB. Но и JAXB то же с кого-то списали. Нельзя выдумать нечто гениальное на пустом месте.
- Похоже на то.
- Ок, вот что там происходит:
В строках 5-8 мы создаем объект класса Cat и заполняем его данными.
Строка 11 – создаем объект Writer для записи результата.
Стока 14 – создаем «контекст». Это аналог ObjectMapper, но от него потом создаются еще два дочерних объекта Marshaller – для сериализации, и Unmarshaller для десериализации. Небольшие отличия от Jackson, но – непринципиально.
Строка 15 – создаем объект Marshaller. Маршалинг – это синоним слова сериализация.
Строка 16 – устанавливает свойство FORMATTED_OUTPUT в TRUE. В результат будут добавлены переносы строки и пробелы, чтобы код был читабельным для человека, а не весь текст в одну строку.
Строка 18 – сериализация объекта.
Строки 21-22 – вывод результата сериализации на экран.
- А что это еще за аннотации @XmlType(name = "cat") и @XmlRootElement?
- @XmlRootElement указывает на то, что этот объект может быть «корнем дерева» элементов в XML. Т.е. быть элементом самого верхнего уровня, все остальные элементы лежат в нем.
@XmlType(name = "cat") указывает на то, что класс участвует в JAXB сериализации, в ней же задано имя, которое будет у XML-тега для этого класса.
Ладно, давай теперь покажу пример десериализации из XML:
Конвертация объекта из XML | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
public static void main(String[] args) throws JAXBException { String xmldata = "<cat><name>Murka</name><age>5</age><weight>4</weight></cat>"; StringReader reader = new StringReader(xmldata); JAXBContext context = JAXBContext.newInstance(Cat.class); Unmarshaller unmarshaller = context.createUnmarshaller(); Cat cat = (Cat) unmarshaller.unmarshal(reader); } |
Класс, объект которого десериализуется из XML | |
@XmlType(name = "cat") @XmlRootElement class Cat { public String name; public int age; public int weight; Cat() { } } |
Все практически аналогично случаю с Jackson. Но на всякий случай объясню все, что тут происходит.
Строка 4 – задаем строку, которая хранит xml для десериализации.
Строка 5 – оборачиваем xml-строку в StringReader.
Строка 7 – создаем JAXB-контекст, куда передаем список классов.
Строка 8 - создаем Unmarshaller – объект, который будет выполнять десериализацию.
Строка 10 – десериализуем xml из объекта reader и получаем объект cat типа Cat.
- Теперь это все выглядит чуть ли не очевидно. А еще пару часов назад, я ломал голову - как же это работает.
- Так всегда бывает, когда становишься умнее – сложные вещи становятся простыми.
- Я становлюсь умнее? Это не может не радовать.
- Отлично. Тогда вот тебе список аннотаций, которые ты можешь использовать, чтобы управлять результатом результатом JAXB сериализации:
JAXB-аннотации | Описание |
---|---|
@XmlElement(name) | Ставится около поля. Поле будет представлено в XMLэлементом. Позволяет задать имя для тэга. |
@XmlAttribute(name) | Ставится около поля. Поле будет представлено в XMLатрибутом! Позволяет задать имя для атрибута. |
@XmlElementWrapper(nillable = true) | Ставится около поля. Позволяет задать «обрамляющий тег» для группы элементов. |
@XmlType | Ставится около класса. Позволяет задать метод для создания объекта, если конструктор по умолчанию private. |
@XmlJavaTypeAdapter | Ставится около поля. Позволяет задать класс, который будет преобразовывать данные поля в строку. |
- Как интересно. А можно примеры с этими аннотациями, а то написанное – это одно, а живой пример – это совсем другое.
- Ок. Будет тебе пример. Хотел только добавить, то JAXB позволяет ставить аннотации у методов getter/setter вместо полей.
Обещанный пример:
Конвертация объекта в XML |
---|
public static void main(String[] args) throws JAXBException { //создание объектов Cat&Zoo для сериализации в XML Cat cat = new Cat(); cat.name = "Murka"; cat.age = 5; cat.weight = 4; Zoo zoo = new Zoo(); zoo.animals.add(cat); zoo.animals.add(cat); //писать результат сериализации будем во Writer(StringWriter) StringWriter writer = newStringWriter(); //создание объекта Marshaller, который выполняет сериализацию JAXBContext context = JAXBContext.newInstance(Cat.class, Zoo.class); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); // самосериализация marshaller.marshal(zoo, writer); //преобразовываем все записанное в StringWriter в строку System.out.println(writer.toString()); } |
Класс, объект которого конвертирует в XML |
@XmlType(name = "zoo") @XmlRootElement class Zoo { @XmlElementWrapper(name="wild-animals", nillable = true) public List<Cat> animals = new ArrayList<Cat> } @XmlType(name = "tiger") class Cat { @XmlElement(name = "catname") public String name; @XmlAttribute(name = "age") public int age; @XmlAttribute(name = "w") public int weight; Cat() { } } |
Результат сериализации и вывода на экран: |
<zoo> <wild-animals> <tiger age="5" w="4"> <catname>Murka</catname> </tiger> <tiger age="5" w="4"> <catname>Murka</catname> </tiger> </wild-animals> </zoo> |
Обрати внимание, в этот раз мы сериализуем не объект Cat, а объект типа Zoo, которых хранит коллекцию объектов Cat.
Объект cat в коллекцию был добавлен дважды, поэтому он 2 раза в XML.
У коллекции есть свой тег – «wild-animals» , который обрамляет все элементы коллекции.
Элементы age & weight стали атрибутами age &w.
С помощью атрибута @XmlType мы поменяли тэг cat на tiger.
Обрати еще внимание на строку 18, теперь мы передаем в JAXB-контекст два класса – Zoo & Cat, т.к. они оба участвуют в сериализации.
- Сегодня очень интересный день – столько нового.
- Ага. Рад за тебя. Сейчас сделаем небольшой перерыв и продолжим.
Задачи |
---|
1. Первая сериализация в XML Раставьте правильно JAXB аннотации у статических классов. Метод main не участвует в тестировании. |
2. Десериализация XML объекта В метод convertFromXmlToNormal первым параметром приходит строка, содержащая xml объект. Вторым параметром приходит имя класса, объект которого необходимо вернуть. Метод convertFromXmlToNormal должен создать объект из xml-строки и вернуть его. |
- Так вот. Как ты уже, наверное, догадался - это еще не все.
Сейчас я тебе расскажу об еще нескольких аспектах JAXB. Но начнем мы, как и в случае с JSON, с коллекций.
При десериализации коллекций в JAXB тоже возникает неопределенность, какую именно коллекцию (ArrayList, LinkedList, Vector, …) подставить в переменную типа List? И опять ответ на этот вопрос дают аннотации.
Тут все довольно просто. Если тип коллекции не задан в аннотации к ней, тогда JAXB попробует подобрать самую подходящую коллекцию – на основе типа. Для List это будет ArrayList, для Map – HashMap и т.д.
На самом деле тут гораздо меньше проблем, чем в случае с JSON, т.к. у каждого класса есть его уникальный тэг, а по тэгу можно точно определить – что это за класс.
Например, если надо десериализовать группу элементов, которые унаследованы от общего предка, для этого используется аннотация @XmlAny:
Конвертация объекта из XML | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
public static void main(String[] args) throws JAXBException { String xmldata = "<zoo><cat/><cat/><dog/><cat/></zoo>"; StringReader reader = new StringReader(xmldata); JAXBContext context = JAXBContext.newInstance(Cat.class, Zoo.class, Dog.class); Unmarshaller unmarshaller = context.createUnmarshaller(); Cat cat = (Cat) unmarshaller.unmarshal(reader); } |
Класс, объект которого десериализуется из XML | |
@XmlType(name = "zoo") @XmlRootElement class Zoo { @XmlAny public List<Object> animals; } @XmlType(name = "cat") @XmlRootElement class Cat { public String name; public int age; public int weight; Cat() { } } @XmlType(name = "dog") @XmlRootElement class Dog { public String name; public int age; public int weight; Cat() { } } |
Если коллекция помечена аннотацией @XmlAny, то в нее могут быть помещены любые подходящие объекты. JAXB Unmarshaller при этом обращает внимание на тэги.
При этом последовательность тэгов « <zoo><cat/><cat/><dog/><cat/><zoo> » будет превращена в набор объектов Cat, Cat, Dog, Cat.
- Ожидаемо, в общем.
- Ага. О, кстати, вот еще что. Если ты десериализуешь смесь текста и тэгов, для этого надо использовать аннотацию @XmlMixed.
Пример такого XML:
Пример XML, для которого нужно использовать аннотацию @XmlMixed |
---|
<data> <items> test <item/> text 2 <item> name </item> text 3 </items> </data> |
- Ого. А я и забыл, что такой XML бывает. Привык, что все так красиво, тэги вложены, все дела.
- Бывает. И даже на этот случай у JAXB есть аннотация!
- Отлично. А я вот, кстати, хотел спросить, а как сериализуются enum?
- Хороший вопрос! Молодец! Я-то как раз упустил эту тему.
Для enum существует специальная аннотация @XmlEnum, которой надо помечать enum. В ней же можно указать – будут значения представлены в виде чисел или строк.
Также есть специальная аннотация @XmlEnumValue, которая позволяет задать специальное значение, которое будет соответствовать данному значению enum’а.
Примеры:
Числа | Строки |
---|---|
@XmlType @XmlEnum(Integer.class) public enum Code { @XmlEnumValue("1") START, @XmlEnumValue("2") INPROGRESS, @XmlEnumValue("3") FINISH @XmlEnumValue("-1") ERROR } |
@XmlType @XmlEnum(String.class) public enum Card { @XmlEnumValue("Пика") CLUBS, @XmlEnumValue("Бубна") DIAMONDS, @XmlEnumValue("Черва") HEARTS, @XmlEnumValue("Трефа") SPADES } |
- Ух ты. Не могу представить, где мне это может понадобиться, но думаю, что это очень полезная штука. А главное – не обязательно придерживается стандартных строковых или числовых значений.
- Ага. Это полезно, когда ты, например, пишешь программу, которая обменивается сообщениями, скажем, с сервером Facebook, и у них свой заданный набор значений. Тебе достаточно просто прописать их у своего enum и все заработает.
- Это же замечательно. Мне определенно нравится JAXB.
- Отлично. Тогда на сегодня все. Иди - отдыхай.
Задачи |
---|
1. Создание класса по строке xml Восстановите класс по переданной строке xml. Класс должен быть в отдельном файле. Метод getClassName должен возвращать восстановленный класс. Метод main не участвует в тестировании. |
2. Комментарий внутри xml Реализовать метод toXmlWithComment, который должен возвращать строку - xml представление объекта obj. В строке перед каждым тэгом tagName должен быть вставлен комментарий comment. Сериализация obj в xml может содержать CDATA с искомым тегом. Перед ним вставлять комментарий не нужно. Пример вызова: toXmlWithComment(firstSecondObject, "second", "it's a comment") Пример результата: <?xml version="1.0" encoding="UTF-8" standalone="no"?> <first> <!--it's a comment--> <second>some string</second> <!--it's a comment--> <second>some string</second> </first> |
- Вот тебе несколько заданий:
Что надо найти в Google | |
---|---|
1 | Что такое JSON |
2 | В чем связь JSON и JavaScript |
3 | Как использовать Jackson |
4 | Java jackson. Как настроить сериализацию в JSON |
5 | Настройка JAXB |
6 | Сериализация в JAXB примеры |
7 | Аннотации в JAXB |
8 | Документация по Jackson |
9 | Документация по JAXB |
10 | Проблемы десериализации в Jackson |
- Привет, Амиго!
Вот тебе дополнительный материал по теме.
- Привет, Амиго!
- Привет, Хулио.
- Ты купил на обед Json? Ой, я хотел сказать батон.
- Купил. Но кажется мне, кому - то нужно отдохнуть. Присаживайся.
- Привет, Амиго!
Вопросы к собеседованиям | |
---|---|
1 | Что такое JSON? |
2 | В чем отличия Java и JavaScript? |
3 | В чем отличия JSON и XML? |
4 | Какие фреймворки для работы с JSON вы знаете? |
5 | Какие фреймворки для работы с XML вы знаете? |
6 | Какие аннотации Jackson вы знаете? |
7 | Какие аннотации JAXB вы знаете? |
8 | В чем отличие сериализации и десериализации в JSON? |
9 | Что лучше JSON или XML? Почему? |
10 | Что такое DTO? |
- Привет, боец!
- Поздравляю тебя с повышением уровня квалификации. Нам нужны отчаянные парни.
- Уверен, у тебя есть еще много нерешенных задач. Самое время решить парочку из них!