2

I have this JSON Array as input.

[
    {
        "FirstName": "Test1",
        "LastName": "Test2",
        "Address": "London, GB",
        "Error": "Something's gone wrong"
    },
    {
        "FirstName": "Test3",
        "LastName": "Test4",
        "Address": "NewYork, US",
        "Error": "Something's gone wrong"
    },
    {
        "DisplayName": "ContactNumber",
        "Value": "01234 123 123"
    }
]

I want to build a JSON Object like this in C#

[
    "pages":{
        "FirstName": "Test1",
        "LastName": "Test2",
        "Address": "London, GB",
        "Error": "Something's gone wrong"
    },
    {
        "FirstName": "Test3",
        "LastName": "Test4",
        "Address": "NewYork, US",
        "Error": "Something's gone wrong"
    },
  "labels": {
        "DisplayName": "ContactNumber",
        "Value": "01234 123 123"
  }
}
]

I've created a Model with above output properties but they are not getting mapped when I deserialize the object into that Model. All values are returning null.

var deserializedData = JsonConvert.DeserializeObject<List<Config>>(serializedData);

the response I receive when I check is

[
    {
        "pages": null,
        "labels": null
    },
    {
        "pages": null,
        "labels": null
    }
]

Can anyone help me build the Custom Model for this format I want in C#.

Thanks in advance.

EKD
  • 73
  • 7
  • 1
    Your desired JSON is invalid. If you upload it to https://jsonlint.com/ you will get `Error: Parse error on line 7: Expecting 'STRING', got '{'`. Should the values of `"pages"` and `"labels"` be arrays? – dbc Nov 30 '17 at 16:34
  • 1
    *I've created a Model with above properties but they are not getting mapped when I deserialize the object into that Model.* -- can you share what you have tried so far? – dbc Nov 30 '17 at 16:37
  • try visual studio menu and create class from your json using it ...you might be facing problem because its not deserialize json string to your structure – Pranay Rana Nov 30 '17 at 16:38
  • I want to build a JSON Array like this [{ "Pages": { properties here}, "Labels": { properties here } }] – EKD Nov 30 '17 at 16:57
  • its better you provide full json string that you are inputing to function for conversion to object ... – Pranay Rana Nov 30 '17 at 17:28
  • serializedData value of this – Pranay Rana Nov 30 '17 at 17:28
  • @EKD : check my code below its working code , done code based on json string you provided before ...if string is different than please update same in your question – Pranay Rana Nov 30 '17 at 17:38
  • @EKD - things working for me for array of json also please check that also...and i suggest you check structure you are passing if below code update dont work for you,, for input i passed its working for me – Pranay Rana Nov 30 '17 at 17:42
  • @PranayRana I've updated the question. Apologies for misleading earlier with wrong input. I've updated the question. – EKD Nov 30 '17 at 17:48
  • @EKD - hi updated my answer , have a look your json structure is not valid one , also please accept / upvote answer if it works for you... – Pranay Rana Dec 01 '17 at 06:27

2 Answers2

0

you json is not valid json structure for parsing , this not valid at all(check what is wrong with it here : https://jsonlint.com/)

not valid json string given by you

[
    "pages":{
        "FirstName": "Test1",
        "LastName": "Test2",
        "Address": "London, GB",
        "Error": "Something's gone wrong"
    },
    {
        "FirstName": "Test3",
        "LastName": "Test4",
        "Address": "NewYork, US",
        "Error": "Something's gone wrong"
    },
  "labels": {
        "DisplayName": "ContactNumber",
        "Value": "01234 123 123"
  }
}
]

However I fixed json you given

Fixed and working json

[{
    "pages": [{
            "FirstName": "Test1",
            "LastName": "Test2",
            "Address": "London, GB",
            "Error": "Something's gone wrong"
        },
        {
            "FirstName": "Test3",
            "LastName": "Test4",
            "Address": "NewYork, US",
            "Error": "Something's gone wrong"
        }
    ],
    "labels": {
        "DisplayName": "ContactNumber",
        "Value": "01234 123 123"
    }
}]

After applying fix json , your C# object is

public class Page
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string Error { get; set; }
}

public class Labels
{
    public string DisplayName { get; set; }
    public string Value { get; set; }
}

public class RootObject
{
    public List<Page> pages { get; set; }
    public Labels labels { get; set; }
}

and I tried this with my fixed json and its working fine.

End of Edit


Below code is working fine for me with the class structure i provided you in below

for list

var jsonString = "[{ \"pages\": {\"id\": 12345, \"name\": \"John Doe\", \"number\": \"123\", \"test\": "+
 "\"John\" }, \"labels\": { \"test\": \"test1\", \"test2\": \"test2\" } }," +
                            "{ \"pages\": {\"id\": 12345, \"name\": \"John Doe\", \"number\": \"123\", \"test\": "+
 "\"John\" }, \"labels\": { \"test\": \"test1\", \"test2\": \"test2\" } }]";

var deserializedData = JsonConvert.DeserializeObject<List<RootObject>>(jsonString);

With single object as input

var jsonString = "{ \"pages\": {\"id\": 12345, \"name\": \"John Doe\", \"number\": \"123\", \"test\": "+
 "\"John\" }, \"labels\": { \"test\": \"test1\", \"test2\": \"test2\" } }";

 var deserializedData = JsonConvert.DeserializeObject<RootObject>(jsonString);

based on input

{ "pages": { "id": 12345, "name": "John Doe", "number": "123", "test": 
 "John" }, "labels": { "test": "test1", "test2": "test2" } }

below is generated classes

public class Pages
{
    public int id { get; set; }
    public string name { get; set; }
    public string number { get; set; }
    public string test { get; set; }
}

public class Labels
{
    public string test { get; set; }
    public string test2 { get; set; }
}

