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)

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

Не знаю. Просто полюбопытствовал. А так мимокрокодил.

ados ★★★★★
()

отказаться от JSON в пользу какого-то другого формата, у которого метаданные являются обязательными

Хотя, у обезьянки есть проблески разума. Может она догадается взять палку в руки?

cocucka ★★★★☆
()

Что уже делалось и что предлагается?

anonymous
()

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

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

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

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

то есть создатель читает поток и создает обьект нужного типа из данных в нем.

ps. чтобы поместить разнзых животных в клетку надо иметь список на Animals, а не T. а актуальный T выяснять опять же по тегу например хранимому в обьекте или за счет рантаймовой поддержки выяснения актуального типа.

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

Ладно, чуствительный ты наш, у тебя есть два варианта: либо ты сдаёшь свою лицензию java developer’а и продолжаешь перекладывать json’ы из монго в браузер и обратно, либо ты всё таки понимаешь важность статической типизации для производительности твоего сервиса и выбираешь соответствующий формат данных.

cocucka ★★★★☆
()

man полиморфизм, json schema validation

anonymous
()
Ответ на: комментарий от cocucka

Смешная Сосиска.

Тут два пути - проверять всё руками или прикрутить Typescript сбоку бантиком. Ну а дальше интерфесами обмазаться по самое небалуйся.

Иногда хорошо первым, иногда - вторым.
И важность статики переоценена.

anonymous
()

в C# есть тип dynamic в который распарсить json и работать с ним +/- как в динамических языках

Но требуется такое крайне редко, обычно формат данных заранее известен

Ford_Focus ★★★★★
()

Прочитал твой пост, а проблему всё равно не понял. Лучше покажи конкретный пример JSON-а и что ты хочешь видеть после десериализации.

Legioner ★★★★★
()

json это про хранение структурированных данных, а не про сериализацию/десириализацию. Даже питонисты знают это и используют pickle вместо json-а, когда нужна сериализация/десириализация питоновских объектов. А вот данные лучше в json-е хранить.

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

Проблема по большей части лишь в десериализации. Во время десериализации ObjectMapper должен знать в какой класс инстанцировать тот или иной кусок JSON-а. По умолчанию эта мета информация берётся из самого класса, но класс статически типизирован и поэтому такая метаинформация не всегда годится. Для тех же целей ObjectMapper может использовать миксин классы или специальные аннотации в самих POJO. Это помогает и добавляет полиморфизм, но это костыль, который необходимо поддерживать и согласовывать с клиентом. Так же можно создать кастомный ObjectMapper, который будет брать fully qualified class name из дополнительного поля в JSON, но это тоже костыль.

Сам подход видится мне неверным. JSON пришёл из динамически типизированого и прототипно ориентированного языка - JavaScript. В нём не предусмотрена поддержка полиморфизма и статической типизации. Любая попытка добавить такую функциональность превращается в костыль или даже набор костылей. С другой стороны это достаточно популярный формат и отказаться от него вовсе не просто.

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

json это про хранение структурированных данных, а не про сериализацию/десириализацию.

Предложи другой формат для передачи данных в REST запросах или друой способ обработки REST запросов без десериализации этих данных в те или иные POJO.

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

Не понимаю в чем суть проблемы. Можно делать как угодно. Можно обмазать родительский класс аннотациями JsonTypeInfo и JsonSubTypes. И все будет само понимать куда десериализироваться по встроенному полю type. Можно прикрутить схемы для валидации. Можно шляться по JsonNode и самому пытаться раскурить что тебе прислали. Все подходы уже есть в практике.

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

А нафига тебе в Java REST? У JS-а проблем с JSON-ом меньше, родной как-никак. А в Java будь добр писать сериализовалку/десерилизовалку ручками (а как это уже твои проблемы, способов вагон). Ну и да, для REST json не обязателен, просто популярен. Хоть xml фигачь, чисто твоя проблема что с ним дальше делать.

peregrine ★★★★★
()

Как в жабе — не знаю, а в Scala есть библиотеки, позволяющие автогенерить JSON-представление.

На ум приходит что-то типа SOAP.

Брось каку.

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

Даже питонисты знают это и используют pickle вместо json-а, когда нужна сериализация/десириализация питоновских объектов.

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

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

Как в жабе — не знаю, а в Scala есть библиотеки, позволяющие автогенерить JSON-представление.

Ты про генерирование POJO из Json Schema? Пробовал, оно банально не доделанное и вообще любая схема мало подходит для автогенерации кода. Например там нет поддержки генериков.

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

