Can't set TimeSpan property from JSON source C# - c#

I have been trying to read a JSON file and deserialize it to a class object. The problem is that I am storing a TimeSpan? property of the class as string in the JSON file which doesn't get deserialized and remains null. Please find the code for reference below:
Interface:
public interface IAirport
{
.
.
.
TimeSpan? TimeOffset { get; set; }
.
.
.
}
Class :
public class Airport:IAirport{
.
.
.
private TimeSpan? _offsetTime;
public TimeSpan? OffssetTime
{
get{return _offsetTime;}
set{SetProperty<TimeSpan>(ref _offsetTime, value);}
}
.
.
.
}
This is the way I deserialize :
private static T ConvertJsonStreamToObject<T>(StreamReader fileStream)
{
JsonSerializer serializer = new JsonSerializer();
return (T)serializer.Deserialize(fileStream, typeof(T));
}
and lastly the JSON :
{
"AirportDesignation": "ZZU",
"EffectiveDate": "1901-01-01 00:00:00.0000000",
"Description": "Mzuzu",
"DescriptionShort": "Mzuzu",
"EffectiveStatus": "A",
"Country": "MWI",
"State": " ",
"LatitudeDegrees": 11,
"LatitudeMinutes": 27,
"LatitudeSeconds": 0,
"LatitudeCoordinates": "S",
"LongitudeDegrees": 34,
"LongitudeMinutes": 1,
"LongitudeSeconds": 0,
"LongitudeCoordinates": "E",
"AirportType": "A",
"TimeOffset": "00:00:00",
"IsTimeOffsetPositive": true
}
I tried the following way to set the property and use TimeSpan.Parse() to parse the string to TimeSpan but none of them worked :
private string _timeOffset;
public TimeSpan? TimeOffset
{
get { return TimeSpan.Parse(_timeOffset); }
set { _timeOffset = value.ToString(); }
}
I am out of ideas to try, any help would be appreciated.
UPDATE:
I tried #Caius Jard suggestion, getting the System.ArgumentNullException
Updated the property as below:
private string _timeOffset;
[Required]
public TimeSpan? TimeOffset
{
get { return TimeSpan.Parse(_timeOffset); }
set { SetProperty<string>(ref _timeOffset, value.Value.ToString()); }
}

Found the culprit finally, It was an attribute causing the problem. I had an interface as the base for the Airport class and it required the TimeOffset to be a [DataMember] otherwise the property isn't identified as the data member of the class.
Adding the [DataMember] attribute now sets the value.

Related

How can I parse json to a C# object (Class)?

