How to use string interpolation and verbatim string together to create a JSON string literal?
Asked Answered
G

4

20

I'm trying to create a string literal representing an array of JSON objects so I thought of using string interpolation feature as shown in the code below:

    public static void MyMethod(string abc, int pqr)
    {
        string p = $"[{{\"Key\":\"{abc}\",\"Value\": {pqr}  }}]";
    }

Now I thought of using verbatim string so that I don't have to escape double quotes using backslashes. So I came to know through this answer that verbatim string and string interpolation can be used together. So I changed my code as below:

public static void MyMethod(string abc, int pqr)
{
    string p = $@"[{{"Key":"{abc}","Value": {pqr} }}]";
}

But it fails to compile. Can anyone help me if there is anything wrong in my usage or it will not be possible to escape double quotes in such a case using string verbatim feature of C#?

Gusher answered 16/6, 2017 at 6:47 Comment(4)
Verbatim strings require that you escape double quotes by doubling them up. $@"[{{""Key"":""{abc}"",""Value"": {pqr} }}]" will work, but this is hardly more readable. Consider using a JSON serializer to avoid string wetwork.Auditorium
@Gusher even assuming you really really don't want to use a JSON serializer I'd avoid to build string this way, format string quickly becomes absolutely unreadable and error-prone. In this case maybe String.Format() will help and if you write few more functions then you will have a nice short readable code snippet (QuoteJsonString(), ConvertToJsonKeyValuePair() and one line LINQ to concat them...)Thaine
This question should not have been closed, as it is not a duplicate. Formatting a JSON string in C# is tricky and is not covered by the duplicate question.Electric
Here's my raw string with interpolation answer if this question gets reopened. public static string MyMethod(string abc, int pqr) { string p = $$""" [{ "Key":"{{abc}}", "Value": {{pqr}} }] """; return p; }Electric
S
17

The best way is to use JSON serializers as they have in-built handling related to escape characters and other things. See here.

However, if we want to go through this path only to create the JSON string manually, then it can be solved as follows by changing the inner double quotes to single quotes :

public static string MyMethod(string abc, int pqr)
{
   string p = $@"[{{'Key':'{ abc}','Value': {pqr} }}]";
   return p;
}
Shandeigh answered 14/7, 2017 at 6:58 Comment(2)
This is not valid JSON. Single quotes are valid Javascript, but not JSON.Taratarabar
You can use double quotes to escape the quotes, ie @" { ""key"": ""value"" } " However, despite the manual indicating that you can combine $@ for an interpolated verbatim string, VSCode is telling me that doing so is an error. Not sure what the exactly correct syntax is to achieve it.Pouliot
B
1

you can create dictionary and serialize it to json using Json.NET does this.

Dictionary<string, string> values = new Dictionary<string, string>();
values.Add("key1", "value1");
values.Add("key2", "value2");

string json = JsonConvert.SerializeObject(values);
// {
//   "key1": "value1",
//   "key2": "value2"
// }

you can see here more detail : http://www.newtonsoft.com/json/help/html/SerializingCollections.htm

Buckthorn answered 16/6, 2017 at 6:54 Comment(1)
I'm going to leverage this piece of code in a method which gets called from custom action of installshield. Adding a dependency on JSON.NET dll in my C# project will invite adding a dependency on this dll in the installshield project as well. This is an explicit work which is a fair amount of overhead just to fix this one line of code on C# side if at all it can be fixed.Gusher
E
1

I agree with everyone else that building it from strings is a bad idea. I also understand that you don't want to include an extra dependency.

Here's a bit of code I wrote previously to convert a Dictionary to a JSON string. It's pretty basic, only accepts string types, and doesn't escape quote marks in any of the names/values, but that can be done fairly easily.

If you're trying to serialize a large JSON string from basic types, this is the way I'd recommend to do it. It'll help you stay sane.

private static string DictToJson(Dictionary<string, string> Dict)
{
    var json = new StringBuilder();

    foreach (var Key in Dict.Keys)
    {
        if (json.Length != 0)
            json = json.Append(",\n");

        json.AppendFormat("\"{0}\" : \"{1}\"", Key, Dict[Key]);
    }
    return "{" + json.ToString() + "}";
}
Extraversion answered 16/6, 2017 at 7:6 Comment(1)
In my JSON data I can also have int, date values. You are taking every value as string.Gusher
M
1

