Использовать пустую строку, нулевое или удалить пустое свойство в запросе / ответе API

25

При передаче объекта через API, например, в формате JSON без схемы, каков идеальный способ вернуть несуществующее строковое свойство? Я знаю, что есть разные способы сделать это, как в примерах в приведенных ниже ссылках.

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

С точки зрения кода, ограничение строковых функций для работы только с одним типом, т. Е. string(Не нулевым), облегчает их доказательство; избегание нуля также является причиной наличия Optionобъекта. Итак, если код, который генерирует запрос / ответ, не использует нуль, я думаю, что код на другой стороне API не будет вынужден использовать ноль тоже.

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

Итак, что является лучшим способом сделать это, чтобы решить эти проблемы? Зависит ли это от формата передаваемого объекта (схема / без схемы)?

imel96
источник
2
Также обратите внимание, что Oracle обрабатывает пустые строки и нулевые строки одинаково. И вот тут: в бумажном вопроснике, как вы можете отличить ответ без ответа и ответ, состоящий из пустой строки?
Бернхард Хиллер
Если вы используете наследование, легко сказать. if( value === null) { use parent value; } Однако, если вы установите дочернее значение, даже в пустую строку (например, переопределите родительское значение по умолчанию пустым), то как вы «наследуете» значение? Для меня установка его в ноль будет означать «сбросить это значение, чтобы мы знали, использовать родительское значение».
Фрэнк Форте
Поскольку «Удалить пустое свойство» также является причиной «избегать» a null(это правда, что nullизбегается как таковой), спрашивающий означает «Вернуть ненулевое значение» [Объект] (то есть: пустая строка, пустой массив и т. Д.), Когда они напиши "избегай".
виолончель

Ответы:

18

TLDR; Удалить нулевые свойства

Первое, что нужно иметь в виду, это то, что приложения на их краях не являются объектно-ориентированными (и не функциональными, если программирование в этой парадигме). JSON, который вы получаете, не является объектом и не должен рассматриваться как таковой. Это просто структурированные данные, которые могут (или не могут) преобразовываться в объект. В общем случае ни одному входящему JSON не следует доверять как бизнес-объекту, пока он не будет проверен как таковой. Тот факт, что он десериализован, не делает его действительным. Поскольку JSON также имеет ограниченные примитивы по сравнению с внутренними языками, часто стоит создать DTO - JML -выравнивание для входящих данных. Затем используйте DTO для создания бизнес-объекта (или попытки ошибки) для выполнения операции API.

Когда вы рассматриваете JSON как просто формат передачи, имеет больше смысла пропускать свойства, которые не установлены. Это меньше, чтобы отправить через провод. Если ваш внутренний язык по умолчанию не использует пустые значения, вы, вероятно, можете настроить десериализатор на выдачу ошибки. Например, моя общая установка для Newtonsoft.Json переводит нулевые / отсутствующие свойства только в / из optionтипов F # и в противном случае выдаст ошибку. Это дает естественное представление о том, какие поля являются необязательными (с optionтипом).

Как всегда, обобщения только дошли до вас. Вероятно, есть случаи, когда свойство по умолчанию или нулевое свойство подходит лучше. Но ключ не в том, чтобы рассматривать структуры данных на границе вашей системы как бизнес-объекты. Бизнес-объекты должны иметь деловые гарантии (например, имя не менее 3 символов) при успешном создании. Но структуры данных, снятые с провода, не имеют реальных гарантий.

Кейси Спикман
источник
3
В то время как большинство современных сериализаторов имеют необязательные поля, опускание пустых значений в ответе не всегда является хорошей идеей, поскольку это может создать дополнительную сложность для обработки полей, допускающих обнуляемость. Таким образом, он действительно зависит от регистра , в зависимости от того, как ваша библиотека сериализации обрабатывает обнуляемые значения, и стоит ли (потенциальная) дополнительная сложность обработки этих обнуляемых значений действительно стоит сэкономить несколько байтов на запрос. Вы должны усердно работать, чтобы проанализировать ваши бизнес-кейсы.
Крис Cirefice
@ChrisCirefice Да, я думаю, что последний абзац охватывает это. Есть случаи, когда будет лучше использовать разные стратегии.
Кейси
Я согласен с тем, что JSON используется только как формат передачи, он не передает объект по проводам, как CORBA. Я также согласен, что свойства могут быть добавлены и удалены; представления могут меняться, элементы управления могут меняться, особенно в Интернете.
imel96
15

