简体   繁体   中英

JSON C# deserialize Array With 1 item to Object

I have a JSON structure like this, I cannot Change this structure, because it comes from a webservice which i cannot access.

[
  {
    "query": "BusinessFunction",
    "result": [
      {
        "id": [
          "10247"
        ],
        "lastModificationUser": [
          "maxmustermann"
        ],
        "description": [],
        "name": [
          "Engineering Data Mgmt"
        ],
       ...
      },
      {
        "id": [
          "10455"
        ],
        ...
      }
  },
  ...
]

As you can see, every Attribut got an array with one exactly parameter. Is there a simple way to get them into a construct like my class BusinessFunctionData without extract every parameter manually?

class BusinessFunctionData
{
    [JsonProperty(PropertyName = "id")]
    public string id { get; set; }
    [JsonProperty(PropertyName = "lastModificationUser")]
    public string lastModificationUser { get; set; }
    [JsonProperty(PropertyName = "description")]
    public string description { get; set; }
}

I already found Json.net Documentation . I could use this, to extract everyone. But I have over 200 Parameters per class, so i'm not sure about performance and usabillity.

Maybe someone got an idea thats easier and faster.

I try to get a solution where i can use something simular than this:

    public IList<BusinessFunctionData> deseralize(string jsonstring)
    {
        var data = JArray.Parse(jsonstring);
        IList<BusinessFunctionData> outputlist = new List<BusinessFunctionData>();
        var JsonProgramData = data[0]["result"].Children().ToList();
        foreach (var prog in JsonProgramData)
        {

            BusinessFunctionData programm = JsonConvert.DeserializeObject<BusinessFunctionData>(prog.ToString());
            outputlist.Add(programm);
        }
        return outputlist;
    }  

I'm hopeing someone can answer my question about performance. When i download the json file, it's over 100mb big, and it should'nt take too much time to get it in, i need to analyze it in addition.

When dealing with large JSON objects, it is important not to load the entire JSON stream into an intermediate representation before final deserialization. Thus:

  1. Do not download the JSON as a string. From Performance Tips :

    To minimize memory usage and the number of objects allocated, Json.NET supports serializing and deserializing directly to a stream. Reading or writing JSON a piece at a time, instead of having the entire JSON string loaded into memory, is especially important when working with JSON documents greater than 85kb in size to avoid the JSON string ending up in the large object heap.

    Instead, Newtonsoft recommends to deserialize directly from the response stream, eg:

     HttpClient client = new HttpClient(); using (Stream s = client.GetStreamAsync("http://www.test.com/large.json").Result) using (StreamReader sr = new StreamReader(s)) using (JsonReader reader = new JsonTextReader(sr)) { JsonSerializer serializer = new JsonSerializer(); // read the json from a stream // json size doesn't matter because only a small piece is read at a time from the HTTP request RootObject root = serializer.Deserialize<RootObject>(reader); } 
  2. Do not load your entire JSON into a JArray simply in order to deserialize the "result" value. Instead stream through the JSON with a JsonTextReader until you find a property named "result" and then deserialize its value, as is shown in JSON.NET deserialize a specific property .

  3. To automatically map all non-collection-valued object properties from and to single-entry arrays, you can create a custom IContractResolver that applies an appropriate custom JsonConverter to properties of the appropriate type.

Putting all this together, you need the following extension methods and contract resolver:

public static class JsonExtensions
{
    public static IEnumerable<T> DeserializeNamedProperties<T>(Stream stream, string propertyName, JsonSerializerSettings settings = null, int? depth = null)
    {
        using (var textReader = new StreamReader(stream))
            foreach (var value in DeserializeNamedProperties<T>(textReader, propertyName, settings, depth))
                yield return value;
    }

    public static IEnumerable<T> DeserializeNamedProperties<T>(TextReader textReader, string propertyName, JsonSerializerSettings settings = null, int? depth = null)
    {
        var serializer = JsonSerializer.CreateDefault(settings);
        using (var jsonReader = new JsonTextReader(textReader))
        {
            while (jsonReader.Read())
            {
                if (jsonReader.TokenType == JsonToken.PropertyName
                    && (string)jsonReader.Value == propertyName
                    && depth == null || depth == jsonReader.Depth)
                {
                    jsonReader.Read();

                    yield return serializer.Deserialize<T>(jsonReader);
                }
            }
        }
    }
}

public class ArrayToSingleContractResolver : DefaultContractResolver
{
    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
    static ArrayToSingleContractResolver instance;

    static ArrayToSingleContractResolver() { instance = new ArrayToSingleContractResolver(); }

    public static ArrayToSingleContractResolver Instance { get { return instance; } }

    readonly SimplePropertyArrayToSingleConverter simpleConverter = new SimplePropertyArrayToSingleConverter();

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var jsonProperty = base.CreateProperty(member, memberSerialization);
        if (jsonProperty.Converter == null && jsonProperty.MemberConverter == null)
        {
            if (jsonProperty.PropertyType.IsPrimitive 
                || jsonProperty.PropertyType == typeof(string))
            {
                jsonProperty.Converter = jsonProperty.MemberConverter = simpleConverter;
            }
            else if (jsonProperty.PropertyType != typeof(object)
                && !typeof(IEnumerable).IsAssignableFrom(jsonProperty.PropertyType)
                && !typeof(JToken).IsAssignableFrom(jsonProperty.PropertyType))
            {
                jsonProperty.Converter = jsonProperty.MemberConverter = new ObjectPropertyArrayToSingleConverter(this, jsonProperty.PropertyType);
            }
        }

        return jsonProperty;
    }
}

public static class JsonContractExtensions
{
    public static bool? IsArrayContract(this JsonContract contract)
    {
        if (contract == null)
            throw new ArgumentNullException();
        if (contract is JsonArrayContract)
            return true;
        else if (contract is JsonLinqContract)
            return null; // Could be an object or an array.
        else
            return false;
    }
}

class SimplePropertyArrayToSingleConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        while (reader.TokenType == JsonToken.Comment)
            reader.Read();
        if (reader.TokenType == JsonToken.Null)
            return null;
        var contract = serializer.ContractResolver.ResolveContract(objectType);
        bool hasValue = false;
        if (reader.TokenType == JsonToken.StartArray)
        {
            while (reader.Read())
            {
                switch (reader.TokenType)
                {
                    case JsonToken.Comment:
                        break;
                    case JsonToken.EndArray:
                        return UndefaultValue(objectType, existingValue, contract);
                    default:
                        if (hasValue)
                            throw new JsonSerializationException("Too many values at path: " + reader.Path);
                        existingValue = ReadItem(reader, objectType, existingValue, serializer, contract);
                        hasValue = true;
                        break;
                }
            }
            // Should not come here.
            throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
        }
        else
        {
            existingValue = ReadItem(reader, objectType, existingValue, serializer, contract);
            return UndefaultValue(objectType, existingValue, contract);
        }
    }

    private static object UndefaultValue(Type objectType, object existingValue, JsonContract contract)
    {
        if (existingValue == null && objectType.IsValueType && Nullable.GetUnderlyingType(objectType) == null)
            existingValue = contract.DefaultCreator();
        return existingValue;
    }

    private static object ReadItem(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer, JsonContract contract)
    {
        if (contract is JsonPrimitiveContract || existingValue == null)
        {
            existingValue = serializer.Deserialize(reader, objectType);
        }
        else
        {
            serializer.Populate(reader, existingValue);
        }
        return existingValue;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteStartArray();
        if (value != null)
            serializer.Serialize(writer, value);
        writer.WriteEndArray();
    }
}