While you can use string interpolation to manually create well-formed JSON, you should not. Instead, in .NET Core 3.1 and later, use the built-in System.Text.Json to generate JSON:

public static string MyMethod(string abc, int pqr) => 
    System.Text.Json.JsonSerializer.Serialize(new [] { new { Key = abc, Value = pqr } });

Demo .NET Core fiddle here.

In earlier versions use Json.NET or DataContractJsonSerializer or even JavaScriptSerializer as detailed in How do I turn a C# object into a JSON string in .NET?, e.g.:

public static partial class DataContractJsonSerializerHelper
{
    public static string ToJson<T>(this T obj, DataContractJsonSerializerSettings settings = null)
    {
        var serializer = new DataContractJsonSerializer(obj == null ? typeof(T) : obj.GetType(), settings);
        using (var memory = new MemoryStream())
        {
            serializer.WriteObject(memory, obj);
            memory.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(memory))
                return reader.ReadToEnd();
        }
    }
}

public static string MyMethod(string abc, int pqr) => 
    new [] { new Dictionary<string, object>() { { "Key", abc }, { "Value", pqr } } }
        .ToJson(new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true });

Demo fiddle #2 here.

That being said, if you do want to use string interpolation to manually emit JSON, you must correctly handle the following to avoid creating malformed JSON:

  • As noted in comments by Jeroen Mostert, C# verbatim string literals require embedded double quotes to be doubled up like so:

    $@"[{{""Key"":""{abc}"",""Value"": {pqr} }}]";
    

    Keep in mind that it's easy to confuse C# string literal escaping (which is evaluated at compile time) with JSON string escaping (which is required at runtime.) As you write your C# code you will likely need to be typing both.

  • JSON numeric values must be formatted using the invariant culture to ensure that the decimal separator and negative sign used conform to JSON RFC 8259, so you must use either FormattableString or String.Create(CultureInfo.InvariantCulture, DefaultInterpolatedStringHandler):

    String.Create(CultureInfo.InvariantCulture, 
                  $@"[{{""Key"":""{abc}"",""Value"": {pqr} }}]");
    

    See Culture-specific formatting for details.

  • As stated in RFC 8259, certain characters in JSON string literals must be escaped:

    All Unicode characters may be placed within the quotation marks, except for the characters that MUST be escaped: quotation mark, reverse solidus, and the control characters (U+0000 through U+001F).

    You will need to do this yourself to your abc string.

Putting all this together, you must still create a method for serializing a string to JSON such as the following:

public static class JsonExtensions
{
    static Dictionary<char, string> GetMandatoryEscapes()
    {
        // Standard escapes from https://www.json.org/json-en.html
        var fixedEscapes = new KeyValuePair<char, string> []
        {
            new('\\', "\\\\"), // Nust be first.
            new('"',  "\\\""), // This is correct, but System.Text.Json preferrs the longer "\\u0022" for security reasons.
            new('/',  "\\/"),
            new('\b', "\\b"),
            new('\f', "\\f"),
            new('\n', "\\n"),
            new('\r', "\\r"),
            new('\t', "\\t"),
        };
        // Required control escapes from https://www.rfc-editor.org/rfc/rfc8259#section-7
        var controlEscapes = Enumerable.Range(0, 0x1F + 1)
            .Select(i => (char)i)
            .Except(fixedEscapes.Select(p => p.Key))
            .Select(c => new KeyValuePair<char, string>(c, @"\u"+((int)c).ToString("X4")));
        return fixedEscapes.Concat(controlEscapes).ToDictionary(p => p.Key, p => p.Value);
    }
    
    static Dictionary<char, string> Escapes { get; } = GetMandatoryEscapes();
    
    public static string ToJson(this string s) => 
        s.Aggregate(new StringBuilder("\""), (sb, c) => Escapes.TryGetValue(c, out var s) ? sb.Append(s) : sb.Append(c)).Append("\"").ToString();
}

And now you can use string interpolation to make well-formed JSON as follows:

public static string MyMethod(string abc, int pqr) =>
    String.Create(CultureInfo.InvariantCulture, 
                  $@"[{{""Key"":{abc.ToJson()},""Value"":{pqr} }}]");

Demo fiddle #3 here.

But again, just use a proper JSON serializer.

Manvell answered 23/4 at 20:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.