public class RootObject
{
    public Pages pages { get; set; }
    public Labels labels { get; set; }
}

If you have Json with you than you can generate C# class using visual studio itself.

In visual studio Find "Paste Sepcial" menu. i.e. copy you json string and click on Paste special , it will generate C# class for you.

you can follow my post : Generate Class From JSON or XML in Visual Studio

enter image description here

or use online website like : json2csharp

Pranay Rana
  • 175,020
  • 35
  • 237
  • 263
  • My input is [ { "FirstName": "Test1", "LastName": "Test2", "Address": "London, GB", "Error": "Something's gone wrong" }, { "FirstName": "Test3", "LastName": "Test4", "Address": "NewYork, US", "Error": "Something's gone wrong" }, { "DisplayName": "ContactNumber", "Value": "01234 123 123" } ] I've generated the class using Paste JSON as classes. And still I'm getting response as null The input I'm providing is a JSON Array. I wnt it with custom defined in JSON – EKD Nov 30 '17 at 17:11
  • @EKD please provide your code of C# for doing this operation – Pranay Rana Nov 30 '17 at 17:14
  • var deserializedData = JsonConvert.DeserializeObject>(serializedData); Output [ { "pages": null, "labels": null }, { "pages": null, "labels": null } ] – EKD Nov 30 '17 at 17:19
  • @EKD - please provide your full json also .....i am trying things at my end – Pranay Rana Nov 30 '17 at 17:25
  • I've updated the question properly now @Pranay Looking forward for your help. – EKD Nov 30 '17 at 17:28
0

What you have is a polymorphic JSON array containing a variety of types of objects, distinguishable by the presence of specific properties ("DisplayName" vs "FirstName", e.g.). What you would like to do is to deserialize that into a c# model in which each possible array item gets added to a collection-valued property of your model, where the collection property is chosen to have the correct item type.

This can be accomplished by using a custom JsonConverter. Since the problem statement is generic I'm going to make the converter be generic. A hardcoded converter would require less code.

First, define your desired model as follows:

public class Page
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string Error { get; set; }
}

public class Label
{
    public string DisplayName { get; set; }
    public string Value { get; set; }
}

public class RootObject
{
    public List<Page> pages { get; set; }
    public List<Label> labels { get; set; }
}

Next define the following converter:

public class PolymorphicArrayToObjectConverter<TObject> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(TObject).IsAssignableFrom(objectType);
    }

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

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

    static JsonObjectContract FindContract(JObject obj, IEnumerable<Type> derivedTypes, JsonSerializer serializer)
    {
        List<JsonObjectContract> bestContracts = new List<JsonObjectContract>();
        foreach (var type in derivedTypes)
        {
            if (type.IsAbstract)
                continue;
            var contract = serializer.ContractResolver.ResolveContract(type) as JsonObjectContract;
            if (contract == null)
                continue;
            if (obj.Properties().Select(p => p.Name).Any(n => contract.Properties.GetClosestMatchProperty(n) == null))
                continue;
            if (bestContracts.Count == 0 || bestContracts[0].Properties.Count > contract.Properties.Count)
            {
                bestContracts.Clear();
                bestContracts.Add(contract);
            }
            else if (contract.Properties.Count == bestContracts[0].Properties.Count)
            {
                bestContracts.Add(contract);
            }
        }
        return bestContracts.Single();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        else if (reader.TokenType != JsonToken.StartArray)
            throw new InvalidOperationException("JSON token is not an array at path: " + reader.Path);

        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
        existingValue = existingValue ?? contract.DefaultCreator();

        var lookup = contract
            .Properties
            .Select(p => new { Property = p, PropertyContract = serializer.ContractResolver.ResolveContract(p.PropertyType) as JsonArrayContract })
            .Where(i => i.PropertyContract != null)
            .ToDictionary(i => i.PropertyContract.CollectionItemType);
        var types = lookup.Select(i => i.Key).ToList();

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                case JsonToken.EndArray:
                    return existingValue;
                default:
                    {
                        var itemObj = JObject.Load(reader);
                        var itemContract = FindContract(itemObj, types, serializer);
                        if (itemContract == null)
                            continue;
                        var item = serializer.Deserialize(itemObj.CreateReader(), itemContract.UnderlyingType);
                        var propertyData = lookup[itemContract.UnderlyingType];
                        var collection = propertyData.Property.ValueProvider.GetValue(existingValue);
                        if (collection == null)
                        {
                            collection = propertyData.PropertyContract.DefaultCreator();
                            propertyData.Property.ValueProvider.SetValue(existingValue, collection);
                        }
                        collection.GetType().GetMethod("Add").Invoke(collection, new [] { item });
                    }
                    break;
            }
        }
        // Should not come here.
        throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
    }
}

Then, deserialize and re-serialize your RootObject collection as follows:

var settings = new JsonSerializerSettings
{
    Converters = { new PolymorphicArrayToObjectConverter<RootObject>() },
};
var root = new List<RootObject> { JsonConvert.DeserializeObject<RootObject>(jsonString, settings) };

var outputJson = JsonConvert.SerializeObject(root, Formatting.Indented);

As a result, the following JSON will be generated:

[
  {
    "pages": [
      {
        "FirstName": "Test1",
        "LastName": "Test2",
        "Address": "London, GB",
        "Error": "Something's gone wrong"
      },
      {
        "FirstName": "Test3",
        "LastName": "Test4",
        "Address": "NewYork, US",
        "Error": "Something's gone wrong"
      }
    ],
    "labels": [
      {
        "DisplayName": "ContactNumber",
        "Value": "01234 123 123"
      }
    ]
  }
]

Sample fiddle.

Note - code to automatically infer the array item type was adapted from this answer.

dbc
  • 104,963
  • 20
  • 228
  • 340