1

I'm sure there's a converter I need to implement to handle NameValueCollection objects using System.Text.Json (note: I've seen the answers related to legacy JavaScriptSerializer).

This:

NameValueCollection nvc = new NameValueCollection();
nvc.Add("foo", "bar");
nvc.Add("foo", "bar2");
nvc.Add("bar", "baz");

string json = SerializationHelper.SerializeJson(nvc, true);
Console.WriteLine(json);

Produces:

[
  "foo",
  "bar"
]

Instead of what I would want:

{
  "foo": "bar",
  "foo": "bar2",
  "bar": "baz"
}

Or even:

{
  "foo": "bar,bar2",
  "bar": "baz"
}

Serialization method looks like this:

public static string SerializeJson(object obj, bool pretty)
{
    if (obj == null) return null;
    string json;

    JsonSerializerOptions options = new JsonSerializerOptions();
    options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    options.Converters.Add(new ExceptionConverter<Exception>());

    if (pretty)
    {
        options.WriteIndented = true;
        json = JsonSerializer.Serialize(obj, options);
    }
    else
    {
        options.WriteIndented = false;
        json = JsonSerializer.Serialize(obj, options);
    }

    return json;
}

Any thoughts on how to get the desired behavior or a converter that would handle this case?

joelc
  • 2,687
  • 5
  • 40
  • 60
  • 1
    It doesn't "just work", it's explicitly handled. NameValueCollection is an ancient class that predates generics and *doesn't* implement `IDictionary` and its elements aren't KeyValuePair items. It's very specialized (it's even in the namespace .Specialized) so it's rather rare to try and serialize it as anything other than URL query parameters. The `or even` string is a bad idea - JSON arrays have very specific syntax and that isn't an array. The System.Text.Json serializers are meant for API serialization where you don't find `NameValueCollection`s or query parameters – Panagiotis Kanavos Mar 13 '23 at 19:50
  • If you really want to use NameValueCollection you can create a custom type converter and either register it globally or apply it with an attribute to any NameValueCollection you want – Panagiotis Kanavos Mar 13 '23 at 19:52
  • Great to know, so what is the best way to get it to serialize like a dictionary using ```System.Text.Json```? – joelc Mar 13 '23 at 19:52
  • So why do you want to use something that's never found in JSON, as JSON? As for how, you'll have to write a custom type converter. That's how JSON.NET handles NameValueCollection too – Panagiotis Kanavos Mar 13 '23 at 19:53
  • Does this answer your question? [how to convert NameValueCollection to JSON string?](https://stackoverflow.com/questions/7003740/how-to-convert-namevaluecollection-to-json-string) – quaabaam Mar 13 '23 at 19:53
  • @quaabaam this refers to a completely unrelated class, the obsolete JavascriptSerializer which was used before JSON.NET. None of the answers apply to this question – Panagiotis Kanavos Mar 13 '23 at 19:57
  • @PanagiotisKanavos I thought the link's last answer from 2018 had relevance, maybe I'm wrong. – quaabaam Mar 13 '23 at 19:58
  • @Panagiotis the intent is to have it represented in JSON as a dictionary. I agree and understand it's not a native JSON type. – joelc Mar 13 '23 at 19:58
  • @Panagiotis do you have an example that you could share of a custom type converter for ```NameValueCollection``` that works? – joelc Mar 13 '23 at 20:01
  • Works like what? Because even JSON.NET produces just the keys. `JsonConvert.SerializeObject(nvc)` produces `["foo","bar"]`. Based on that the question should be closed as unreproducible - you already get the same results as JSON.NET. If the question was how to serialize NVCs as dictionaries in either JSON.NET or System.Text.Json, the answers you'll find in SO in both cases are to convert the NVC to a dictionary first – Panagiotis Kanavos Mar 13 '23 at 20:05
  • You're right, I'll amend the question accordingly. – joelc Mar 13 '23 at 20:07
  • Serializing to a JSON object with duplicate property names would be a bad idea -- the JSON RFC explicitly calls this out as producing unpredictable results, as different JSON parsers have different ideas on how to handle this. Merging the values would be done by almost none of them; instead you're likely to get only the first or only the last of the values in the serialized output. If you really need the ability to serialize multiple values for the same name, serializing the values to an array is a better idea. – Jeroen Mostert Mar 13 '23 at 20:13
  • @Jeroen I was hoping for a converter that would combine the values into a comma-separated list, which is commonly used (e.g. in HTTP queries, headers, etc) to preserve single/unique key semantics – joelc Mar 13 '23 at 20:16
  • 2
    You're asking for custom behavior. There are SO questions asking for what you want and the answer is you have to write this yourself. If the code is called infrequently you *can* convert an NVC to a dictionary. You can get the keys with `.AllKeys` and then use `nvc.GetValues(key)` to get the values for each key as a `string[]`. If you use `nvc[key]` you'll get them as single string with comma-separated values. Eg, `nvc.AllKeys.ToDictionary(k=>k,k=>nvc.GetValues(k))` will produce a `Dictionary` – Panagiotis Kanavos Mar 13 '23 at 20:24
  • For clarity, I'm asking for help. Yes, it's custom behavior. I'm not savvy in writing converters for System.Text.Json and that's why I'm asking the community (yes, I could just go learn it and try to roll my own, I get that). If you don't want to provide an answer or a solution that produces the desired result, I understand, and that's fine. – joelc Mar 13 '23 at 20:32

1 Answers1

1

You can write simple custom converter which will transform the NameValueCollection into dictionary and serialize it:

class MyConv : System.Text.Json.Serialization.JsonConverter<NameValueCollection>
{
    public override NameValueCollection? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();

    public override void Write(Utf8JsonWriter writer, NameValueCollection value, JsonSerializerOptions options)
    {
        var val = value.Keys.Cast<string>()
            .ToDictionary(k => k, k => string.Join(", ", value.GetValues(k)));
        System.Text.Json.JsonSerializer.Serialize(writer, val);
    }
}

And add it to options:

options.Converters.Add(new MyConv());
Guru Stron
  • 102,774
  • 10
  • 95
  • 132