LINUX.ORG.RU

JSON десериализация и динамическая vs статическая типизация

 , , , ,


0

1

Как известно JSON пришёл из JavaScript, который является языком программирования с динамической типизацией и прототипной моделью данных. Грубо говоря там есть Object в который можно динамически добавлять всё что угодно - как поля, так и функции. В JavaScript вообще не нужно объявлять какую-то заранее известную дата модель. А вот в таких языках как Java, со статической типизацией, это делать надо. При этом возникает масса проблем.

Допустим у нас есть примерно следующая модель данных в Java:

public class Cage<T extends Animal> {
    private T animal;

    // boilerplate code omitted
}

Класс Animal является базовым классом и какой именно его потомок может оказаться в этом поле заранее не известно. Внутри JSON может прийти совершенно любое животное.

Может возникнуть необходимость держать в клетке несколько животных и даже допускать, что они будут разными. В этом случае потребуется их список.

public class Cage<T extends Animal> {
    private List<T> animals;

    // boilerplate code omitted
}

Проблема такого кода ещё и в том, что тип T всё равно статический и поселить, например, кошек с собаками в один список не получится, даже если они ладят друг с другом в реальной жизни.

Если данные приходят в виде JSON, их десериализация в такие модели данных со статической типизацией становится нетривиальной и это основная проблема, которую я хотел бы обсудить. Для того, чтобы всё работало правильно рано или поздно приходится добавлять в этот JSON дополнительную информацию о типах. В случае примеров выше - информацию о том, какой именно потомок Animal используется в каждом конкретном элементе списка. Мало того, что усложняется JSON, так ещё и десериализацию, например для REST контроллера в Spring Boot, приходится делать нестандартной.

Как правильнее решить эту проблему? Отказаться от POJO в пользу вложеных Map-ов или какого-то иного динамического отображения JSON (например ObjectNode из библиотеки Jackson)? Вообще отказаться от Java и писать серверный код на JavaScript под node.js? Или отказаться от JSON в пользу какого-то другого формата, у которого метаданные являются обязательными? На ум приходит что-то типа SOAP. Все три подхода имеют массу недостатков. Или может быть существует какой-то четвёртый подход, о котором я не подумал?



Последнее исправление: hummer (всего исправлений: 1)

Ответ на: комментарий от Miguel

Нет. Я про генерирование JSON-представления из sealed trait/case class.

JSON-представление - это JSON, генерируемый во время сериализации? С ним нет проблем, проблема в обратном действии - десериализации.

hummer
() автор топика
Ответ на: комментарий от Nervous

Статика такая статика.

Ну да, JSON пришёл из совершенно другой парадигмы.

А если это какой-нибудь интерфейс, который все жывотные реализуют? Типа class Doggie implements IGooodBoy {}.

И как мне это поможет? У разных жимотных могут добавляться разные поля и соответствующие гетеры/сетеры. Общий интерфейс тут не поможет.

hummer
() автор топика

Проблема такого кода ещё и в том, что тип T всё равно статический и поселить, например, кошек с собаками в один список не получится, даже если они ладят друг с другом в реальной жизни.

За базар ответишь? List of Animal очевидный ответ. Если у тебя приходит в JSONе список в котором есть и кошки и собаки - значит изначально это был список животных - List of Animal, а значит ты и должен восстанавливать в List of Animal который корректно принимает и экземпляры Cat и Dog.

Для того, чтобы всё работало правильно рано или поздно приходится добавлять в этот JSON дополнительную информацию о типах.

Разумеется приходится. И этим занимается сериализатор. И List превращается не в JSONArray, а в JSONObject у которого есть {«containerType»: «…», «items»: […]}

какой именно потомок Animal используется в каждом конкретном элементе списка

Информация о классе является неотъемлемым атрибутом объекта (this.getClass()) и если ты потерял её на сериализации - это только твои проблемы.

