Deserializing JSON with Dynamic Keys

Deserializing JSON with dynamic keys

Seriously, no need to go down the dynamic route; use

var deser = new JavaScriptSerializer()
.Deserialize<Dictionary<string, Dictionary<string, int>>>(val);
var justDaily = deser["daily"];

to get a dictionary, and then you can e.g.

foreach (string key in justDaily.Keys)
Console.WriteLine(key + ": " + justDaily[key]);

to get the keys present and the corresponding values.

json deserialization to C# with dynamic keys

If you are using Json.NET, you can use JsonConvert.DeserializeObject<Dictionary<string, Dataset>>(json) and the keys of the dictionary will be nasdaq_imbalance, DXOpen IM, Float Shares

Parse (Deserialize) JSON with dynamic keys (C#)

you don't need to deserialize, you can parse

var jsonParsed=JObject.Parse(json);

string[] previewNames= ((JArray)jsonParsed["objects"])
.Select(v => ((JObject)v["sets"]))
.Select(i=>i.Properties().Select(y=> y.Value["preview"]["resource"])).First()
.Select(i=> (string) ((JObject)i)["preview_name"]).ToArray();

result

    preview.040914
preview.81a54c

Deserialize JSON with dynamic key into Object

You are close.

In your JSON, the dynamic keys are at the root level, and so that object needs to be represented by a Dictionary<string, ISBN>. It seems you have realized this, however, in your model, you have added an outer class to contain the dictionary. This doesn't line up with the JSON and is not needed here. Instead, you should deserialize directly into the dictionary:

string json = new WebClient().DownloadString(URLAddress);
var bookRoot = JsonConvert.DeserializeObject<Dictionary<string, ISBN>>(json);

From there, you can loop over the Values collection on the dictionary to access the books without having to know the key(s) in advance:

foreach (var isbn in bookRoot.Values)
{
// ...
}

Or, if you're expecting only one book, you can get it from the dictionary like this:

var isbn = bookRoot.Values.First();

Here is a working demo: https://dotnetfiddle.net/csgSKp

Deserialize JSON with dynamic keys using C# Json.NET

The typical solution to dealing with dynamic keys is to use a Dictionary<string, T> in place of a regular class. See How can I deserialize a child object with dynamic (numeric) key names? for an example of this. However, that solution doesn't really work for your case, because there are other properties in the same object which do not have dynamic keys (the bankName and totalAmount), and the values of those properties are primitives whereas the value of dynamic property is an array of bank accounts. A better solution here is to use a JsonConverter.

Before we get to that, we need to set up a class structure to deserialize into. This is pretty straightforward:

class RootObject
{
public List<Bank> BankDetails { get; set; }
}

[JsonConverter(typeof(BankConverter))]
class Bank
{
public string BankName { get; set; }
public decimal TotalAmount { get; set; }
public List<Account> Accounts { get; set; }
}

class Account
{
[JsonProperty("sNo")]
public int SequenceNumber { get; set; }
[JsonProperty("acNo")]
public string AccountNumber { get; set; }
[JsonProperty("acBalance")]
public decimal Balance { get; set; }
}

You'll notice that I've added a few [JsonProperty] attributes in the Account class to map the shorthand property names in the JSON to friendlier property names in that class. And the [JsonConverter] attribute on the Bank class tells the serializer that we will be using a custom BankConverter to handle that class.

Here is the code for the BankConverter. It uses a JObject internally to make it easier to read and work with the JSON.

class BankConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Bank);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
Bank bank = new Bank();
// populate the known properties (bankName and totalAmount)
serializer.Populate(obj.CreateReader(), bank);
// now handle the dynamic key
bank.Accounts = obj[bank.BankName].ToObject<List<Account>>(serializer);
return bank;
}

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

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

With these classes in place, you can deserialize the JSON like this:

var root = JsonConvert.DeserializeObject<RootObject>(json);

Here is a working demo: https://dotnetfiddle.net/knsRLv

How to deserialize JSON with dynamic and static key names in C#

You have two options, the first is to deserialise directly to a List<Dictionary<string, object>>, for example:

var responses = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(json);

Alternatively, if you are stuck on using your object, you will need to write a custom converter. For example, something like this:

public class MyResponseConverter : JsonConverter
{
public override bool CanConvert(Type type) => type == typeof(MyResponse);

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var responseObject = JObject.Load(reader);

MyResponse response = new MyResponse
{
StartTime = (string)responseObject["starttime"],
EndTime = (string)responseObject["endtime"],
};

var varData = new Dictionary<string, object>();

foreach (var property in responseObject.Properties())
{
if(property.Name == "starttime" || property.Name == "endtime")
{
continue;
}

varData.Add(property.Name, property.Value);
}

response.VarData = varData;
return response;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// If you want to write to JSON, you will need to implement this method
throw new NotImplementedException();
}
}

And your class would change slightly to this:

[JsonConverter(typeof(MyResponseConverter))]
public class MyResponse
{
[JsonProperty(PropertyName = "starttime")]
public string StartTime { get; set; }
[JsonProperty(PropertyName = "endtime")]
public string EndTime { get; set; }

public Dictionary<string, object> VarData { get; set; }
}

Now you deserialise like this:

var responses = JsonConvert.DeserializeObject<List<MyResponse>>(json);

Deserialize json string with nested array of objects with dynamic key

the keys are guaranteed to be unique so in the end, I want one single flat map. Another point, I don't need to serialize TransactionInfo back to JSON.

Since all keys are unique, and you don't care about serialization of this POJO back into JSON, you can transform the list of maps into a map inside a constructor.

public class TransactionInfo {
String transactionId;
Boolean isSettled;
Map<String, String> transactionProperties;

public TransactionInfo(String transactionId, Boolean isSettled, Map<String, String> transactionProperties) {
this.transactionId = transactionId;
this.isSettled = isSettled;
this.transactionProperties = transactionProperties;
}

public TransactionInfo(
@JsonProperty("transactionId") String transactionId,
@JsonProperty("isSettled") Boolean isSettled,
@JsonProperty("transactionProperties") List<Map<String, String>> transactionPropertiesList) {

this.transactionId = transactionId;
this.isSettled = isSettled;
this.transactionProperties = transactionPropertiesList.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
}
}

Code-snippet from the main():

String json = """
{
"transactionId": "EFODKKXHE003",
"isSettled": false,
"transactionProperties": [
{
"key1": "Value1"
},
{
"key2": "Value2"
},
{
"key3": "Value3"
}
]
}""";

ObjectMapper mapper = new ObjectMapper();
TransactionInfo transactionInfo = mapper.readValue(json, TransactionInfo.class);
System.out.println(transactionInfo);

Output:

TransactionInfo{transactionId='EFODKKXHE003', isSettled=false, transactionProperties={key1=Value1, key2=Value2, key3=Value3}}


Related Topics



Leave a reply



Submit