I have JSON data that I need to parse from C# object.
this is JSON Example.
{
"types":
[
[
"tour_type",
[
["groups",1],
["individual",2]
]
]
]
}
Here are my C# classes that are meant to contain that data:
using System;
using Newtonsoft.Json;
namespace JsonDeserializationTest
{
[JsonProperty("types")]
public class Types
{
[JsonProperty]
public List<Type> Values {get;set;}
}
public class Type
{
[JsonProperty]
public string Key {get;set;}
[JsonProperty]
public List<Dictionary<string, int>> Values { get; set; }
}
}
It's not working now.
How can I fix it?
Use the JsonSerializer (System.Text.Json) object.
Code:
YourClass obj = JsonSerializer.Deserialize<YourClass>(jsonString);
Your json has a list of list of the object... but you are declaring only List of the object.
public class Types
{
[JsonProperty("types")]
public List<List<object>> Values { get; set; }
// ------ UPDATE: This can only be list of list of 'object' ------- \\
}
Also, you are using the JsonProperty on the class, which is not where that normally goes. You want to use that on the property of the class.
UPDATE:
You cannot use List<List<Type>> for the json you are getting, it can only be List<List<object>>. You have to use object because it can either be a string or a List<List<string>>. After you update your Types class, you can successfully deserialize the json above.
var obj = JsonConvert.DeserializeObject<Types>(json);
and based on your json definition, you can access tour_type by using the following code
types.Values.First()[0].ToString()
// output: tour_type
List<List<string>> data = JsonConvert.DeserializeObject<List<List<string>>>(types.Values.First()[1].ToString())
// data[0]
[0]: "groups"
[1]: "1"
// data[1]
[0]: "individual"
[1]: "2"
Since both of the items in the types are objects, you will either have to convert them to string or a list of list of strings or whatever object they actually are.
The JSON payload in the provided example is formatted quite strangely, especially since it contains seemingly unnecessary array nesting. A payload like this usually includes more nested objects (rather than a bunch of nested arrays). Additionally, it has a list of (string, int) pairs, which is semantically very similar to a Dictionary<string, int>, but the payload doesn't lend itself to that. It would be helpful to know where it is coming from (what context) to understand how it might change.
The example JSON brings up a few questions (that you may want to ask yourself):
Can the "types" array contain multiple entries (at its immediate nesting)?
Can the "tour_type" key name appear after the array of string, int pairs? Is it possible for an entry where no such name exists?
What other elements can exist in the arrays within "tour_type"?
Is it guaranteed that the most nested array will contain just a single (string, int) pair?
Similarly, it is hard to understand what the example C# class is trying to encapsulate. Is List<Dictionary<string, int>> necessary?
All that said, here's a solution using the built-in System.Text.Json library, that could work for you. You could write something similar using Newtonsoft.Json, if necessary. The solution assumes:
We can't change the JSON payload (and that the third party API response will always returns something that is structurally similar to the example)
We can only make minimal changes to the C# class object provided in the example
The solution creates and a JsonConverter<T> that uses the low-level Utf8JsonReader to manually parse and create the custom object. This is required since nested "[" are being used to delineate what should be objects rather than "{". The converter is then registered by annotating the class with the attribute. Now, simply call JsonSerializer.Deserialize, passing in the JSON payload.
public class Tours
{
[JsonPropertyName("types")]
public List<UserType> Types { get; set; }
}
// Annotate the type to register the converter to use
[JsonConverter(typeof(CustomUserTypeConverter))]
public class UserType
{
public string Key { get; set; }
public Dictionary<string, int> Values { get; set; }
}
// This will use the low-level reader to build up the UserType
public class CustomUserTypeConverter : JsonConverter<UserType>
{
// Extra structural validation was done for invalid/incomplete JSON
// which might be too strict or incorrect and hence might require adjustments.
public override UserType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var result = new UserType();
if (!reader.Read())
{
throw new JsonException("Incomplete JSON.");
}
if (reader.TokenType != JsonTokenType.EndArray)
{
result.Key = reader.GetString();
ReadAndValidate(ref reader, JsonTokenType.StartArray);
int depthSnapshot = reader.CurrentDepth;
var values = new Dictionary<string, int>();
do
{
reader.Read();
if (reader.TokenType != JsonTokenType.StartArray && reader.TokenType != JsonTokenType.EndArray)
{
throw new JsonException($"Invalid JSON payload. Expected Start or End Array. TokenType: {reader.TokenType}, Depth: {reader.CurrentDepth}.");
}
if (reader.CurrentDepth <= depthSnapshot)
{
break;
}
reader.Read();
if (reader.TokenType != JsonTokenType.EndArray)
{
string key = reader.GetString();
reader.Read();
int value = reader.GetInt32();
values.Add(key, value);
ReadAndValidate(ref reader, JsonTokenType.EndArray);
}
} while (true);
ReadAndValidate(ref reader, JsonTokenType.EndArray);
result.Values = values;
}
return result;
}
private void ReadAndValidate(ref Utf8JsonReader reader, JsonTokenType expectedTokenType)
{
bool readNext = reader.Read();
if (!readNext || reader.TokenType != expectedTokenType)
{
string message = readNext ?
$"Invalid JSON payload. TokenType: {reader.TokenType}, Depth: {reader.CurrentDepth}, Expected: {expectedTokenType}" :
$"Incomplete JSON. Expected: {expectedTokenType}";
throw new JsonException(message);
}
}
// Implement this method if you need to Serialize (i.e. write) the object
// back to JSON
public override void Write(Utf8JsonWriter writer, UserType value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
Here's how you would use the above converter to serialize the JSON string provided in the example, along with how to access the values.
public static Tours ParseJson(string json)
{
Tours tours = JsonSerializer.Deserialize<Tours>(json);
return tours;
}
public static void AccessValues(Tours tours)
{
foreach (UserType data in tours.Types)
{
string typeName = data.Key; // "tour_type"
foreach (KeyValuePair<string, int> pairs in data.Values)
{
string key = pairs.Key; // "groups" or "individual
int value = pairs.Value; // 1 or 2
}
}
}
For what it's worth, Visual Studio suggests the following C# class structure for the example JSON (which is similar to what #Jawad suggested):
public class Rootobject
{
public object[][] types { get; set; }
}
Hope that helps.
I couldn't figure out your JSON so I created an example with verified JSON.
Try this:
JSON:
{
"Items": [
{
"Name": "tour",
"Attributes": [
{
"Name": "groups",
"Value": 1
},
{
"Name": "individual",
"Value": 2
}
]
},
{
"Name": "demo",
"Attributes": [
{
"Name": "this is demo",
"Value": 3
},
{
"Name": "design pattern",
"Value": 99
}
]
}
]
}
Types foo = JsonSerializer.Deserialize<Types>(jsonString);
public class TypeAttribute
{
public string Name { get; set; }
public int Value { get; set; }
}
public class Type
{
private readonly ICollection<TypeAttribute> _attributes;
public Type()
{
_attributes = new Collection<TypeAttribute>();
}
public void AddAttributes(IEnumerable<TypeAttribute> attrs)
{
foreach(TypeAttribute ta in attrs)
{
_attributes.Add(ta);
}
}
public string Name { get; set; }
public IEnumerable<TypeAttribute> Attributes
{
get { return _attributes; }
set
{
foreach(TypeAttribute ta in value)
{
_attributes.Add(ta);
}
}
}
}
public class Types
{
ICollection<Type> _items;
public Types()
{
_items = new Collection<Type>();
}
public void AddItems(IEnumerable<Type> tps)
{
foreach (Type t in tps)
{
_items.Add(t);
}
}
public IEnumerable<Type> Items
{
get { return _items; }
set
{
foreach (Type t in value)
{
_items.Add(t);
}
}
}
}

Unable to handle Collection Deserealization Error

I'm trying to handle deresealize an object that does not comply with some of my classes. I wold like the code to execute and fail only on the invalid attributes but the deserealization method is returning a null Object.
I am using this method in a generic utility class that deserealizes some string to any given type.
From the test code, the error handler works correctly on invalid dates and other invalid types and returns the object with the default .NET initialization values.
If I change (or comment) the Items collection in the sub object, the code works.
string json = "{\"Id\":8,\"CreatedByUserId\":0,\"CreatedOnDate\":\"2019X-Y02Z-W06T18:A51:05.783\",\"LastModifiedByUserId\":1,\"LastModifiedOnDate\":\"2019-03-12T17:00:34.82\",\"OperationData\":{\"IsActive\":true,\"Items\":[{\"_Id\":1,\"Id_Value\":0,\"Id\":1},{\"_Id\":2,\"Id\":2},{\"Id\":1,\"IsDeleted\":false,\"Content\":{}}]}}";
TestType test = DeserealizeContent(json);
/*The convertion utility*/
private static TestType DeserealizeContent(string data)
{
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
Error = HandleDeserializationError
};
var r = JsonConvert.DeserializeObject<TestType>(data, settings);
return r;
}
public static void HandleDeserializationError(object sender, ErrorEventArgs errorArgs)
{
errorArgs.ErrorContext.Handled = true;
}
/*Supporting types*/
public class TestType {
public int Id { get; set; }
public DateTime CreatedOnDate { get; set; }
public int CreatedByUserId { get; set; }
public string Instructions { get; set; }
public OperationDataType OperationData {get;set;}
}
public class OperationDataType {
public bool IsActive { get; set; }
public List<int> Items { get; set; }
}
I was expecting the error handler to catch handle the exception and continue with the process but instead the deserealization just returns null in the end.
If I change List Items to List Items the result is correctly parsed.
My expected result wold be:
{
"Id": 8,
"CreatedByUserId": 0,
"CreatedOnDate": null,
"LastModifiedByUserId": 1,
"LastModifiedOnDate": "2019-03-12T17:00:34.82",
"OperationData": {
"IsActive": true,
"Items": null
}
}
EDIT - workaround
The suggestion from Yair (bellow) works.
Changing from List to List works as expected and the exception get handle correctly.
The items in your json is not an array of int so how you want it to be List?
Items is array of objects look at the json formated:
{
"Id": 8,
"CreatedByUserId": 0,
"CreatedOnDate": "2019X-Y02Z-W06T18:A51:05.783",
"LastModifiedByUserId": 1,
"LastModifiedOnDate": "2019-03-12T17:00:34.82",
"OperationData": {
"IsActive": true,
"Items": [{
"_Id": 1,
"Id_Value": 0,
"Id": 1
}, {
"_Id": 2,
"Id": 2
}, {
"Id": 1,
"IsDeleted": false,
"Content": {}
}
]
}
}
You can do one of three things:
handle the json content as string or map it so the items will be int array [1,2,3].
create item class for the items that include all the fields you need then extract the int that you want.
get it as object like you do now and use reflection () for getting the int you want.
you can use this function for reflection:
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
and then use it like this:
GetPropValue(test.OperationData.items[0], "Id")
EDIT
you can use this to deserialize the json in generic way:
Newtonsoft.Json.Linq.JObject jsonDeserialized = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject<object>(json);
and then
you can map it manually or with automapper to the new TestType test that you want without the items in it.
you can get the values like this:
test.Id = jsonDeserialized["Id"];
test.CreatedByUserId = jsonDeserialized["CreatedByUserId"];
and so on
according to your last comment i find that if i changed the List<int> Items to List<long> Items it works as you wanted. it is something with primitive types and the parsing that deserialize do.