Вообще отказаться от Java

Пора бы уже признать, что джава — устаревшая, проприетарная, оверхайпнутая говнина, на которой уже давно никто не пишет ничего нового. Для формошлёпства есть Qt, для сервисов — Go и Rust, для низкого уровня — С/С++, для веба — JS, для остального — R и Python. Джаве просто некуда втиснуться. Идея вездесущей виртуальной машины провалилась с треском. За счёт гугла ещё пока держатся, но мы-то знаем про адекватность решений этой конторки.

anonymous
()
Ответ на: комментарий от peregrine

Даже питонисты знают это и используют pickle вместо json-а

Помимо прочих проблем с pickle, главное, о чём не стоит забывать — десериализация pickle исполняет произвольный питоновский код.

PolarFox ★★★★★
()

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

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

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

Как правильнее решить эту проблему?

Какую проблему? Потерю части данных при передаче? Так не теряй её, запиши в жсон к какому типу относится твой объект. Или ты наоборот хочешь данные, которых у тебя нет и которые неоткуда взять? Ну тогда везде в коде используй общий тип животное.

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

ya-betmen ★★★★★
()

А зачем делать List animals, где T extends Animal, когда можно сделать List? Описанную проблему это не решает, но тебе же все равно придется приводить к нужному типу каждое животное, если потребуются животноспецифические методы

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

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

Эм... в просто List кто-нибудь запишет Empire State Building и приводи его потом к Animal. Не зря в Scala этого нет.

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

А зачем делать List<T> animals, где T extends Animal, когда можно сделать List<Animal>? Описанную проблему это не решает, но тебе же все равно придется приводить к нужному типу каждое животное, если потребуются животноспецифические методы

Сообщение должно было выглядеть так

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

соответственно, в список будут попадать и так только объекты, которые extends Animal

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

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

Miguel ★★★★★
()

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

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

Вообще хотел поставить зачеркнутую, но выглядело некузяво ,

ya-betmen ★★★★★
()

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

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

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

А идеи вроде

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

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

x3al ★★★★★
()

прототипной моделью данных

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

Грубо говоря там есть Object в который можно динамически добавлять всё что угодно - как поля, так и функции

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

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

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

Если данные приходят в виде JSON, их десериализация в такие модели данных со статической типизацией становится нетривиальной и это основная проблема, которую я хотел бы обсудить

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

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

А вот это твоя задача, если тебе с json-ом возиться не хочется. Я не джавист и не знаю что у вас есть готового

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

Эм... скорее, молоток для ремонта микроэлектроники.

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

Так же можно создать кастомный ObjectMapper, который будет брать fully qualified class name из дополнительного поля в JSON, но это тоже костыль.

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

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

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

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

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

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

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

Сначала все кладешь в List animals потом instanceof

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

Удобно же. Есть у тебя структура с 20 полями, которые ещё и меняться могут в процессе эксплуатации со временем. Тебе надо сейчас три поля. Но никто не может поручиться, что через год не понадобится ещё что-нибудь. Ты хранишь эти 3 поля и рядом исходный JSON. Через год понадобилось ещё одно поле, создал, простым update-ом заполнил его. Через полтора года срочно потребовалось сделать разовый отчёт по пятому полю. Написал запрос, вытащив нужные данные прямо из JSON и всё.

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

Есть у тебя структура с 20 полями, которые ещё и меняться могут в процессе эксплуатации со временем. Тебе надо сейчас три поля. Но никто не может поручиться, что через год не понадобится ещё что-нибудь. Ты хранишь эти 3 поля и рядом исходный JSON. Через год понадобилось ещё одно поле, создал, простым update-ом заполнил его. Через полтора года срочно потребовалось сделать разовый отчёт по пятому полю.

Это то так, но архитектура JSON на порядок менее универсальная чем к примеру struct …

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

Circe сделает из такого

sealed trait Animal
case class Dog(...dog fields...) extends Animal
case class Cat(...cat fields...) extends Animal

сериализатор и десериализатор, которые будут работать с JSON-ом примерно такого вида:

[
  {
    "Dog": {
      ...dog fields...
    }
  },
  {
    "Cat": {
      ...cat fields...
    }
  },
  ...
]
Miguel ★★★★★
()

В JavaScript вообще не нужно объявлять какую-то заранее известную дата модель.

Даже в жс никто не пихает несовместимые модели в один список. Проблема высосана из пальца.

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

для сервисов — Go и Rust

Как бы мне помог Go в данном случае?

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