简体   繁体   中英

Why does deserialization of nested dictionary throws an exception in C# but works in F#?

I serialize a F# record type to Json with Newtonsoft.Json.FSharp.

type Prices =  Dictionary<string, decimal>

type PriceList = {Id:string; Name:string; Date:DateTime; CurrencySymbol:string; Status:Status; Prices:Prices}

let private converters : JsonConverter array =
  [| BigIntConverter();
    GuidConverter();
    ListConverter();
    OptionConverter();
    MapConverter();
    TupleArrayConverter();
    UnionConverter();
    UriConverter();
    CultureInfoConverter() |]

let private settings = JsonSerializerSettings (
                        Converters = converters,
                        Formatting = Formatting.Indented,
                        NullValueHandling = NullValueHandling.Ignore)

let serialize obj = JsonConvert.SerializeObject(obj, settings)

The result in JSON is this:

{
  "Id": "PriceList20140201",
  "Name": "PriceList",
  "Date": "2014-02-01T00:00:00+01:00",
  "CurrencySymbol": "€",
  "Status": 0,
  "Prices": {
    "ItemCodeA": 512.4,
    "ItemCodeB": 471.0
  }
}

And if I deserialize this it works fine

JsonConvert.DeserializeObject<PriceList>(text)

Result in F# Interactive:

val y : PriceList =
{Id = "PriceList20140201";
Name = "PriceList";
Date = 01.04.2014 00:00:00;
CurrencySymbol = "€";
Status = Sale;
Prices =
 dict
   [("ItemCodeA", 512.4M); ("ItemCodeB", 471.0M);...];}

Now I want to deserialize it in C# using Newtonsoft.Json

public class PriceList
{
    public string Id { get; set; }
    public string Name { get; set; }
    public DateTime Date { get; set; }
    public string CurrencySymbol { get; set; }
    public Status Status { get; set; }
    public Prices Prices { get; set; }
}

public class Prices : Dictionary<string, decimal>
{
}
...
JsonConvert.DeserializeObject<PriceList>(json)

This results in an JsonSerializationException:

Cannot deserialize the current JSON array (eg [1,2,3]) into type 'Halep.Logic.OfferManagement.Contracts.DataClasses.Pricing.Prices' because the type requires a JSON object (eg {"name":"value"}) to deserialize correctly. To fix this error either change the JSON to a JSON object (eg {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (eg ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array. Path 'Prices', line 7, position 13.

I read that I need a CustomConverter for nested dictionaries. But as far as I know it should be possible since version 6.0.? without a custom converter. I'm currently using 8.0.3. Why does it work in F# but not in C#? Is the C# object structure wrong?

I tried to replace PriceList directly with Dictionary but same result.

Update The last sentence means I tried this:

public class PriceList
{
    public string Id { get; set; }
    public string Name { get; set; }
    public DateTime Date { get; set; }
    public string CurrencySymbol { get; set; }
    public Status Status { get; set; }
    public Dictionary<string, decimal> Prices { get; set; }
}

And got nearly the same Exception:

Cannot deserialize the current JSON array (eg [1,2,3]) into type 'System.Collections.Generic.Dictionary`2[System.String,System.Decimal]' because the type requires a JSON object (eg {"name":"value"}) to deserialize correctly. To fix this error either change the JSON to a JSON object (eg {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (eg ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array. Path 'Prices', line 7, position 13.

Update 2: Solution This code works. The last comment of @JustinNiessner led to the solution. The problem was that the input was in fact this which contains [ ] instead of { }:

{
  "Id": "PriceList20140201",
  "Name": "PriceList",
  "Date": "2014-02-01T00:00:00+01:00",
  "CurrencySymbol": "€",
  "Status": 0,
  "Prices": [
    "ItemCodeA": 512.4,
    "ItemCodeB": 471.0
  ]

}

type Prices = Dictionary<string, decimal> is a type abbreviation in F# rather than creating a new class that inherits from Dictionary<string, decimal> as the C# code does:

F# Type Abbreviations

That is why you're seeing deserialization differences between the two.

There are two ways that you can fix this in C#. The first would be to create a similar type alias with the using directive:

using Prices = System.Collections.Generic.Dictionary<string, object>;

The other would be to simply replace the type with Decimal<string, decimal> in the PriceList class definition:

public class PriceList
{
    public string Id { get; set; }
    public string Name { get; set; }
    public DateTime Date { get; set; }
    public string CurrencySymbol { get; set; }
    public Status Status { get; set; }
    public Dictionary<string, decimal> Prices { get; set; }
}

And then the C# Desserialization will work the same way as the F#.

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