Как отправить разделитель групп (непечатаемые символы ascii) с помощью RestSharp RestClient из С#

Обновлено

Я полностью переработал этот вопрос и включил полный рабочий пример.

Теперь мой краткий вопрос: почему я не вижу отдельные символы строки «\ u001d», когда я использую RestSharp RestClient и выбираю json в качестве параметров RequestFormat для отправки простого объекта на сервер? Я отправляю вывод на тестовый сервер и вижу только 1d, когда проверяю вывод в двоичном редакторе. Я бы ожидал 5C 75 30 30 31 64 ('\','u','0','0','1','d') И это то, что вы увидите, если просто используете NewtonSoft.Json для сериализации одного и того же простого объекта, содержащего строка «=». Насколько я понимаю, RestSharp сериализует ваш объект как .json (если вы выберете соответствующие параметры) и отправит на сервер для десериализации.

using Newtonsoft.Json;
using RestSharp;
using RestSharp.Serializers;
using System;
using System.Collections.Generic;
using System.IO;

/// <summary>
/// To Test this,
/// 1. I first serialize a string to JSON and then print out the JSON string as a character array to show that
/// '\' 'u' '0' '0' '1' 'd' all show up in the serialized json string.  I also put in original string \\001d (double \) for use later with RestSharp.
/// 2. I expect this json string to show up if I use RestSharp to send an object to a web service because RestSharp serializes everything to JSON
/// if it's so configured. 
///  I make use of http://posttestserver.com/ to 
/// act as "server", so I can just examine the data on that site. I copy the data on the server to a text file and open with binary editor.
/// Notice that there is just a '1d' present between 12 and 34.  
/// You can perhaps duplicate what you get with just serialization by starting with (double \). But why should you need to do that?
/// Notice I try both RestClient's serializer and try to use NewtonSoft Serializer w/ RestClient.
/// 3.  Finally I tried to send the serialized string with a client that had not been set up for JSON.  Unfortunately, on server, it just shows up 
/// as <String />.  Not sure if it's me setting up client incorrectly or what's going on.
/// </summary>
class Program
{
    static void Main(string[] args)
    {
        Tank tank1 = new Tank();
        string string1 = "12\u001d34" + "  " + "56\\u001d78" ; // you don't need double \\ with just serialization, but perhaps you need them with RestClient?
        tank1.Description = string1;// new string(array1);          
        tank1.Id = 1;

        // Show that we can serialize and each character of \u001d shows up as 
        JsonSerializerSettings settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
        string conversion1 = JsonConvert.SerializeObject(tank1, Formatting.Indented, settings);
        Console.WriteLine("JSON serialized string (showing hidden characters");

        foreach(char dummyChar in conversion1.ToCharArray())
        {
            Console.Write(dummyChar + "  ");
            //Console.Write(conversion1.ToCharArray()[i] + "  ");
        }            Console.WriteLine();

        // Demonstrate that straight RestClient doesn't work.
        RestClient client1 = new RestClient("http://posttestserver.com/"); // a website that let's you push data at it.

        var request = new RestRequest(Method.POST); // method is Method.POST
        request.AddHeader("Content-Type", "application/json");
        request.AddHeader("X-ACME-API-VERSION", "1");
        request.Resource = "post.php?dir=David";  // Put in unique name that you can easily find at http://posttestserver.com/data/2016/
        request.RequestFormat = DataFormat.Json;

        request.AddBody(tank1);

        var response = client1.Execute(request); // after this line, go examine http://posttestserver.com/data/2016/ and find your post
        // copy text to text file and open with binary editor.  I claim you will see a 1d but not / (47) or u (117) or 0 (48) i.e. just 1d but not \u001d

        // now try RequestClient w/ json serializer.... 
        request.JsonSerializer = new JsonSerializerNewtonSoft();

        response = client1.Execute(request);

        // Finally, try just sending the json serialized stuff with a RestClient that has NOT been set to json stufff
        RestClient client3 = new RestClient("http://posttestserver.com/"); // a website that let's you push data at it.
        request.Resource = "post.php?dir=David";  // Put in unique name that you can find at http://posttestserver.com/data/2016/

        var request3 = new RestRequest(Method.PUT); // method is Method.PUT // not sure what to put here
        //request3.AddHeader("Content-Type", "application/json"); // not sure what to use here if anything
        request3.AddHeader("X-ACME-API-VERSION", "1");
        request3.Resource = "post.php?dir=David";  // Put in unique name that you can find at http://posttestserver.com/data/2016/
        request3.RequestFormat = DataFormat.Xml; // not sure what to use here

        request3.AddBody(conversion1);

        var response3 = client3.Execute(request3); // hard to evaluate.  Shows up at test server as <String /> 
    }
}

interface ITank
{
    int Id { get; set; }
    string Description { get; set; }
}
public class Tank : ITank
{
    public Tank() { }
    public string Description { get; set; }
    public int Id { get; set; }
}

public class Tanks
{
    public Tanks() { }
    public IEnumerable<Tank> Group { get; set; }
}

public class JsonSerializerNewtonSoft : ISerializer
{
    private readonly Newtonsoft.Json.JsonSerializer _serializer;

    /// <summary>
    /// Default serializer
    /// </summary>
    public JsonSerializerNewtonSoft()
    {
        ContentType = "application/json";

        _serializer = new Newtonsoft.Json.JsonSerializer
        {
            MissingMemberHandling = MissingMemberHandling.Ignore,
            NullValueHandling = NullValueHandling.Include,
            DefaultValueHandling = DefaultValueHandling.Include
        };
    }

    /// <summary>
    /// Default serializer with overload for allowing custom Json.NET settings
    /// </summary>
    public JsonSerializerNewtonSoft(Newtonsoft.Json.JsonSerializer serializer)
    {
        ContentType = "application/json";
        _serializer = serializer;
    }

    /// <summary>
    /// Serialize the object as JSON
    /// </summary>
    /// <param name="obj">Object to serialize</param>
    /// <returns>JSON as String</returns>
    public string Serialize(object obj)
    {
        using (var stringWriter = new StringWriter())
        {
            using (var jsonTextWriter = new JsonTextWriter(stringWriter))
            {
                jsonTextWriter.Formatting = Formatting.Indented;
                jsonTextWriter.QuoteChar = '"';

                _serializer.Serialize(jsonTextWriter, obj);
                var result = stringWriter.ToString();
                return result;
            }
        }
    }

    /// <summary>
    /// Unused for JSON Serialization
    /// </summary>
    public string DateFormat { get; set; }

    /// <summary>
    /// Unused for JSON Serialization
    /// </summary>
    public string RootElement { get; set; }

    /// <summary>
    /// Unused for JSON Serialization
    /// </summary>
    public string Namespace { get; set; }

    /// <summary>
    /// Content type for serialized content
    /// </summary>
    public string ContentType { get; set; }
}

person Dave    schedule 08.11.2016    source источник
comment
Как вы создаете тело вашего запроса? Можете ли вы показать эту часть кода?   -  person Brian Rogers    schedule 08.11.2016
comment
Я добавил код как обновление 2. Спасибо за внимание! Мне также пришло в голову попробовать другой сериализатор. Может НьютонСофт? Возможно, сериализатор RestSharp делает что-то неожиданное. И я действительно не понимаю, как исследовать его сериализованный материал. Это только что сделано и отправлено с вызовом _client.Execute(...)   -  person Dave    schedule 08.11.2016
comment
Этот код, который вы только что опубликовали для создания тела, похоже, не соответствует JSON, который вы разместили ранее. Что мне действительно нужно, так это минимальный, полный и проверяемый пример, который можно использовать для воспроизведения проблемы.   -  person Brian Rogers    schedule 08.11.2016
comment
Спасибо, Брайан. Я понимаю и работаю над этим. Как вы понимаете, мне нужно убрать проприетарные вещи и упростить (минимум).   -  person Dave    schedule 08.11.2016
comment
Привет Брайан. Я разместил полный рабочий пример и значительно упростил вопрос! Я пытался использовать Fiddle и безуспешно. В частности, я не мог понять, как добавить ссылку на System.IO. Как я заявляю в вопросе, я думаю, что теперь все сводится к тому, почему Restsharp не сериализуется в json, как это делает NewtonSoft? Заметьте, я даже пытался использовать сериализатор NewtonSoft!   -  person Dave    schedule 10.11.2016
comment
В вашем примере, похоже, отсутствует какой-то код. Существует незавершенный цикл for в конце цикла Main. Не уверен, что вы там задумали.   -  person Brian Rogers    schedule 10.11.2016
comment
Прости за это. Исправлено форматирование. Проблема заключалась в этой строке //for (int i = 0; i ‹ convert1.ToCharArray().Length; i++), которую мне пришлось заменить оператором foreach. Понятия не имею, что происходит... Я использую ‹pre› и ‹/pre› для форматирования кода   -  person Dave    schedule 10.11.2016
comment
Хорошо, спасибо, я вижу ваше обновление. Обратите внимание, что на самом деле вам не нужно использовать теги <pre> для форматирования кода — все, что имеет отступ в 4 или более пробела в редакторе, будет автоматически отформатировано как код. См. страницу справки Markdown.   -  person Brian Rogers    schedule 10.11.2016
comment
Ах да. И я иногда так делаю. Но я обнаружил, что если я публикую много кода (например, количество здесь), форматирование каким-то образом действительно испортится. Я думаю, что последующие строки имеют отступ в 4 пробела или что-то в этом роде. Конечно, было бы неплохо, если бы был какой-то механизм, говорящий, что все это код, пока я не скажу обратное. Я думал, что ‹pre› сделал это. Я понятия не имею, почему оператор for (int i = 0; i ‹ convert1.ToCharArray().Length; i++) все испортил. Мне также нужно вернуться и выяснить, почему я не могу разместить образец на Fiddle. Это было бы идеально, так как я бы заметил форматирование   -  person Dave    schedule 10.11.2016


Ответы (1)


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

Хорошо, здесь происходит несколько вещей, которые приводят к результатам, которые вы видите. Давайте посмотрим на них по очереди.


Во-первых, вы создаете такую ​​строку:

string string1 = "12\u001d34" + "  " + "56\\u001d78";

Количество обратных косых черт, которые вы используете, безусловно, имеет значение, потому что они имеют то же особое значение в C#, что и в JSON. В частности, нотация \uxxxx в C# означает «вставить в строку символ Unicode с кодом xxxx из 4 шестнадцатеричных цифр (UTF-16)». И наоборот, нотация \\ означает «вставить в строку один символ \». Итак, в первую часть вашей строки вы вставляете единственный символ 0x001d, который является управляющим символом разделителя групп. Во второй части строки вы вставляете шесть символов: \, u, 0, 0, 1 и d. Вы можете сами увидеть разницу с помощью простой тестовой программы, которая выводит символы в виде шестнадцатеричных цифр:

public class Program
{
    public static void Main()
    {
        DumpCharsAsHex("\u001d");   // inserts actual 0x001d character (Group Separator) into string
        DumpCharsAsHex("\\u001d");  // inserts chars '\', 'u', '0', '0', '1', 'd' into string
    }

    private static void DumpCharsAsHex(string s)
    {
        if (s != null)
        {
            for (int i = 0; i < s.Length; i++)
            {
                int c = s[i];
                Console.Write(c.ToString("X") + " ");
            }
        }
        Console.WriteLine();
    }
}

Выход:

1D 
5C 75 30 30 31 64

Скрипт: https://dotnetfiddle.net/8tjIiX


Во-вторых, поведение Json.Net и SimpleJson (внутренний сериализатор RestSharp) определенно различается в отношении управляющих символов, встроенных в строку. Json.Net распознает управляющие символы и преобразует их в соответствующую управляющую последовательность JSON. (Это преобразование выполняется JavaScriptUtils.WriteEscapedJavaScriptString, который, в свою очередь, вызывает StringUtils.ToCharAsUnicode.) RestSharp, с другой стороны, не делает такого преобразования и просто передает невидимый управляющий символ через JSON без изменений. (Вы можете увидеть это в SimpleJson.EscapeToJavascriptString.)< br> Опять же, простая тестовая программа демонстрирует разницу:

public class Program
{
    public static void Main()
    {
        Foo foo = new Foo { Bar = "\u001d" };

        string json = Newtonsoft.Json.JsonConvert.SerializeObject(foo);
        Console.WriteLine(json);
        DumpCharsAsHex(json);

        string json2 = RestSharp.SimpleJson.SerializeObject(foo);
        Console.WriteLine(json2);
        DumpCharsAsHex(json2);
    }

    private static void DumpCharsAsHex(string s)
    {
        if (s != null)
        {
            for (int i = 0; i < s.Length; i++)
            {
                int c = s[i];
                Console.Write(c.ToString("X") + " ");
            }
        }
        Console.WriteLine();
    }
}

public class Foo
{
    public string Bar { get; set; } 
}

Выход:

{"Bar":"\u001d"}
7B 22 42 61 72 22 3A 22 5C 75 30 30 31 64 22 7D 
{"Bar":""}
7B 22 42 61 72 22 3A 22 1D 22 7D

Скрипт: https://dotnetfiddle.net/caxZfq

Как видите, в первом JSON, созданном Json.Net, управляющий символ в исходной строке был преобразован в escape-нотацию JSON, которая по совпадению выглядит точно так же, как исходный код C#. Когда JSON десериализуется на другом конце, он снова преобразуется в управляющий символ.

Во втором JSON, созданном RestSharp, фактически присутствует управляющий символ (1D между 22s), хотя он не виден в выводе JSON. Я должен отметить, что это определенно неправильное поведение в соответствии с разделом 9 спецификация JSON (выделено мной):

Строка представляет собой последовательность кодовых точек Unicode, заключенных в кавычки (U+0022). Все символы могут быть помещены в кавычки, за исключением символов, которые должны быть экранированы: кавычка (U+0022), перевернутая солидус (U+005C) и управляющие символы от U+0000 до U+001F.

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


В-третьих, в вашем коде похоже, что вы пытаетесь использовать Json.Net в качестве замены сериализатора для RestSharp, но, похоже, это не влияет на ваши результаты. Причина в том, что ваши заявления не по порядку.

Вы делаете это:

var request = new RestRequest(Method.POST);
...
request.RequestFormat = DataFormat.Json;
request.AddBody(tank1);
request.JsonSerializer = new JsonSerializerNewtonSoft();
response = client1.Execute(request);

Обратите внимание, что вы вызываете AddBody перед установкой JsonSerializer в запросе. RestRequest.AddBody — это метод, который вызывает сериализатор для получения JSON и добавляет результат в тело запроса; это не выполняется RestClient.Execute. Таким образом, к тому времени, когда вы установите альтернативный сериализатор JSON, будет слишком поздно — вы уже использовали внутренний сериализатор для добавления JSON в тело запроса, а альтернативный сериализатор никогда не вызывается. Измените порядок этих двух операторов, и он должен работать так, как вы хотите.

var request = new RestRequest(Method.POST);
...
request.RequestFormat = DataFormat.Json;
request.JsonSerializer = new JsonSerializerNewtonSoft();
request.AddBody(tank1);
response = client1.Execute(request);

Надеюсь, это имеет смысл.

person Brian Rogers    schedule 10.11.2016
comment
Миллион благодарностей, Брайан! Почему не всем известно, что сериализатор RestSharp ведет себя таким образом (по сравнению с NewtonSoft)? Или я просто пропустил это? И я предполагаю, что NewtonSoft правильный, а RestSharp неправильный, или можно ли аргументировать по-другому? С удовольствием отмечен как ответ! - person Dave; 11.11.2016
comment
Я бы сказал, что эта проблема, вероятно, не является общеизвестной, потому что (а) несколько необычно хотеть отправлять управляющие символы через JSON, и (б) те, кому это необходимо, могут выбрать использование массива байтов с кодировкой base-64 вместо обычный текст, который позволяет избежать проблемы. Кроме того, многие люди, использующие RestSharp, предпочитают заменить сериализатор по умолчанию на Json.Net, потому что он более надежен с точки зрения функций, которые он предлагает в качестве сериализатора. Короче говоря, проблема возникает не так часто. - person Brian Rogers; 11.11.2016
comment
С точки зрения того, какая библиотека имеет правильное поведение в этой ситуации, Json.Net работает правильно, а SimpleJson определенно нет. Согласно разделу 9 спецификации JSON , управляющие символы в строках должны экранироваться, а SimpleJson этого не делает. Я обновил свой ответ цитатой из соответствующего раздела спецификации. - person Brian Rogers; 11.11.2016
comment
Еще раз спасибо, Брайан. Выбор использовать простой текст был сделан до того, как я начал. Тем не менее, не исключено, что в будущем мы можем переключиться на массив байтов в кодировке base-64. У вас есть пример или учебник, на который вы могли бы мне указать? Я думаю, что все это пришло в голову, потому что раньше не отправлялись непечатаемые символы ascii. Спасибо! - person Dave; 14.11.2016
comment
Я не говорю, что вы обязательно должны переключиться на base-64, теперь, когда у вас все работает; просто некоторые люди могут решить обращаться с этим таким образом. Он особенно хорошо подходит для отправки двоичных данных через текстовую среду. Вот пример использования Json.Net для отправки массива байтов через JSON. Json.Net автоматически обрабатывает преобразование в строку с кодировкой base-64 и обратно. Я не уверен, что в сериализатор JSON Ruby встроена обработка base-64, но у него есть модуль Base64, поэтому преобразование можно выполнить как шаг после десериализации. - person Brian Rogers; 15.11.2016