json.net deserializing #prefixed properties

I am trying to deserialize json i retrieve from a website into POCOs, and I am stuck on the fact, that json.net will not deserialize properties with a preceding # sign.
I have found numerous posts on SO, which state the solution to this is to annotate the C# properties in the POCO with the JsonPropertyAttribute and specifying the property name directly. I have done so, however, my C# property stays null.
POCO code:
[Newtonsoft.Json.JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)]
public class Event : IEvent
{
private readonly String name;
private readonly String context;
private readonly String type;
private readonly UInt32 id;
private readonly DateTime startDate;
private readonly DateTime endDate;
public Event(String name)
{
this.name = name;
}
[JsonProperty(Required = Required.Always, PropertyName = "#context")]
public String Context { get { return this.context; } }
[JsonProperty(Required = Required.Always, PropertyName = "#type")]
public String Type { get { return this.type; } }
[JsonProperty(Required = Required.Always)]
public String Name { get { return this.name; } }
public UInt32 ID { get { return this.id; } }
public DateTime StartDate { get { return this.startDate; } }
public DateTime EndDate { get { return this.endDate; } }
}
Deserializing code:
public void Test()
{
string innerHtml = #"{
""#context"": ""http:\/\/ schema.org"",
""#type"": ""Event"",
""name"": ""Kabarett Tipps in \u00d6sterreich: Diese K\u00fcnstler sollten Sie nicht verpassen"",
""location"": {
""#type"": ""Place"",
""address"": {
""#type"": ""PostalAddress"",
""addressCountry"": ""AT"",
""addressLocality"": ""Wien - Landstrasse"",
""postalCode"": ""1030"",
""streetAddress"": null
},
""name"": ""Ganz \u00d6sterreich"",
""url"": ""\/l\/ganz-oesterreich""
},
""url"": ""\/e\/kabarett-tipps-in-oesterreich-diese-kuenstler-sollten-sie-nicht-verpassen#st-241664441"",
""startDate"": ""2018-06-18"",
""endDate"": ""2019-06-24"",
""image"": ""https:\/\/cdn.kurier.at\/img\/100\/210\/772\/kabarett.jpg""
}";
IEvent #event = JsonConvert.DeserializeObject<Event>(innerHtml);
}
I see Name being populated, but Type and Context remain null. Anybody else observing this issue?
As Brian Rogers pointed out, my POCO properties were readonly. Json.net's documentation states:
Because a JsonConverter creates a new value, a converter will not work
with readonly properties because there is no way to assign the new
value to the property. Either change the property to have a public
setter or place a JsonPropertyAttribute or DataMemberAttribute on the
property.
Apparently placing that attribute on the property does not have the desired effect. I placed the attribute on the private readonly fields backing the properties, which finally yielded the desired result.
Thanks Brian for the pointer :)

