4

I have a JSON String that I'm trying to deserialize to C# in one swoop.

The Children nodes of SalesLines is a string representation. I want objects all the way down when I deserialize. What is the best way to this with JSON.NET?

{
   "value":[
      {
         "documentType":"Quote",
         "SONumber":"S-QUO1001",
         "SalesLines":"[{\"SONumber\":\"S-QUO1001\",\"LineNum\":10000,\"ItemId\":\"1936-S\",\"ItemAttributes\":[{\"AttibuteName\":\"COLOR\",\"AttributeValue\":\"YELLOW\"},{\"AttibuteName\":\"DEPTH\",\"AttributeValue\":\"100\"},{\"AttibuteName\":\"WIDTH\",\"AttributeValue\":\"120\"},{\"AttibuteName\":\"HEIGHT\",\"AttributeValue\":\"115\"},{\"AttibuteName\":\"MATERIAL DESCRIPTION\",\"AttributeValue\":\"COTTON, WOOD LEGS\"},{\"AttibuteName\":\"MODEL YEAR\",\"AttributeValue\":\"1940\"}]}]"
      }
   ]
}
dbc
  • 104,963
  • 20
  • 228
  • 340
aherrick
  • 19,799
  • 33
  • 112
  • 188
  • 1
    If your JSON were well-formed, you could apply `EmbeddedLiteralConverter>` from [How do I convert an escaped JSON string within a JSON object?](https://stackoverflow.com/a/39154630/3744182) to your `SalesLines` property. – dbc May 14 '19 at 18:53
  • I've updated to include valid json – aherrick May 14 '19 at 19:16
  • 1
    OK, then does applying `[JsonConverter(typeof(EmbeddedLiteralConverter>)]` to `public List SalesLines { get; set; }` do the job? – dbc May 14 '19 at 19:20
  • 1
    Why have you got this string representation, i.e. JSON within JSON? That's an odd thing to do. Are you able to alter the code which generates this data to do it in a better way? That would be the ideal solution – ADyson May 14 '19 at 19:25
  • @ADyson agreed this is one I don't have control over unfortunately. – aherrick May 14 '19 at 19:26
  • Are you maybe in a position to at least request that it be changed, even if you don't have direct control? – ADyson May 14 '19 at 19:30

2 Answers2

4

The value of your SalesLines property is double-serialized JSON: a string value that contains JSON embedded as a string literal. You would like to deserialize its contents to a final data model in one step.

To see what the data model should look like, you can unescape the JSON as follows:

var json = JToken.Parse(jsonString);

foreach(var token in json.SelectTokens("value[*].SalesLines").ToList())
{
    token.Replace(JToken.Parse((string)token));
}

Console.WriteLine(json);

Then use one of the code-generation tools mentioned in How to auto-generate a C# class file from a JSON object string to generate a data model from the unescaped JSON (I used http://json2csharp.com/):

public class ItemAttribute
{
    public string AttibuteName { get; set; }
    public string AttributeValue { get; set; }
}

public class SalesLine
{
    public string SONumber { get; set; }
    public int LineNum { get; set; }
    public string ItemId { get; set; }
    public List<ItemAttribute> ItemAttributes { get; set; }
}

public class Value
{
    public string documentType { get; set; }
    public string SONumber { get; set; }
    public List<SalesLine> SalesLines { get; set; }
}

public class RootObject
{
    public List<Value> value { get; set; }
}

Finally, apply EmbeddedLiteralConverter<List<SalesLine>> from this answer to How do I convert an escaped JSON string within a JSON object? to Value:

public class Value
{
    public string documentType { get; set; }
    public string SONumber { get; set; }

    [JsonConverter(typeof(EmbeddedLiteralConverter<List<SalesLine>>))]
    public List<SalesLine> SalesLines { get; set; }
}

Now you will be able to deserialize the JSON to RootObject directly:

root = JsonConvert.DeserializeObject<RootObject>(jsonString);

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 2
    EXACTLY what I was looking for. Thx! – aherrick May 14 '19 at 20:25
  • Is using this Literal Converter more or equally as performant as manually deserializing in a loop? – aherrick May 14 '19 at 20:37
  • 1
    @aherrick - It avoids loading the *entire* JSON into a `JToken` hierarchy upfront, so memory impact should be minimized. (Also, using `dynamic` rather than explicit types such as `JToken` or `RootObject` adds a performance penalty.) But you really have to test it yourself, see https://ericlippert.com/2012/12/17/performance-rant/ and also https://stackify.com/top-11-json-performance-usage-tips/, especially #6. – dbc May 14 '19 at 20:43
  • 1
    https://www.newtonsoft.com/json/help/html/Performance.htm#ManuallySerialize states that *The absolute fastest way to read and write JSON is to use JsonTextReader/JsonTextWriter directly to manually serialize types.* However doing this is quite inconvenient so I wouldn't recommend it unless you absolutely must. – dbc May 14 '19 at 20:46
-2

Create a class that will store your SalesLines, another class that will store ItemAttributes.

SalesLines class should have a property List.

Create a SalesOrder class that will have List as one of the properties.

Then you should be able to deserialize into SalesOrder.

ADyson
  • 57,178
  • 14
  • 51
  • 63
Alex M
  • 141
  • 2
  • 6
  • 1
    This doesn't really solve OP's problem, which is that the value of `"SalesLines"` is a **`string`** containing embedded JSON objects, and they want to directly deserialize that string to a data model. – dbc May 14 '19 at 20:00
  • It does solve the op's problem. The idea is the same as what you wrote above. – Alex M May 14 '19 at 20:46