Сделать сериализуемый json без схемы с помощью веб-API

У меня есть строка JSON, которая содержит сложный, вложенный объект и, вероятно, изменит схему в будущем. Я хотел бы передать его веб-API для сериализации в конвейере согласования контента по мере необходимости.

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

Так, например, скажем, у меня есть следующая строка:

    @"{
      name: "Dan"
      children: [
         {
            name: 'Fred',
         },
         {
            name: 'Fannie',
            age: 30,
            children: {
                own: [
                    {name: "Barney"},
                    {name: "Angela"}
                ],
                adopted: {
                    {name: "Sven"}
                }            
            }  
        }
    }"

Я не знаю, какова его схема, и она может измениться в любое время, я просто хочу иметь возможность отправлять ее через веб-API с надлежащим согласованием содержимого.

Я могу сделать JObject.Parse(...), но веб-API не может правильно обрабатывать JObject. Он будет правильно обрабатывать словари, но я не могу понять, как использовать JSON.Net для десериализации словарей произвольной глубины вложенности.


person George Mauer    schedule 29.08.2014    source источник
comment
Вы только что сказали, что хотите сериализовать строку JSON как JSON?   -  person Nick    schedule 30.08.2014
comment
Вы всегда можете использовать JObject.Parse(json), который возвращает проанализированный JObject (который также реализует IDictonary). Но я не понимаю, как вы хотите получить из него свойства, ничего об этом не зная.   -  person L.B    schedule 30.08.2014
comment
@ФУНТ. Я хочу отправить его клиенту с помощью веб-API. Web Api может сериализовать его как xml, json или что-то еще в зависимости от доступных обработчиков. Однако он не может перейти в xml из JObject   -  person George Mauer    schedule 30.08.2014
comment
@George Mauer - Хорошо, но в вашем вопросе говорится, что у меня есть строка JSON ... Я хотел бы передать ее веб-API для сериализации в XML или JSON. В любом случае, как бы вы вообще справились с таким объектом? Если вы не знаете его схему, вы не знаете его свойств. Вы просто взаимодействуете с ним исключительно посредством отражения?   -  person Nick    schedule 30.08.2014
comment
@Nick Я взаимодействую с ним на стороне клиента. Это ничем не отличается от того, что делают люди, которые используют Couch или Riak, когда они напрямую обращаются к веб-серверу базы данных, я просто хочу, чтобы согласование контента обрабатывалось WebApi. Веб-API прекрасно работает с вложенными словарями, анонимными типами или поставщиками динамических типов, так что да, на каком-то уровне WebApi, по крайней мере, использует отражение.   -  person George Mauer    schedule 30.08.2014
comment
Вы должны поддерживать XML? Если нет, вы можете удалить этот модуль форматирования, а затем использовать JObject с сигнатурой метода Object.   -  person John Koerner    schedule 30.08.2014
comment
Да, я хотел бы поддерживать xml из этого. В идеале, если кто-то вводит URL-адрес непосредственно в свой браузер, он должен возвращать его в формате xml, а не в формате json.   -  person George Mauer    schedule 30.08.2014
comment
Dictionary<TKey,TValue> не сериализуем. Вы можете реализовать свой собственный сериализуемый словарь, и это уже сделал кто-то другой: weblogs.asp.net/pwelter34/ 444961 Используя это, вы могли бы просто вложить объекты SerializableDictionary внутрь значения SerializableDictionary?   -  person Nick    schedule 30.08.2014
comment
@Nick Dictionary<TKey,TValue> можно сериализовать с помощью Json.Net   -  person L.B    schedule 30.08.2014
comment
@GeorgeMauer это ваш вопрос, как я могу преобразовать JObject в XElement, короче говоря?   -  person L.B    schedule 30.08.2014
comment
Нет... это как преобразовать строку json во что-то, что можно сериализовать с помощью WebApi. надо сменить тему...   -  person George Mauer    schedule 30.08.2014
comment
@GeorgeMauer Тогда почему бы просто не преобразовать JSON в объект, а затем при необходимости сериализовать объект в JSON в XML? Я думаю, вы сбиваете всех с толку в том, чего вы пытаетесь достичь, потому что вы не описали проблему кратко.   -  person mason    schedule 30.08.2014
comment
@mason Конечно, я мог бы преобразовать его в объект. Однако класс не будет бессхемным, и мне придется поддерживать его каждый раз, когда базовые данные изменяются, даже если мне все равно, что это на сервере. Я мог бы использовать словарь, но Json.Net, похоже, не десериализует вложенные словари, я мог бы использовать JObject, но тогда веб-API задыхается во время согласования содержимого, когда запрашивается xml. Не саркастически, я просто не понимаю, где люди сбиты с толку - разве это не просто базовые вещи для обсуждения контента?   -  person George Mauer    schedule 30.08.2014
comment
@GeorgeMauer Почему бы не десериализовать в анонимный тип? Затем сериализуйте анонимный тип в JSON или XML. Или используйте Json.NET, чтобы преобразовать его в XML, который поддерживается в их документации.   -  person mason    schedule 30.08.2014
comment
@mason Я бы с удовольствием, ты знаешь, как это сделать? Я играл с ним, и я не могу понять это. Как я уже упоминал в вопросе, DeserializeAnonymousType на самом деле не делает то, что кажется - вам все равно нужно предоставить тип шаблона для того, как именно все должно быть десериализовано. Если бы я мог преобразовать JObject в анонимный тип, это сработало бы отлично.   -  person George Mauer    schedule 30.08.2014
comment
Вы спрашиваете, как сделать что-то с помощью API без необходимости кодировать это самостоятельно, что невозможно сделать в API. Когда люди говорят вам, что API не поддерживает это, вы говорите им, что они просто переформулируют проблему. Я не знаю, что вы ожидаете получить здесь. Если это не поддерживается API, то оно не поддерживается API. Период. Напишите свою функцию. Если вам нужна помощь в написании этой функции, я уверен, что люди здесь помогут вам с этим, но никто не может просто магическим образом добавить новую функциональность в API для вас.   -  person Nick    schedule 02.09.2014
comment
@Ник, я тоже пришел к такому выводу. Дело в том, что никто на самом деле не сказал, что это невозможно, потому что X. Что-то в том, как я задал вопрос, явно вызвало много путаницы (хотя я действительно не уверен, что - это очень простая вещь что это шокирует не поддерживается). На самом деле я сел и провел более длительное исследование того, в чем проблема, а также идиоматический способ ее устранения. Смотрите мой ответ о том, что на самом деле происходит под капотом.   -  person George Mauer    schedule 02.09.2014