class ObjectPropertyArrayToSingleConverter : SimplePropertyArrayToSingleConverter
{
    readonly Type propertyType;
    readonly IContractResolver resolver;
    int canConvert = -1;

    public ObjectPropertyArrayToSingleConverter(IContractResolver resolver, Type propertyType)
        : base()
    {
        if (propertyType == null || resolver == null)
            throw new ArgumentNullException();
        this.propertyType = propertyType;
        this.resolver = resolver;
    }

    int GetIsEnabled()
    {
        var contract = resolver.ResolveContract(propertyType);
        return contract.IsArrayContract() == false ? 1 : 0;
    }

    bool IsEnabled
    {
        get
        {
            // We need to do this in a lazy fashion since recursive calls to resolve contracts while creating a contract are problematic.
            if (canConvert == -1)
                Interlocked.Exchange(ref canConvert, GetIsEnabled());
            return canConvert == 1;
        }
    }

    public override bool CanRead { get { return IsEnabled; } }

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

Then use it like:

string url = @"..."; // Replace with your actual URL.

IList<BusinessFunctionData> outputlist;

WebRequest request = WebRequest.Create(url);
using (var response = request.GetResponse())
using (var responseStream = response.GetResponseStream())
{
    var settings = new JsonSerializerSettings { ContractResolver = ArrayToSingleContractResolver.Instance, NullValueHandling = NullValueHandling.Ignore };
    outputlist = JsonExtensions.DeserializeNamedProperties<List<BusinessFunctionData>>(responseStream, "result", settings).FirstOrDefault();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM