Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# data contract for complex JSON object

This is probably something really simple and I looked everywhere and tried everything I could come up with. So I apologize if this is a simple search and I was just looking for the wrong thing. I'm also new to data contracts and somewhat JSON so this probably isn't really that complex.

I am creating an API to ingest JSON and store it in our database. The JSON will look something like this:

{
"appname" : "MyApp",
"key" : "Test123",
"data" :[
  { "data1" : "10551296", "data2" : "TrainingIns", "data3" : "Completed"}
  ,
  { "connectorType" : "webserver-to-appserver", "sourceUri" : "data4", "destinationUri" : "data5", "rails" : "N", "data6" : "null" }
  ,
  { "groupId" : "group1", "failbackAction" : "null", "normal" : "null", "failoverAction" : "null", "failbackAction" : "null", "failoverAction" : "null", "artifactId" : "mywebserver", "normalState" : "null" }
  ,
  { "instanceId" : "10551296abc" }]
,
"updateId" : "MyID",
"updateTS" : "30-AUG-16 05.56.24.000000000 AM" ,
"creationUser" : "APICall"
}

Where the 'data' field will be an array with a variable amount of JSON objects. The issue I am having stems from either not getting data in the 'data' object or having it be completely undefined.

[DataContract]
public class Update_DB
{
    [DataMember(Name = "appname", IsRequired = true)]
    public string appname { get; set; }
    [DataMember]
    public string key { get; set; }

    [DataMember(Name="data",IsRequired = true)]
    public List<JsonValue> data { get; set; }

    [DataMember]
    public string updateId { get; set; }
    [DataMember]
    public string updateTS { get; set; }
    [DataMember]
    public string creationUser { get; set; }
}

I've gathered I might need a collection of some sort? I've tried everything I could find but I don't know how I should define the data member for 'data'. The above contract gives me empty arrays when I do this:

string x = JsonConvert.SerializeObject(collection.data);

I can get every other field I just need to turn the 'data' field into a string.

Hopefully that is enough info. Thanks in advance for any help!

like image 691
Joey Gelpi Avatar asked Feb 20 '26 13:02

Joey Gelpi


2 Answers

Under normal circumstances, you could define your data property as a List<Dictionary<string, string>>, like so:

    [DataMember(Name = "data", IsRequired = true)]
    public List<Dictionary<string, string>> data { get; set; }

Then you would be able to serialize and deserialize it successfully with Json.NET. Unfortunately, one of your data objects has duplicated keys:

  {
     "groupId":"group1",
     "failbackAction":"null",
     "normal":"null",
     "failoverAction":"null",
     "failbackAction":"null",
     "failoverAction":"null",
     "artifactId":"mywebserver",
     "normalState":"null"
  },

Using duplicated keys is not recommended by the JSON standard, which states:

When the names within an object are not unique, the behavior of software that receives such an object is unpredictable.

In addition, c# dictionaries of course do not support duplicated keys, and data contract serialization does not duplicated property names.

However, it is possible to read a JSON object with duplicated keys using Json.NET's JsonReader and create a custom JsonConverter to handle duplicated keys.

First, define the following class to replace JsonValue. JsonValue is a silverlight-specific class whose use has been deprecated in overall .Net:

[JsonConverter(typeof(JsonValueListConverter))]
public sealed class JsonValueList
{
    public JsonValueList()
    {
        this.Values = new List<KeyValuePair<string, string>>();
    }

    public List<KeyValuePair<string, string>> Values { get; private set; }
}

class JsonValueListConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(JsonValueList).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var jsonValue = (existingValue as JsonValueList ?? new JsonValueList());
        if (reader.TokenType != JsonToken.StartObject)
            throw new JsonSerializationException("Invalid reader.TokenType " + reader.TokenType);
        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                case JsonToken.PropertyName:
                    {
                        var key = reader.Value.ToString();
                        if (!reader.Read())
                            throw new JsonSerializationException(string.Format("Missing value at path: {0}", reader.Path));
                        var value = serializer.Deserialize<string>(reader);
                        jsonValue.Values.Add(new KeyValuePair<string, string>(key, value));
                    }
                    break;
                case JsonToken.EndObject:
                    return jsonValue;
                default:
                    throw new JsonSerializationException(string.Format("Unknown token {0} at path: {1} ", reader.TokenType, reader.Path));
            }
        }
        throw new JsonSerializationException(string.Format("Unclosed object at path: {0}", reader.Path));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var jsonValue = (JsonValueList)value;
        writer.WriteStartObject();
        foreach (var pair in jsonValue.Values)
        {
            writer.WritePropertyName(pair.Key);
            writer.WriteValue(pair.Value);
        }
        writer.WriteEndObject();
    }
}

Notice the use of [JsonConverter(typeof(JsonValueListConverter))]. This specifies the use of a custom converter when serializing and deserializing JsonValueList.

Next, define your Update_DB class as follows:

[DataContract]
public class Update_DB
{
    [DataMember(Name = "appname", IsRequired = true)]
    public string appname { get; set; }
    [DataMember]
    public string key { get; set; }

    [DataMember(Name = "data", IsRequired = true)]
    public List<JsonValueList> data { get; set; }

    [DataMember]
    public string updateId { get; set; }
    [DataMember]
    public string updateTS { get; set; }
    [DataMember]
    public string creationUser { get; set; }
}

Now you will be able to serialize and deserialize your JSON successfully. Sample fiddle.

Update

If you do not have duplicated keys, you can define your class as follows:

[DataContract]
public class Update_DB
{
    [DataMember(Name = "appname", IsRequired = true)]
    public string appname { get; set; }
    [DataMember]
    public string key { get; set; }

    [DataMember(Name = "data", IsRequired = true)]
    public List<Dictionary<string, string>> data { get; set; }

    [DataMember]
    public string updateId { get; set; }
    [DataMember]
    public string updateTS { get; set; }
    [DataMember]
    public string creationUser { get; set; }
}

And then the following:

var collection = new Update_DB
{
    data = new List<Dictionary<string, string>>
    {
        new Dictionary<string, string>
        {
            {"data1", "10551296"},
            {"data2", "TrainingIns"},
            {"data3", "Completed"},
        },
        new Dictionary<string, string>
        {
            {"connectorType", "webserver-to-appserver"},
            {"sourceUri", "data4"},
            {"destinationUri", "data5"},
        },
    },
};

string x = JsonConvert.SerializeObject(collection.data, Formatting.Indented);

Console.WriteLine(x);

Produces the output:

[
  {
    "data1": "10551296",
    "data2": "TrainingIns",
    "data3": "Completed"
  },
  {
    "connectorType": "webserver-to-appserver",
    "sourceUri": "data4",
    "destinationUri": "data5"
  }
]

Sample fiddle.

like image 180
dbc Avatar answered Feb 23 '26 01:02

dbc


Another option is to use the dynamic keyword. You could use a list of this type for data (per below).

[DataContract]
public class Update_DB
{
    [DataMember(Name = "appname", IsRequired = true)]
    public string appname { get; set; }
    [DataMember]
    public string key { get; set; }

    [DataMember(Name = "data", IsRequired = true)]
    public List<dynamic> data { get; set; }

    [DataMember]
    public string updateId { get; set; }
    [DataMember]
    public string updateTS { get; set; }
    [DataMember]
    public string creationUser { get; set; }
}

From there, you could use the object by deserializing with JSON.Net, and access into the dynamic data object (assuming you know something about the shape of this dynamic object). Something like below will work based on the input string from the original post.

Update_DB dataObj = JsonConvert.DeserializeObject<Update_DB>(objectJSON);
string test = dataObj.data[1].connectorType; //evaluates to "webserver-to-appserver"
like image 36
BRass Avatar answered Feb 23 '26 02:02

BRass



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!