我有一个简单的JSON键/值列表,通过POST发送回ASP.NET。例子:

{ "key1": "value1", "key2": "value2"}

我没有试图反序列化为强类型的.NET对象

我只需要一个普通的旧字典(Of String,String),或者一些等价的东西(哈希表,字典(OfString,Object),老式的StringDictionary——见鬼,一个二维字符串数组对我来说很有用。

我可以使用ASP.NET 3.5中可用的任何东西,也可以使用流行的Json.NET(我已经在使用它对客户端进行序列化)。

显然,这两个JSON库都没有这种开箱即用的能力——它们完全专注于通过强契约实现基于反射的反序列化。

有什么想法吗?

限制:

我不想实现我自己的JSON解析器尚无法使用ASP.NET 4.0希望远离旧的、不推荐使用的JSON ASP.NET类


当前回答

我的方法直接反序列化为IDictionary,中间没有JObject或ExpandObject。代码使用转换器,该转换器基本上是从JSON.NET源代码中的ExpandoObjectConverter类复制的,但使用IDictionary而不是ExpandoObject。

用法:

var settings = new JsonSerializerSettings()
{
    Converters = { new DictionaryConverter() },
};
var result = JsonConvert.DeserializeObject<IDictionary<string, object>>(json, settings);

代码:

// based on ExpandoObjectConverter, but using arrays instead of IList, to behave similar to System.Web.Script.Serialization.JavaScriptSerializer
public class DictionaryConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return ReadValue(reader);
    }

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(IDictionary<string, object>));
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    private object ReadValue(JsonReader reader)
    {
        while (reader.TokenType == JsonToken.Comment)
        {
            if (!reader.Read())
                throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");
        }

        switch (reader.TokenType)
        {
            case JsonToken.StartObject:
                return ReadObject(reader);
            case JsonToken.StartArray:
                return ReadList(reader);
            default:
                if (IsPrimitiveToken(reader.TokenType))
                    return reader.Value;

                throw JsonSerializationExceptionCreate(reader, string.Format(CultureInfo.InvariantCulture, "Unexpected token when converting IDictionary<string, object>: {0}", reader.TokenType));
        }
    }

    private object ReadList(JsonReader reader)
    {
        List<object> list = new List<object>();

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                default:
                    object v = ReadValue(reader);

                    list.Add(v);
                    break;
                case JsonToken.EndArray:
                    return list;
            }
        }

        throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");
    }

    private object ReadObject(JsonReader reader)
    {
        IDictionary<string, object> dictionary = new Dictionary<string, object>();
        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.PropertyName:
                    string propertyName = reader.Value.ToString();

                    if (!reader.Read())
                        throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");

                    object v = ReadValue(reader);

                    dictionary[propertyName] = v;
                    break;
                case JsonToken.Comment:
                    break;
                case JsonToken.EndObject:
                    return dictionary;
            }
        }

        throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");
    }

    //based on internal Newtonsoft.Json.JsonReader.IsPrimitiveToken
    internal static bool IsPrimitiveToken(JsonToken token)
    {
        switch (token)
        {
            case JsonToken.Integer:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Boolean:
            case JsonToken.Undefined:
            case JsonToken.Null:
            case JsonToken.Date:
            case JsonToken.Bytes:
                return true;
            default:
                return false;
        }
    }

    // based on internal Newtonsoft.Json.JsonSerializationException.Create
    private static JsonSerializationException JsonSerializationExceptionCreate(JsonReader reader, string message, Exception ex = null)
    {
        return JsonSerializationExceptionCreate(reader as IJsonLineInfo, reader.Path, message, ex);
    }

    // based on internal Newtonsoft.Json.JsonSerializationException.Create
    private static JsonSerializationException JsonSerializationExceptionCreate(IJsonLineInfo lineInfo, string path, string message, Exception ex)
    {
        message = JsonPositionFormatMessage(lineInfo, path, message);

        return new JsonSerializationException(message, ex);
    }

    // based on internal Newtonsoft.Json.JsonPosition.FormatMessage
    internal static string JsonPositionFormatMessage(IJsonLineInfo lineInfo, string path, string message)
    {
        if (!message.EndsWith(Environment.NewLine))
        {
            message = message.Trim();

            if (!message.EndsWith(".", StringComparison.Ordinal))
                message += ".";

            message += " ";
        }

        message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);

        if (lineInfo != null && lineInfo.HasLineInfo())
            message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);

        message += ".";

        return message;
    }
}

其他回答

令人恼火的是,如果您想使用默认的模型绑定器,看起来您必须使用数字索引值,如表单POST。

请参阅本文的以下摘录http://msdn.microsoft.com/en-us/magazine/hh781022.aspx:

虽然这有点违反直觉,但JSON请求也有相同之处要求他们也必须遵守表单后命名语法。例如,上一个UnitPrice的JSON负载收集此数据的纯JSON数组语法为表示为:[ {“代码”:“USD”,“金额”:100.00},{“代码”:“EUR”,“金额”:73.64}]但是,默认值提供程序和模型绑定程序需要要表示为JSON表单帖子的数据:{“UnitPrice[0].Code”:“USD”,“单价[0]。金额”:100.00,“单价[1].代码”:“EUR”,“单价[1].金额”:73.64}复杂的对象收集场景可能是最复杂的场景之一开发人员遇到的问题广泛的场景,因为语法并不一定对所有开发人员都显而易见。然而,一旦你学习发布复杂集合的相对简单的语法,这些场景变得更容易处理。

编辑:这是可行的,但使用Json.NET的公认答案要简单得多。留下这个,以防有人只需要BCL代码。

现成的.NET框架不支持它。一个明显的疏忽——并不是每个人都需要反序列化为具有命名财产的对象。所以,我最终推出了自己的:

VB.NET:

<Serializable()> Public Class StringStringDictionary
    Implements ISerializable
    Public dict As System.Collections.Generic.Dictionary(Of String, String)
    Public Sub New()
        dict = New System.Collections.Generic.Dictionary(Of String, String)
    End Sub
    Protected Sub New(info As SerializationInfo, _
          context As StreamingContext)
        dict = New System.Collections.Generic.Dictionary(Of String, String)
        For Each entry As SerializationEntry In info
            dict.Add(entry.Name, DirectCast(entry.Value, String))
        Next
    End Sub
    Public Sub GetObjectData(info As SerializationInfo, context As StreamingContext) Implements ISerializable.GetObjectData
        For Each key As String in dict.Keys
            info.AddValue(key, dict.Item(key))
        Next
    End Sub
End Class

与C#相同:

public class StringStringDictionary : ISerializable
{
    public System.Collections.Generic.Dictionary<string, string> dict;
    public StringStringDictionary()
    {
        dict = new System.Collections.Generic.Dictionary<string, string>();
    }
    protected StringStringDictionary(SerializationInfo info, StreamingContext context)
    {
        dict = new System.Collections.Generic.Dictionary<string, string>();
        foreach (SerializationEntry entry in info)
            dict.Add(entry.Name, (string)entry.Value);
    }
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        foreach (string key in dict.Keys)
            info.AddValue(key, dict[key]);
    }
}

调用方式:

string MyJsonString = "{ \"key1\": \"value1\", \"key2\": \"value2\"}";
System.Runtime.Serialization.Json.DataContractJsonSerializer dcjs = new
  System.Runtime.Serialization.Json.DataContractJsonSerializer(
    typeof(StringStringDictionary));
System.IO.MemoryStream ms = new
  System.IO.MemoryStream(Encoding.UTF8.GetBytes(MyJsonString));
StringStringDictionary myfields = (StringStringDictionary)dcjs.ReadObject(ms);
Response.Write("Value of key2: " + myfields.dict["key2"]);

很抱歉,C#和VB.NET混合使用…

马克·伦德尔(Mark Rendle)将此作为评论发布,我想将其作为答案发布,因为这是迄今为止唯一一个能够从Google reCaptcha响应中返回成功和错误代码json结果的解决方案。

string jsonReponseString= wClient.DownloadString(requestUrl);    
IDictionary<string, object> dict = new JavaScriptSerializer().DeserializeObject(jsonReponseString) as IDictionary<string, object>;

再次感谢,马克!

我建议使用.NET4.5中的System.Runtime.Serialization.Json。

[DataContract]
public class Foo
{
   [DataMember(Name = "data")]
   public Dictionary<string,string> Data { get; set; }
}

然后这样使用:

var serializer = new DataContractJsonSerializer(typeof(List<Foo>));
var jsonParams = @"{""data"": [{""Key"":""foo"",""Value"":""bar""}] }";
var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonParams));

var obj = serializer.ReadObject(stream);
Console.WriteLine(obj);

这是我使用System.Text.Json的解决方案。您可以获得嵌套对象的Json字符串,然后可以将其转换为所需类型。

public static Dictionary<string,string> JsonToDictionary(this string json)
        {
            var objectValues = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
            var stringValues = objectValues.Select(o => new KeyValuePair<string, string>(o.Key, o.Value?.ToString()));
            return stringValues.ToDictionary(pair => pair.Key, pair => pair.Value);
        }

以下是从嵌套对象获取值的用法示例:

 var result= json.JsonToDictionary()["outerField"]
                .JsonToDictionary()["innerField"];

请注意,此解决方案不包括以数组形式开始的json对象,如[12,13]。这些对象可以在开头作为数组读取,然后可以在每个项上应用扩展方法,以防这些项是具有自己财产的复杂对象。