Обновление: я немного отредактировал ответ, потому что это могло привести к путанице.


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

С точки зрения разработчика API, существует только два типа свойств:

  • требуется (они ДОЛЖНЫ иметь значение своего определенного типа и НЕ ДОЛЖНЫ быть пустыми),
  • необязательно (они МОГУТ содержать значение своего определенного типа, но МОГУТ также содержать null.

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

С другой стороны, если необязательное свойство объекта не будет установлено и оставлено пустым, я предпочитаю в любом случае оставить их в ответе со nullзначением. Исходя из моего опыта, клиентам API легче реализовать синтаксический анализ, поскольку им не требуется проверять, существует ли свойство на самом деле или нет, потому что оно всегда есть, и они могут просто преобразовать ответ в свой пользовательский DTO, обрабатывая nullзначения по желанию.

Динамическое включение / удаление полей из сил реагирования, включая дополнительные условия для клиентов.


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

Энди
источник
Да, пустая строка является значением, и я использую nullдля ссылок. Смешивание значений со ссылками - одна из моих задач. Как вы отличаете необязательные поля с nullне необязательными строковыми полями, которые имеют nullзначение? Повторный анализ, разве не проверка существования свойств делает синтаксический анализатор более хрупким?
imel96
2
@ imel96 Необязательные поля НИКОГДА не могут быть нулевыми. Если что-то не является необязательным, оно ДОЛЖНО всегда содержать значение (определенного типа).
Энди
3
Это. Как частый потребитель API, я ненавижу, когда мне приходится иметь дело с «динамическими» структурами, которые возвращаются ко мне, даже если они имеют форму опускаемого необязательного поля. (также многие согласились, что есть большая разница между ZLS и Null). Я бы с радостью принял нулевые значения весь день. Как автор API, одна из моих целей - сделать потребление клиентов как можно более безболезненным, а это означает, что всегда ожидаемые структуры данных.
jleach
@DavidPacker, поэтому, если я правильно понимаю, вы используете, nullчтобы указать значение является необязательным. Таким образом, когда вы определяете объект, который имеет необязательное строковое свойство, а потребитель не имеет этого свойства, он должен отправить пустую строку для этого свойства. Это правильно?
imel96
2
@GregoryNisbet Не делай этого, пожалуйста. Это бессмысленно.
Энди
3

null Использование зависит от приложения / языка

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

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

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

  1. Планирую ли я добавлять будущие типы значений? Если так, то Option, вероятно, будет лучше для вашего дизайна интерфейса.
  2. Когда мне нужно проверять звонки потребителей? Статически? Динамически? До? После? Вообще? Если ваш язык программирования поддерживает это, используйте преимущества статической типизации, поскольку это позволяет избежать объема кода, который вы должны создать для проверки. Optionвероятно, лучше всего подходит для этого случая, если ваша строка не имеет значения NULL. Тем не менее, вам, вероятно, все nullравно придется проверять пользовательский ввод на наличие строкового значения, поэтому я бы, вероятно, отложил вопрос до первой строки: сколько типов значений я хочу / буду представлять.
  3. Означает ли nullошибка программиста в моем языке программирования? К сожалению, nullчасто это значение по умолчанию для неинициализированных (или неявно инициализированных) указателей или ссылок в некоторых языках. Является ли nullзначение приемлемым в качестве значения по умолчанию? Это безопасно в качестве значения по умолчанию? Иногда nullуказывает на освобожденные значения. Должен ли я предоставить потребителям моего интерфейса указание этих потенциальных проблем с управлением памятью или инициализацией в их программе? Что такое режим отказа такого звонка перед лицом таких проблем? Находится ли вызывающий абонент в том же процессе или потоке, что и мой, чтобы такие ошибки представляли высокий риск для моего приложения?

В зависимости от ваших ответов на эти вопросы, вы, вероятно, сможете определить, подходит ли nullвам ваш интерфейс.

Пример 1

  1. Ваше приложение критично для безопасности
  2. Вы используете какой-либо тип инициализации кучи при запуске, и nullэто возможное строковое значение, возвращаемое после неудачной попытки выделить место для строки.
  3. Существует вероятность того, что такая строка попадает в ваш интерфейс

Ответ: nullвероятно, не подходит

Обоснование: nullв этом случае фактически используется для указания двух разных типов значений. Первое может быть значением по умолчанию, которое пользователь вашего интерфейса может захотеть установить. К сожалению, второе значение - это флаг, указывающий, что ваша система работает неправильно. В таких случаях вы, вероятно, захотите потерпеть неудачу настолько безопасно, насколько это возможно (что бы это ни значило для вашей системы).

Пример 2

  1. Вы используете структуру C, которая имеет char *член.
  2. Ваша система не использует распределение кучи, и вы используете проверку MISRA.
  3. Ваш интерфейс принимает эту структуру в качестве указателя и проверяет, что структура не указывает на NULL
  4. Стандартное и безопасное значение char *члена для вашего API может быть указано одним значением:NULL
  5. После инициализации структуры вашего пользователя вы хотели бы предоставить пользователю возможность не явно инициализировать char *элемент.

Ответ: NULLможет быть уместным

Обоснование: существует небольшая вероятность того, что ваша структура пройдет NULLпроверку, но не будет инициализирована. Однако ваш API может быть не в состоянии учесть это, если у вас нет какой-либо контрольной суммы для значения структуры и / или проверки диапазона адреса структуры. Линтеры MISRA-C могут помочь пользователям вашего API, помечая использование структур перед их инициализацией. Однако, что касается char *члена, если указатель на структуру указывает на инициализированную структуру, NULLэто значение по умолчанию для неопределенного члена в инициализаторе структуры. Следовательно, NULLможет служить безопасным значением по умолчанию для char *члена структуры в вашем приложении.

Если бы он был в интерфейсе сериализации, я бы задал себе следующие вопросы о том, использовать ли null в строке.

  1. Является nullпоказатель потенциальной клиентской стороны ошибки? Для JSON в JavaScript это, вероятно, нет, поскольку nullне обязательно используется как указание на ошибку выделения. В JavaScript это используется как явное указание на отсутствие объекта в ссылке, которая будет установлена ​​проблематично. Однако существуют не-javascript-парсеры и сериализаторы, которые отображают JSON nullна нативный nullтип. Если это так, то возникает вопрос о том, nullнормально ли использование родного языка для вашей конкретной комбинации языка, анализатора и сериализатора.
  2. Влияет ли явное отсутствие значения свойства более чем на одно значение свойства? Иногда a nullфактически указывает, что у вас полностью новый тип сообщения. Для ваших потребителей формата сериализации может быть проще указать совершенно другой тип сообщения. Это гарантирует, что их проверка и логика приложения могут иметь четкое разделение между двумя различиями сообщений, которые предоставляет ваш веб-интерфейс.

Генеральный Совет

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

  • Какой тип (значения) значения принимает данное свойство
  • Какие диапазоны значений действительны для данного свойства.
  • Как составной тип должен быть структурирован. Какие свойства должны / должны / могут присутствовать в агрегатном типе?
  • Если это какой-то тип контейнера, сколько элементов может или должен содержать контейнер, и какие типы значений содержит контейнер?
  • В каком порядке возвращаются свойства или экземпляры типа контейнера или агрегата?
  • Какие побочные эффекты существуют при установке определенных значений и каковы побочные эффекты при чтении этих значений?
с обратным рассеянием
источник
1

Вот мой личный анализ этих вопросов. Он не подкреплен какой-либо книгой, бумагой, учебой или чем-то еще, только моим личным опытом.

Пустые строки как null

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

Какой-то глупый пример: скажем, есть атрибут, который хранит внешний ключ, и этот атрибут не определен или есть null, это будет означать, что отношение не определено, тогда как пустую строку ""можно понимать как определенное отношение и Идентификатор внешней записи - это пустая строка.

Не определено против null

Это не черная или белая тема. У обоих подходов есть свои плюсы и минусы.

В пользу явного определения nullценностей существуют следующие плюсы:

  • Сообщения более информативны, так как вы можете узнать все ключи, просто взглянув на любое сообщение.
  • Что касается предыдущего пункта, то проще потребителю кодировать и обнаруживать ошибки в потребителе данных: легче выявлять ошибки, если вы выбираете неправильные ключи (возможно, неправильно написано, может быть изменен API и т. Д.).

В пользу предположения, что несуществующий ключ равен семантике null:

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

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

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

bgusach
источник
Пример использования пустых строк - это то, что я подразумеваю под деталями реализации, то есть предположим, что API используется для представления строк базы данных. Будет ли это иметь какое-то значение, если не будет задействована ни одна база данных и она предназначена исключительно для передачи представлений объектов?
imel96
Это не должно быть деталью реализации. Мой пример на самом деле говорит о PK, которые связаны с БД, но я попытался объяснить, что пустая строка не равна nil / nothing / null. Другой пример: в игре есть объект персонажа, и он имеет атрибут «партнер». nullПартнер явно означает , что нет партнера на всех, но ""может быть понято как есть партнер , чье имя "".
bgusach
Я в порядке с нулевой ссылкой на партнера, значит, нет партнера, а также ссылка не является строкой. Но имя партнера - это строка, даже если вы допустите пустое значение в качестве имени партнера, разве вы не поймаете это значение и не замените его пустой строкой?
imel96
Если нет партнера, я бы не стал менять на nullпустую строку. Может быть, рендеринг в форме, но никогда в модели данных.
bgusach
Я не имел в виду ни одного партнера, партнер был бы объектом. Это тот партнер, nameо котором я говорил, вы позволите назвать имя партнера пустым?
imel96
1

Я бы поставил пустую строку в ситуации, когда строка присутствует, и она оказалась пустой строкой. Я бы поставил ноль в ситуации, когда я хочу явно сказать «нет, этих данных нет». И опустите ключ, чтобы сказать: «Нет данных, не беспокойтесь».

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

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

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

gnasher729
источник
Мне нравится ваша точка зрения на "если нужно, чтобы клиент отличился".
imel96
0

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

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

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

Так же, как и с любым вопросом «что лучше», у меня остается ответ: это зависит . Если вы хотите разделить ваш рабочий процесс и более четко захватить, когда что-то не установлено, используйте null. Если вы предпочитаете, чтобы программа просто продолжала в том же духе, используйте пустую строку. Важно то, что вы последовательны , выберите общий возврат и придерживайтесь этого.

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

Эрдрик Айронроуз
источник
Наличие «разделенного рабочего процесса» - это плохо. Допустим, на стороне производителя все чисто, методы строкового типа возвращают только strings, а не null. Если API использует нуль, то в какой-то момент производитель должен создать это nullдля соответствия API. Тогда потребителю nullтоже нужно справиться . Но я думаю, что я понял, что вы говорите, просто решите и определите API с правами, верно? Означает ли это, что нет ничего плохого в любом из них?
imel96
Да, все, что вы делаете в своем API, повлияет на то, как пользователь должен будет структурировать свой код, поэтому, учитывая ваш дизайн с точки зрения пользователя API, вы сможете определить, какой путь лучше. В конечном итоге это ваш API. Просто будь последовательным. Только вы можете решить плюсы и минусы подхода.
Эрдрик Айронроуз
0

Документ!

TL; DR>

Делайте так, как считаете нужным - иногда контекст, в котором он используется, имеет значение. Пример, привязка переменных к Oracle SQL: пустая строка интерпретируется как NULL.

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

  • ЗНАЧЕНИЕ NULL
  • Пусто (пусто)
  • Отсутствует (удалено)

Ваш код может действовать по-разному - запишите, как ваш код реагирует на него:

  • Fail (Exception и т. Д.), Возможно, даже не проходит валидацию (возможно, проверенное исключение) и не может правильно обработать ситуацию (NullPointerException).
  • Предоставьте разумные значения по умолчанию
  • Код ведет себя по-разному

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

  • Лечить Null и Missing то же самое
  • Обрабатывайте пустую строку именно так. Только в случае привязки SQL это можно считать пустым. Убедитесь, что ваш SQL ведет себя согласованно и ожидаемым образом.
Йо Йо
источник
Проблема в том, что без решения проблем разногласия случались довольно часто. Рассмотрим в командной среде, решение должно быть командным решением, во многих случаях это означает, что будет аргумент. Когда у вас есть несколько команд, каждая команда имеет право на свои собственные решения. Я видел API, которые я могу только догадываться, что они реализованы разными командами, которые не согласны друг с другом. Если кто-то может согласиться с одной вещью, документировать это тривиально.
imel96
0

tl; dr - если вы используете это: будьте последовательны в том, что это значит.

Если бы вы включили null, что бы это значило? Существует множество вещей, что бы это могло значить. Одного значения просто недостаточно для представления отсутствующего или неизвестного значения (и это только две из множества возможностей: например, отсутствует - оно было измерено, но мы еще не знаем его. Неизвестно - мы не пытались измерить Это.)

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

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

Grimaldi
источник