Serialize and deserialize part of JSON object as string with WCF

I have a WCF REST service which has a resource which contains several typed fields, and then a field which can be an array of objects. I want the field on our service to serialize this field as if it were a string. Example:
[DataContract]
public class User
{
[DataMember]
public long ID;
[DataMember]
public string Logon;
[DataMember]
public string Features;
}
When users of our API POST a new User object, I'd like them to be able to do use something like this as the body:
{
"ID" : 123434,
"Logon" : "MyLogon",
"Features" : [
{ "type": "bigFeature", "size": 234, "display":true },
{ "type": "smFeature", "windowCount": 234, "enableTallness": true}
]
}
instead of
{
"ID" : 123434,
"Logon" : "MyLogon",
"Features" : "[
{ \"type\": \"bigFeature\", \"size\": 234, \"display\":true },
{ \"type\": \"smFeature\", \"windowCount\": 234, \"enableTallness\": true}
]"
}
On the service side, I'm going to be saving the "Features" array as JSON text blog in the database, and when I return the Object on GET calls, I'd like it to round trip properly.
If you were willing to switch to Json.NET, you could serialize your Features string as a private JToken proxy property:
[DataContract]
public class User
{
[DataMember]
public long ID;
[DataMember]
public string Logon;
string _features = null;
[IgnoreDataMember]
public string Features
{
get
{
return _features;
}
set
{
if (value == null)
_features = null;
else
{
JToken.Parse(value); // Throws an exception on invalid JSON.
_features = value;
}
}
}
[DataMember(Name="Features")]
JToken FeaturesJson
{
get
{
if (Features == null)
return null;
return JToken.Parse(Features);
}
set
{
if (value == null)
Features = null;
else
Features = value.ToString(Formatting.Indented); // Or Formatting.None, if you prefer.
}
}
}
Note that, in order to serialize the Features string without escaping, it must be valid JSON, otherwise your outer JSON will be corrupt. I enforce this in the setter. You could use JArray instead of JToken to enforce the requirement that the string represent a JSON array, if you prefer.
Note that the string formatting isn't preserved during serialization.

How to avoid fields when deserialize the JSON response directly into a strongly typed object?

I have a json response displayed as below. I am using datacontractserializer to serialize.
If I need only "text" and "created time" from this Json response...how should be my DataContract looks like?
Do I need to have all these six properties in my data contract ? and use "IgnoreDataMember" as attribute?
Also, do I need to give same name for my properties in datacontract (Ex : screenName, text as property name ?)
"abcDetails":[
{
"screenName":"my name",
"text":"tweet desc",
"createdTime":1423494304000,
"entities":{ },
"name":"abc",
"id":"123"
}]
To answer your questions:
You can omit properties that you do not need and DataContractJsonSerializer will skip over them.
You class's property names can differ from the JSON property names as long as you set the DataMemberAttribute.Name value to be the same as the name appearing in the JSON.
Your JSON is invalid, it is missing outer braces. I assume that's just a copy/paste error in your question.
Thus your classes could look like:
[DataContract]
public class Detail
{
[DataMember(Name="text")]
public string Text { get; set; }
[DataMember(Name="createdTime")]
public long CreatedTimeStamp { get; set; }
}
[DataContract]
public class RootObject
{
[DataMember(Name="abcDetails")]
public List<Detail> Details { get; set; }
}
And to test:
string json = #"
{
""abcDetails"":[
{
""screenName"":""my name"",
""text"":""tweet desc"",
""createdTime"":1423494304000,
""entities"":{ },
""name"":""abc"",
""id"":""123""
}]
}
";
var response = DataContractJsonSerializerHelper.GetObject<RootObject>(json);
foreach (var detail in response.Details)
{
Console.WriteLine(string.Format("Created Time: {0}; Text: \"{1}\"", detail.CreatedTimeStamp, detail.Text));
}
produces the output
Created Time: 1423494304000; Text: "tweet desc"
using the helper class:
public static class DataContractJsonSerializerHelper
{
public static T GetObject<T>(string json) where T : class
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
return GetObject<T>(json, serializer);
}
public static T GetObject<T>(string json, DataContractJsonSerializer serializer)
{
using (var stream = GenerateStreamFromString(json))
{
var obj = serializer.ReadObject(stream);
return (T)obj;
}
}
private static MemoryStream GenerateStreamFromString(string value)
{
return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
}
}

Categories

Resources