Nastishka ★★★★★
()
Последнее исправление: Nastishka (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

Какую проблему? Потерю части данных при передаче?

Нет. Проблему неправильно десериализации данных из JSON.

Это не считая того, что ты используешь рест.

Что бы использовал ты?

hummer
() автор топика
Ответ на: комментарий от r0ck3r

А зачем делать List<T> animals, где T extends Animal, когда можно сделать List<Animal>?

Менее жёстка привязка к Animal. Во время объявления переменной типа Cage<T extends Animal> можно указать более конкретный, но всё ещё абстрактный T. Например T может быть млекопитающим или каким-то другим видом. Проблему кошек с собаками это не решит, но всё таки сделает работу с животными лучше. Например если мы работаем с ними на уровне млекопитающих, будет гораздо меньше случаев необходимости привидения к конкретному типу конкретного животного.

hummer
() автор топика
Последнее исправление: hummer (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Запятую пропустил, перед животным.

Он всё правильно написал, животное.

hummer
() автор топика
Ответ на: комментарий от no-such-file

Сам себе придумал проблему, теперь героически решаешь. При сериализации сохраняй тип объекта, балда.

Сериализацией занимается другой и не мой код, балда. Тип объектов можно сохранять и я это даже пробовал делать, но это неудобно. Тот другой код может вообще не знать моих классов или вообще не быть написан на Java.

Если речь про рандомно приходящие их астрала данные, то какого хера ты их собрался натягивать на какие-то объекты, а не парсить динамически?

Вот и расскажи как правильно это делать. Какую модель данных использовать и как вообще писать сервисы? ObjectNode из Jackson и дёргать данные через Json Path?

hummer
() автор топика
Ответ на: комментарий от x3al

Не с той стороны подходишь к проблеме. Вопрос не в

Проблема такого кода ещё и в том, что тип T всё равно статический и поселить, например, кошек с собаками в один список не получится

а в том, что твои функции, принимающие объекты этого списка, могут ожидать либо только кошек, либо только собак. Ну или и тех, и других. И в зависимости от того, что ты хочешь, у тебя будет разная схема, по которой ты провалидируешь и распарсишь этот JSON.

А идеи вроде

Вообще отказаться от Java и писать серверный код на JavaScript под node.js?

вместо понимания схем означают, что твой код рано или поздно навернётся в рантайме.

Проблема вообще не связана ни с парсированием, ни с валидацией JSON. Проблема именно в удобной моделе данных. В REST контроллере можно указать тип входных данных Object и ObjectMapper, во время десериализации, создаст структуру из вложенных Map-ов. Но работать с такой модельню данных очень неудобно.

hummer
() автор топика
Ответ на: комментарий от fernandos

ООП на прототипах, что такое прототипная модель данных?

Основанная на прототипах.

Ну, динамическая типизации в сочетании со см. выше.

Вот именно.

Кошки и собаки — экземпляры объектов типа-наследника типа «животное», в чём проблема?

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

Как сложно, при чём тут жсон? Жсон — копактный (относительно) метод хранения данных, что вы дальше будете делать с ним — одному богу известно. Для (де)сериализации пишите свой датамаппер.

Этот метод хранения данных расчитан на динамическую типизацию и Object в JavaScript. Там никакой свой датамапер писать не нужно именно поэтому. В статически типизированном Java приходится извращаться и писать костыли (например кастомный датамапер) или использовать несвойственные этому языку модели данных. Например вложенные Map-ы или другие генерные представления JSON. Я пытаюсь выяснить, есть ли какие-то другие подходы или методологии.

hummer
() автор топика
Ответ на: комментарий от Legioner

Всё равно не понятно.

Вот есть у тебя базовый класс и два наследника. Animal, Dog, Cat. Есть у тебя в JSON-е поле «type»: «dog» или «type»: «cat» и остальные поля для животного/кота/собаки. Jackson без проблем такое разберёт и выдаст тебе List, с которым ты дальше будешь работать как тебе надо. Всё типобезопасно, проблем нет.

Для этого нужно либо обрамлять POJO соответствующими аннотациями из Jackson, либо писать дополнительные миксин классы на каждый такой POJO. Оба решения являются костылями и увеличивают вероятность ошибки. Например легко ошибиться в том какое значение поля type соответствует какому POJO классу. В случае с аннотациями у POJO ещё появляется и дополнительная зависимость от jackson-annotation. Если дата модель является библиотекой и шарится между проектами, придётся везде и всюду тащить и эту её зависимость.

Можно вместо type передавать поле class и в нём полное название конкретного POJO класса - ObjectMapper можно настроить на это без аннотаций и миксинов. Но это неудобно, прежде всего для тех, кто создаёт JSON-ы.

Всё это выглядит как попытка скрестить ужа с ежём. Скрестить можно, но только с некоторым количеством костылей и неудобств.

hummer
() автор топика
Ответ на: комментарий от neversleep

Я не джавист. А в чем здесь костыль? У тебя объекты разных типов перемешаны, по любому нужно как-то их различать. Сделай тогда на входе разные массивы для разных классов.

Правильно, нужно как-то различать, но в JSON не предусмотренны никакие стандартные механизмы для этого. Прежде всего потому, что JSON пришёл из JavaScript, в котором нет строгой типизации. Может быть более правильным подходом была бы работа с JSON в Java без строго типизированной модели данных, то есть с имитацией Object из JavaScript? Но и это было бы костылём, потому что такой подход не специфичен уже для Java. То есть в любом случае будет скрещивание ужа с ежём, в том или ином виде. Посмотри ещё и мой ответ Legioner-у выше.

hummer
() автор топика
Ответ на: комментарий от hummer

Правильно, нужно как-то различать, но в JSON не предусмотренны никакие стандартные механизмы для этого.

А разве они здесь должны быть? JSON даёт свободу - храни и делай потом с этими данными что хочешь.

Посмотри ещё и мой ответ Legioner-у выше.

Я согласен с Legioner`ом, с тем, что нужен type (или аналогичное поле), а дальше уже сам решай, Jackson или своя реализация. ИМХО, проблема выглядит надуманной.

Возможно некостыльный способ способ это использование astral.jar.

Не иначе 😀

neversleep ★★
()
Ответ на: комментарий от hummer

Проблему неправильно десериализации данных из JSON

А где именно она неправильная? У тебя объекты типа животное поступают на вход в виде жсонов. Зачем тебе их разделять на кошек и собак?

ya-betmen ★★★★★
()
Ответ на: комментарий от hummer

Проблема в том, что будут создаваться экземпляры животного, а не кошек и собак

А кошки и собаки не будут наследниками животного? Или вы тот джавист, который это ваше ООП не любит?

Этот метод хранения данных расчитан на динамическую типизацию и Object в JavaScript

Он не расчитан, он оттуда взят.

Там никакой свой датамапер писать не нужно именно поэтому. В статически типизированном Java приходится извращаться и писать костыли (например кастомный датамапер) или использовать несвойственные этому языку модели данных

Знали, какой язык выбирали, при чём тут жсон? Всегда что-то будет работать хорошо, а что-то плохо.

fernandos ★★★
()
Ответ на: комментарий от neversleep

А разве они здесь должны быть? JSON даёт свободу - храни и делай потом с этими данными что хочешь.

Причём тут вообще свобода? JSON просто стал популярным и зачастую бездумно используется за пределами своей естественной среды обитания.

Я согласен с Legioner`ом, с тем, что нужен type (или аналогичное поле), а дальше уже сам решай, Jackson или своя реализация. ИМХО, проблема выглядит надуманной.

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

hummer
() автор топика
Ответ на: комментарий от fernandos

Проблема в том, что будут создаваться экземпляры животного, а не кошек и собак

А кошки и собаки не будут наследниками животного?

Будут, но кто же их создаст?

Он не расчитан, он оттуда взят.

Тавтологию глаголишь, капитан.

Знали, какой язык выбирали, при чём тут жсон? Всегда что-то будет работать хорошо, а что-то плохо.

Не я выбирал.

hummer
() автор топика
Ответ на: комментарий от ya-betmen

А где именно она неправильная? У тебя объекты типа животное поступают на вход в виде жсонов. Зачем тебе их разделять на кошек и собак?

Затем, что на вход могут прийти и кошки с собаками, но их десериализация будет неверной.

hummer
() автор топика
Ответ на: комментарий от hummer

Отвыкайте шланговать, вы или наследников создайте, или жаловаться прекратите.

Для «хочу всё и много» надо было брать другой язык.

fernandos ★★★
()
Ответ на: комментарий от hummer

Тогда

Проблема такого кода ещё и в том, что тип T всё равно статический и поселить, например, кошек с собаками в один список не получится

Получится.

Вы тред создали потому, что вам лень писать даиамаппер и, вероятно, фабрику?

fernandos ★★★
()
Ответ на: комментарий от hummer

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

И что? Ты документацию не пишешь для своего API? Валидацию входных данных не делаешь? Если так, то да, Хьюстон, у нас проблема.

neversleep ★★
()
Ответ на: комментарий от hummer

что делает использование этого поля источником проблем и ошибок.

Я прошу прощения, а что, НЕиспользование этого поля в js как-то спасает от проблем и ошибок?

unC0Rr ★★★★★
()
Ответ на: комментарий от hummer

Естественно, десериализация делается ровно так же, как и сериализация. Делать одно без другого — не очень осмысленно.

Miguel ★★★★★
()
Ответ на: комментарий от hummer

ты все правильно понимаешь, только акцент делаешь немного не на том, дело не в том что жс динамически типизированный, а в том, что он имеет не строгую(слабую) типизацию, а джава строгую(сильную), в этом и вся проблема. Идея с дополнительными метаданными весьма рабочая и я видел такое решение не раз, тут особо ничего не попишешь с этой ситуацией именно из-за озвученного выше.

еще аноним там писал предложения про валидацию по схеме, это тоже вариант, это и есть по сути твои дополнительные метаданные.

выглядит это приблизительно так, но возможны и другие варианты реализации этой идеи https://www.baeldung.com/introduction-to-json-schema-in-java

abcq ★★
()
Последнее исправление: abcq (всего исправлений: 2)
Ответ на: комментарий от fernandos

Кошки и собаки — экземпляры объектов типа-наследника типа «животное», в чём проблема?

Подозреваю, что во входном объекте json'а не написано, что это за покемон.

crutch_master ★★★★★
()
Ответ на: комментарий от crutch_master

Зачем, достаточно лишь либо ввести эти метаданные с информации о типе, или написать валидатор который будет пытаться сопоставить то что приходит жсон с теми видами объектов в которые этот джсон должен быть десериализован, и если это не выходит сделать, отвечать посылающей стороне «вы кто такие я вас не звал идите на хер», с пояснением какая часть джсона не прошла валидацию, по причине невозможности однозначно понять что это за типы в этом джсоне и как их десериализовать в имеющийся пак объектов системы. Обычно такая ситуация будет возникать лишь тогда, когда посылающая сторона при сериализации факапнула и на выходе получился поломанный джсон с какими-нибудь нанами и андефайнд или нулами внутри, или вообще поломанной структурой.

abcq ★★
()
Ответ на: комментарий от hummer

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

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

В статически типизированном Java приходится извращаться и писать костыли (например кастомный датамапер) или использовать несвойственные этому языку модели данных.

Ну почему костыли. Тебе надо разложить как-то данные в стат. классы, вот и любись. Что в этом такого? Делай фабрику животных, как все делают. Как ты будешь определять какой набор полей кому соответствует - уже нюансы. Можешь собрать хоть соответствие класс - набор полей, по нему ориентироваться и дёргать конструктор.

crutch_master ★★★★★
()
Ответ на: комментарий от abcq

Ему лень всем этим заниматься, хочется взять что пришло, херак-херак и обработать. Валидировать json в любом случае придётся на набор полей и допустимые значения и придётся писать специальную херобору для этого. Потому что с хер пойми какими данными работать нельзя, у тебя весь код будет состоять на 80% из проверок и работать в итоге хер пойми как.

crutch_master ★★★★★
()
Ответ на: комментарий от abcq

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

А чем нестрогая типизация отличается от динамической? По-моему это то же самое, только разными словами.

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

Я не говорил, что она не рабочая. Но это всего лишь костыль, который не всегда приемлем.

еще аноним там писал предложения про валидацию по схеме, это тоже вариант, это и есть по сути твои дополнительные метаданные.

Как мне поможет валидация по схеме? У меня проблема не с валидацией, а с десериализацией.

Была идея генерировать модель данных (POJO классы) из схемы и передавать id схемы внутри type, но к сожалению это плохо работает. Во-первых схема вообще не предназначена для кодогенерации. Во-вторых реализация этой кодогенерации не поддерживает все возможности Json Schema и дженерик типы в Java.

hummer
() автор топика
Ответ на: комментарий от hummer

Будут, но кто же их создаст?

Я не понял до конца, что тебе надо. Ты или вообще не хочешь писать классы или хочешь просто задекларировать класс и чтобы влетающий жсон туда разложился. Во втором случае берешь JsonObject и рефлексия в помощь. Список полей есть, конструктор вызвать можно, единственный костыль, который понадобится - это кодоген. Ну, а что вы хотели от убогой жабки.

crutch_master ★★★★★
()
Ответ на: комментарий от crutch_master

ну так и есть по сути, но и жс его от валидации в общем-то не спасет, херак-херак может и позволит сделать, но доверять таким данным без валидации стремновато

abcq ★★
()
Ответ на: комментарий от hummer

А чем нестрогая типизация отличается от динамической? По-моему это то же самое, только разными словами.

В нестрогой статической можно "1" + 2, но нельзя var x = "test". Т.е. типы должны быть известны на этапе компиляции, компилятор делает автоприведение, где это можно. При динамической типизации все типы определяются прямо в рантайме.

crutch_master ★★★★★
()
Ответ на: комментарий от abcq

Да, жс спасёт только от ооп тягомотины и всяких маперов. Провалидировал и уже можешь делать cat.purr или рассовывать всё это по классам не отрывая жопу от стула. Например, то для чего в жабке городят всякие lombook с @Getter @Setter решается стандартными возможностями языка. НО у тебя всё динамическое и когда ты обосрешься и сделаешь cat.pur никто тебя не прикроет.
Вот если бы в жабку завезли что-нибудь стандартное для кодогена на этапе сборки.. Хотя, там и с рефлексией не очень удобно работать.

crutch_master ★★★★★
()
Последнее исправление: crutch_master (всего исправлений: 3)
Ответ на: комментарий от hummer

А чем нестрогая типизация отличается от динамической?

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

Как мне поможет валидация по схеме? У меня проблема не с валидацией, а с десериализацией.

валидация дает вам информацию о типах, которая и является камнем преткновения при десериализации из джсон, когда не понятно что в нем и каких типов.

abcq ★★
()
Ответ на: комментарий от crutch_master

И отличить по значению нельзя? Тогда ваши кошки идентичны собакам.

Ну и жаловаться на жсон в этом случае — какой-то бред.

fernandos ★★★
()
Ответ на: комментарий от fernandos

И отличить по значению нельзя?

По набору полей. Но это же надо на жабке писать руками класс с полями, потом писать мапер, валидировать, а он не хочет, хочет, чтобы само.

crutch_master ★★★★★
()
Ответ на: комментарий от crutch_master

У всех свои недостатки :3 Лишь бы не было войны (внесения в язык новшеств, ломающих обратную совместимость)

abcq ★★
()
Ответ на: комментарий от crutch_master

Я не понял до конца, что тебе надо

Откинуться на кресле, и чтобы экземпляры сами создавались. И нужные.

Ну, а что вы хотели от убогой жабки.

Это не убогость жабки, это специальные ограничения. Представьте, что к ооп-мании уровня «джава работает на 1 миллиарда устройств» добавится динамическая типизация и ко.

fernandos ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.