如何使用 C# 中的 RestSharp RestClient 发送组分隔符(不可打印的 ascii 字符)
How to send group separator (non printable ascii characters) with RestSharp RestClient from C#
已更新
我已经完全修改了这个问题并包含了一个完整的工作示例。
我现在的简洁问题是:
当我使用 RestSharp RestClient 并选择 json 作为向服务器发送简单对象的 RequestFormat 选项时,为什么我看不到字符串“\u001d”的单个字符?我将输出发送到测试服务器,当我使用二进制编辑器检查输出时只看到 1d。
我希望 5C 75 30 30 31 64
('\','u','0','0','1','d') 如果你只使用 NewtonSoft.Json 来序列化包含字符串“\u001d”的同一个简单对象。我的理解是 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; }
}
首先,感谢您发布可用于重现问题的Minimal, Complete, and Verifiable example;这使得提供帮助变得更加容易。
好的,这里发生了一些导致您看到的结果的事情。让我们一个一个地看。
首先,您要创建这样的字符串:
string string1 = "12\u001d34" + " " + "56\u001d78";
您使用的反斜杠的数量肯定 很重要,因为它在 C# 中与在 JSON 中具有相同的特殊含义。具体来说,C# 中的符号 \uxxxx
表示 "insert the Unicode character with the 4-hexadecimal-digit (UTF-16) character code xxxx into the string"。相反,符号 \
表示 "insert a single \
character into the string"。因此,在字符串的第一部分,您要插入单个字符 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
Fiddle: https://dotnetfiddle.net/8tjIiX
其次,Json.Net 和 SimpleJson(RestSharp 内部序列化程序)在字符串中嵌入的控制字符方面肯定存在不同的行为。 Json.Net 识别控制字符并将它们转换为正确的 JSON 转义序列。 (此转换由 JavaScriptUtils.WriteEscapedJavaScriptString
method, which in turn calls StringUtils.ToCharAsUnicode
.) RestSharp, on the other hand, does no such conversion, and just passes the invisible control character through the JSON unchanged. (You can see this in SimpleJson.EscapeToJavascriptString
完成。)
同样,一个简单的测试程序证明了区别:
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
Fiddle: https://dotnetfiddle.net/caxZfq
可以看到,在Json.Net产生的第一个JSON中,原字符串中的控制字符被转换为JSON字符转义符号,巧合的是看起来只是就像原始的 C# 代码一样。当 JSON 在另一端被反序列化时,这将被转换回控制字符。
在由 RestSharp 生成的第二个 JSON 中,控制字符实际上存在(22
之间的 1D
),即使它在 JSON 输出。我应该注意,根据 JSON spec(强调我的)第 9 节,这绝对是不正确的行为:
A string is a sequence of Unicode code points wrapped with quotation marks (U+0022). All characters may be
placed within the quotation marks except for the characters that must be escaped: quotation mark (U+0022),
reverse solidus (U+005C), and the control characters U+0000 to 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);
请注意,在根据请求设置 JsonSerializer 之前,AddBody
调用了 。 RestRequest.AddBody
is the method that calls the serializer to get the JSON and adds the result to the body of the request; this is not done by 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);
希望这是有道理的。
已更新
我已经完全修改了这个问题并包含了一个完整的工作示例。
我现在的简洁问题是:
当我使用 RestSharp RestClient 并选择 json 作为向服务器发送简单对象的 RequestFormat 选项时,为什么我看不到字符串“\u001d”的单个字符?我将输出发送到测试服务器,当我使用二进制编辑器检查输出时只看到 1d。
我希望 5C 75 30 30 31 64
('\','u','0','0','1','d') 如果你只使用 NewtonSoft.Json 来序列化包含字符串“\u001d”的同一个简单对象。我的理解是 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; }
}
首先,感谢您发布可用于重现问题的Minimal, Complete, and Verifiable example;这使得提供帮助变得更加容易。
好的,这里发生了一些导致您看到的结果的事情。让我们一个一个地看。
首先,您要创建这样的字符串:
string string1 = "12\u001d34" + " " + "56\u001d78";
您使用的反斜杠的数量肯定 很重要,因为它在 C# 中与在 JSON 中具有相同的特殊含义。具体来说,C# 中的符号 \uxxxx
表示 "insert the Unicode character with the 4-hexadecimal-digit (UTF-16) character code xxxx into the string"。相反,符号 \
表示 "insert a single \
character into the string"。因此,在字符串的第一部分,您要插入单个字符 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
Fiddle: https://dotnetfiddle.net/8tjIiX
其次,Json.Net 和 SimpleJson(RestSharp 内部序列化程序)在字符串中嵌入的控制字符方面肯定存在不同的行为。 Json.Net 识别控制字符并将它们转换为正确的 JSON 转义序列。 (此转换由 JavaScriptUtils.WriteEscapedJavaScriptString
method, which in turn calls StringUtils.ToCharAsUnicode
.) RestSharp, on the other hand, does no such conversion, and just passes the invisible control character through the JSON unchanged. (You can see this in SimpleJson.EscapeToJavascriptString
完成。)
同样,一个简单的测试程序证明了区别:
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
Fiddle: https://dotnetfiddle.net/caxZfq
可以看到,在Json.Net产生的第一个JSON中,原字符串中的控制字符被转换为JSON字符转义符号,巧合的是看起来只是就像原始的 C# 代码一样。当 JSON 在另一端被反序列化时,这将被转换回控制字符。
在由 RestSharp 生成的第二个 JSON 中,控制字符实际上存在(22
之间的 1D
),即使它在 JSON 输出。我应该注意,根据 JSON spec(强调我的)第 9 节,这绝对是不正确的行为:
A string is a sequence of Unicode code points wrapped with quotation marks (U+0022). All characters may be placed within the quotation marks except for the characters that must be escaped: quotation mark (U+0022), reverse solidus (U+005C), and the control characters U+0000 to 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);
请注意,在根据请求设置 JsonSerializer 之前, 。 希望这是有道理的。AddBody
调用了 RestRequest.AddBody
is the method that calls the serializer to get the JSON and adds the result to the body of the request; this is not done by 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);