Ответы (3)


Как описано в их документации, Json.NET может напрямую преобразовывать JSON в XML. Вот пример из их документации:

string json = @"{
  '?xml': {
    '@version': '1.0',
    '@standalone': 'no'
  },
  'root': {
    'person': [
      {
        '@id': '1',
        'name': 'Alan',
        'url': 'http://www.google.com'
      },
      {
        '@id': '2',
        'name': 'Louis',
        'url': 'http://www.yahoo.com'
      }
    ]
  }
}";

XmlDocument doc = (XmlDocument)JsonConvert.DeserializeXmlNode(json);
// <?xml version="1.0" standalone="no"?>
// <root>
//   <person id="1">
//   <name>Alan</name>
//   <url>http://www.google.com</url>
//   </person>
//   <person id="2">
//   <name>Louis</name>
//   <url>http://www.yahoo.com</url>
//   </person>
// </root>

Поэтому либо передайте JSON в виде строки (без изменений), либо преобразуйте в XML на основе запрошенного типа контента.

person mason    schedule 29.08.2014
comment
Да, но как это согласуется с согласованием содержимого веб-API? - person George Mauer; 30.08.2014
comment
@GeorgeMauer Конечно, это может помочь. content negotiation это не волшебство. Вы прочитаете заголовок Accept из запроса, а затем соответствующим образом сериализуете содержимое (xml или json) (кстати: я делал это много раз с WCF вручную) - person L.B; 30.08.2014
comment
@LB Правильно, я могу делать все что угодно вручную - если бы я хотел, я мог бы даже написать компонент OWIN, который делает что-то супер-вручную. Но я пытаюсь сделать это так, как это идолично для WebApi, и я не понимаю, как это вписывается в эту схему. - person George Mauer; 30.08.2014
comment
@GeorgeMauer Вам придется это написать :) - person L.B; 30.08.2014
comment
@L.B, однако, продумайте процесс - то, что вы возвращаете, ничем фундаментальным образом не отличается от любого другого объекта. В лучшем случае вы можете вернуть JObject, затем создать пользовательский IContentNegotiator, чтобы обнаружить это, затем создать пользовательский MediaTypeFormatters для каждого типа, к которому вы хотите сериализовать этот объект, что для начала является взломом концепции MediaTypeFormatters, но что еще вы можете делать? И все это ради того, что должно быть глупо просто. Просто не может быть, чтобы это был лучший подход. Как сказал сам Мейсон, JObject =› анонимный тип или словарь будут работать, но как? - person George Mauer; 30.08.2014
comment
@GeorgeMauer Я никогда не использовал WebApi, но с WCF вы можете вернуть Message, в котором вы вручную устанавливаете содержимое и заголовки HTTP. Итак, все дело в написании метода ToSomething(object o,responseType). - person L.B; 30.08.2014
comment
Да, вы можете сделать это, но это не соответствует идиоме WebApi. Опять же, я могу сделать это вручную десятком разных способов, я могу написать рекурсивную функцию, чтобы переписать JObject в словарь, проблема не в том, что я не знаю, как это сделать, а в том, что я не знаю, как это сделать. делать это идолопоклонно и не приведет к большой головной боли при обслуживании - person George Mauer; 30.08.2014
comment
@GeorgeMauer Я понимаю, чего вы ожидаете, но не думаю, что вы получите то, что ожидаете. - person L.B; 30.08.2014

