3

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!

4
  • 1
    1) Can you share the JsonValue class? 2) Your JSON has duplicated property names: "failoverAction". According the standard, When the names within an object are not unique, the behavior of software that receives such an object is unpredictable. Are you sure you need this? Commented Sep 27, 2016 at 17:54
  • JsonValue is built into the System.Json namespace. And I actually just edited it out some data because it is work related and those happened to be the same. So normally they wouldn't be. Commented Sep 27, 2016 at 18:12
  • 1
    OK - but aren't you actually using Json.NET? And also I believe System.Json has been deprecated, see nuget.org/packages/System.Json. Are you sure you want to use it? Commented Sep 27, 2016 at 18:16
  • 1
    Ah yes I am using Json.NET. I picked up this project a couple days ago so I'm still figuring it out. JsonValue came from me endlessly trying things because I can't figure out what data type I need to read the 'data' field. Commented Sep 27, 2016 at 18:28

3 Answers 3

4

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.

Sign up to request clarification or add additional context in comments.

5 Comments

I feel bad because I just edited the data real quick to not have work data in it, but normally there wont be duplicate key names. Really I've been looking for this List<Dictionary<string, string>>. Although when I do JsonConvert.SerializeObject(collection.data); I just get [{},{},{},{},{},{},{}]. Do I have to do something different to serialize it because it is complex? Sorry if this is a really easy question!
@JoeyGelpi - I tested the code above on the JSON you provided, and it works. See the fiddle. If you actually have different JSON or different requirements, I cannot say what your problem is without a minimal reproducible example.
It's the same format of JSON just without duplicates. Your fiddle doesn't look to use the JsonValueList class at all so I would think it would work the same as mine. Maybe something is getting lost in transition with the data contract since you are just using a string. I'll take a look at this but I am out for the day. I'll try to strip it down and make a full example if not. Thanks for the help!
@JoeyGelpi - once it became clear that there were no duplicated keys I switched over to the dictionary format. Anyway, I updated the original fiddle to also serialize collection.data and the resulting string looks good.
The difference is that you deserialize the string explicitly. I use a WebInvoke which seems to deserialize automatically when I do ResponseJsonType WriteToDatabase(Update_DB collection); I tried sending collection as a string and just deserializing that like you did but I'm getting bigger errors. I dunno I'll keep trying. Thanks for the help/
1

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"

Comments

-1

Use Json2CSharp.com to make sure you have everything correct:

 public class Datum
{
public string data1 { get; set; }
public string data2 { get; set; }
public string data3 { get; set; }
public string connectorType { get; set; }
public string sourceUri { get; set; }
public string destinationUri { get; set; }
public string rails { get; set; }
public string data6 { get; set; }
public string groupId { get; set; }
public string failbackAction { get; set; }
public string normal { get; set; }
public string failoverAction { get; set; }
public string artifactId { get; set; }
public string normalState { get; set; }
public string instanceId { get; set; }
}

public class RootObject
{
public string appname { get; set; }
public string key { get; set; }
public List<Datum> data { get; set; }
public string updateId { get; set; }
public string updateTS { get; set; }
public string creationUser { get; set; }
}

1 Comment

I see where that is going but the issue is that 'data' can have a variable number of arguments. Which I guess means I shouldn't be using a Data Contract maybe? That's why I figured making it a Collection of some sort without a predefined number of elements would work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.