Итак, после длительного расследования мне удалось свести проблему к следующему:

  • Web Api по умолчанию использует (теперь в основном заброшенный) XmlSerializer, когда указан тип содержимого xml.
  • XmlSerializer не может сериализовать JObjects
  • Не существует встроенного способа преобразования JObjects произвольной глубины вложенности в словари (хотя пользовательская рекурсивная функция для этого не будет слишком сложной).

Таким образом, способ решить эту проблему состоит в том, чтобы реализовать собственный модуль форматирования xml, который преобразует словари в словари или использует что-то вроде XSerializer< /а>. Звучит просто, но во время связанного 30-минутного всплеска мне не удалось запустить свой собственный модуль форматирования.

В конце концов, я просто удалил форматировщик xml, так как он не является строго необходимым для моего API и доставляет больше хлопот, чем того стоит.

person George Mauer    schedule 02.09.2014
comment
JsonConvert.DeserializeObject<dynamic>(json) не может обрабатывать произвольные словари глубины вложенности? Слышал, что можно, но сам не пробовал. Возможно, меня дезинформировали. - person Nick; 03.09.2014
comment
@Nick DeserializeObject мог бы справиться с этим, но речь идет именно о другом направлении - сериализации. - person George Mauer; 03.09.2014
comment
Вы сказали, что нет встроенного способа конвертировать JObjects произвольной глубины вложенности в словари (хотя пользовательская рекурсивная функция для этого не была бы очень сложной). Это десериализация. - person Nick; 04.09.2014
comment
@Ник, нет, десериализация переходит от строки (или какого-либо другого плоского формата, такого как байты) к структурированному объекту. Сериализация переходит от структурированного объекта к плоскому формату. JObject во вложенный словарь не является ни тем, ни другим, это просто сопоставление. - person George Mauer; 04.09.2014
comment
Вы удивляетесь, почему никто не может понять ваш вопрос, вы сами продолжаете говорить о сериализации/десериализации, но теперь вдруг это не то, что вы хотите делать... Ну, если JsonConvert.DeserializeObject<dymanic>() может обрабатывать вложенные словари, может быть, JObject.ToObject<dynamic>() может? - person Nick; 05.09.2014
comment
@Nick Я полагаю, что это может быть так, но нет, я действительно действительно хочу сериализации. Возможно, я не очень ясно выразился (я думал, что понял?), сопоставление JObject со словарями было бы промежуточным шагом, поскольку сами словари могут быть сериализованы без проблем. Я не пробовал ToObject<dynamic> раньше, но только что попробовал. Он просто возвращает еще один JObject просто as dynamic. - person George Mauer; 05.09.2014

Я добился именно этого, приведя свои динамические объекты к типу List<Dictionary<string, object>>. Уточните, пожалуйста, это то, что вам нужно.

Хорошо, после того, как вы обновили свой вопрос, стало ясно, что вам даже не нужно сериализовать его в словарь. Просто используйте Json.NET (он встроен в WebApi и используется как сериализатор JSON по умолчанию).

Кроме того, убедитесь, что в будущем вы передаете допустимый json.

Рабочий пример (WebApi2)

Полезная нагрузка JSON

{
    "name": "Dan",
    "children": [
        {
            "name": "Fred"
        },
        {
            "name": "Fannie",
            "age": 30,
            "children": {
                "own": [
                    {
                        "name": "Barney"
                    },
                    {
                        "name": "Angela"
                    }
                ],
                "adopted": [
                    {
                        "name": "Sven"
                    }
                ]
            }
        }
    ]
}

Код

public class DefaultController : ApiController
{
    [Route, HttpGet]
    public IHttpActionResult Test()
    {
        var json = "{\"name\": \"Dan\",\"children\": [{\"name\": \"Fred\"},{\"name\": \"Fannie\",\"age\": 30,\"children\": {\"own\": [{\"name\": \"Barney\"},{\"name\": \"Angela\"}],\"adopted\": [{\"name\": \"Sven\"}]}}]}";

        var obj = JsonConvert.DeserializeObject(json);

        return Ok(obj);
    }
}

Ответ введите здесь описание изображения

person Stan    schedule 29.08.2014
comment
@GeorgeMauer Проверить ответ - person Stan; 30.08.2014
comment
Что произойдет, если вы отправите заголовок Accept, содержащий application/xml, в свой тестовый метод? - person Brian Rogers; 30.08.2014
comment
@BrianRogers По умолчанию WebApi не может десериализовать объект, который не соответствует XML. Однако вы можете удалить сериализатор XML по умолчанию и добавить свой собственный, который сделает это. stackoverflow.com/a/19085236/440611 - person Stan; 30.08.2014
comment
Это просто повторение проблемы, о которой я упоминал, когда WebApi может сериализовать JObject в json, но не в xml. Разочаровывает то, что анонимный тип или словари могут сериализоваться. Таким образом, трюк состоит в том, чтобы преобразовать JObject в один из них. - person George Mauer; 31.08.2014