I'm trying to write a generic method that will take a JSON string and convert it to a DataTable (the best it can).
It works fine until it comes across a JSON object that has an array as one of the properties.
What I would like to do, is ignore the properties that are arrays and convert as best it can.
I have an example fiddle that explains what I am trying to do:
https://dotnetfiddle.net/cgkNov
public static void Main()
{
const string JsonObject = #"[{Id:""672af604-495b-4dc0-933e-6c55f56bef82"",""Name"":""1"",""MyArray"":[]}]";
const string EndResultJsonObject = #"[{Id:""672af604-495b-4dc0-933e-6c55f56bef82"",""Name"":""1""}]";
var settings = new JsonSerializerSettings
{
Error = (obj, args) =>
{
var context = args.ErrorContext;
context.Handled = true;
}
};
var a = JsonConvert.DeserializeObject<DataTable>(JsonObject, settings);
Console.WriteLine(JsonConvert.SerializeObject(a));
a = JsonConvert.DeserializeObject<DataTable>(EndResultJsonObject, settings);
Console.WriteLine(JsonConvert.SerializeObject(a));
}
Thanks,
Alex
If you want to skip array, you can use something like this. It is just an example, and probably you should improve it.
Add custom converter:
public class SkipArrayConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return !objectType.IsArray;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = JArray.Load(reader);
DataTable dataTable = new DataTable();
int i = 0;
foreach (JToken token in array.Children())
{
var dataRow = dataTable.NewRow();
if (token.Type != JTokenType.Object) continue;
if (i == 0)
{
SetColumns(dataTable, token);
dataRow = dataTable.NewRow();
i++;
}
foreach (JToken jToken in token)
{
if (((JProperty)jToken).Value.Type != JTokenType.Array)
{
string name = ((JProperty)jToken).Name;
object value = ((JValue)((JProperty)jToken).Value).Value;
dataRow[name] = value;
}
}
dataTable.Rows.Add(dataRow);
}
return dataTable;
}
private void SetColumns(DataTable dt, JToken token)
{
foreach (JToken jToken in token)
{
if (((JProperty)jToken).Value.Type != JTokenType.Array)
{
string name = ((JProperty)jToken).Name;
object value = ((JValue)((JProperty)jToken).Value).Value;
Type valueType = value.GetType();
dt.Columns.Add(name, valueType);
}
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
After this you can use it:
JsonSerializerSettings settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new SkipArrayConverter() }
};
const string JsonObject = #"[{Id:""672af604-495b-4dc0-933e-6c55f56bef82"",Name:""1"",MyArray:[{}]}]";
const string EndResultJsonObject = #"[{Id:""672af604-495b-4dc0-933e-6c55f56bef82"",""Name"":""1""}]";
var a = JsonConvert.DeserializeObject<DataTable>(JsonObject, settings);
Console.WriteLine(JsonConvert.SerializeObject(a));
a = JsonConvert.DeserializeObject<DataTable>(EndResultJsonObject, settings);
Console.WriteLine(JsonConvert.SerializeObject(a));
I noticed how you are using double " to wrap fields' keys and values. In your current sample code for JsonObject you are not wrapping the empty array [] in quotes. I tried changing it to
const string JsonObject = #"[{Id:""672af604-495b-4dc0-933e-6c55f56bef82"",""Name"":""1"",""MyArray"":""[]""}]";
That solves the issue with your current data atleast. Since you're using a Third party library to do the actual parsing of JSON, you have limited control over the process.
Maybe what you can try is creating a minified version of DataTable class and then use this new version to parse the JSON string into an object of the new MiniDT class. Let me know if you need some help with that.
Cheers.
use this
const string JsonObject = #"[{Id:""672af604-495b-4dc0-933e-6c55f56bef82"",""Name"":""1"",""MyArray"":[{}]}]";
Array should be declared like this
MyArray:[{}]
Alex,
I found this answer made by Brian Rogers: Removing empty array members from a JSON string
I think this is what you where searching for. Try it out and if it isn't then we can create a code using it as an inspiration.
Related
Can I make these code to one method with generics?
I want return string or string array because my Json file is like below.
So I want to do like this.
public static string langFilePath = #"..\..\..\Core\Data\Lang.json";
public static JObject lang = JObject.Parse(File.ReadAllText(langFilePath));
public static string[] GetJsonValue(string key)
{
string[] values = lang[key].ToObject<string[]>();
return values;
}
But value of key can not be string array.
It can also be string.
Rather than returning one of two possible return types, it's probably simpler to just coerce a single string to an array containing that string. Assuming you don't control the Json, you probably want to write a custom JsonConverter. Here's my quick and dirty stab at the problem:
public class LangEntryConverter: JsonConverter<string[]>
{
// WriteJson implementation only needed if you need to serialize a value back to Json
public override void WriteJson(JsonWriter writer, string[] value, JsonSerializer serializer)
{
if (value.Length == 1)
{
writer.WriteValue(value[0]);
}
else
{
writer.WriteStartArray();
for (var i = 0; i < value.Length; i++)
{
writer.WriteValue(value[i]);
}
writer.WriteEndArray();
}
}
public override string[] ReadJson(JsonReader reader, Type objectType, string[] existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var values = new List<string>();
if (reader.TokenType == JsonToken.StartArray)
{
while (reader.Read() && reader.TokenType != JsonToken.EndArray)
{
if (reader.TokenType == JsonToken.String)
{
values.Add((string)reader.Value);
}
else
{
throw new Exception($"Unexpected token type: {reader.TokenType}");
}
}
}
else if (reader.TokenType == JsonToken.String)
{
values.Add((string)reader.Value);
}
return values.ToArray();
}
}
And then call it like so:
// Note: double-quotations are required for C#'s verbatim string syntax; they are not part of the Json
var json = #"{
""foo"": ""one"",
""bar"": [""one"", ""two"", ""three""]
}";
var lang = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(json, new LangEntryConverter());
Console.WriteLine(String.Join(", ", lang["foo"])); // one
Console.WriteLine(String.Join(", ", lang["bar"])); // one, two, three
Of course, in your particular situation, this may require some tweaking.
Use Generic Syntax:
class Program<T>
{
public static string langFilePath = #"..\..\..\Core\Data\Lang.json";
public static JObject lang = JObject.Parse(File.ReadAllText(langFilePath));
public static T GetJsonValue(string key)
{
T values = lang[key].ToObject<T>();
return values;
}
}
I wrote a custom JSON converter class in json.net and cannot figure out why I am getting the following exception when I deserialize with it.
Unexpected initial token 'EndObject' when populating object. Expected JSON object or array. Path '', line 1, position 177.
I have other converters in my project which are modeled very similar which work without issue so I am unsure why this is being such a problem.
Here is the object being serialized:
[JsonConverter(typeof(CreateCRMIntegrationPromptJsonConverter))]
public abstract class CreateCRMIntegrationDirectPromptBaseBindingModel
{
public bool IncludeInOutput { get; set; }
public string Label { get; set; }
public string Value { get; set; }
public IValidateCRMField Validator { get; set; }
public string ValidatorType { get; set; }
public CRMIntegrationDirectPromptType Type { get; set; }
}
public class CreateCRMIntegrationPromptMobilePhoneBindingModel : CreateCRMIntegrationDirectPromptBaseBindingModel
{
public bool FormatPhoneNumber { get; set; }
}
And the converter
public class CreateCRMIntegrationPromptJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Models.CreateCRMIntegrationDirectPromptBaseBindingModel);
}
public override bool CanWrite { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
JObject jo = JObject.Load(reader);
JsonReader jsonReader = jo.CreateReader();
Dictionary<string, string> values = new Dictionary<string, string>(jo.ToObject<IDictionary<string, string>>(), StringComparer.CurrentCultureIgnoreCase);
var typeValue = values["type"].ToString();
Models.CRMIntegrationDirectPromptType integrationPromptType = Models.CRMIntegrationDirectPromptType.Label;
if (!Enum.TryParse(typeValue, out integrationPromptType))
{
integrationPromptType = Models.CRMIntegrationDirectPromptType.Label;
}
switch (integrationPromptType)
{
.........
case Models.CRMIntegrationDirectPromptType.MobilePhone:
Models.CreateCRMIntegrationPromptMobilePhoneBindingModel cRMIntegrationPromptMobilePhoneReturnModel = new Models.CreateCRMIntegrationPromptMobilePhoneBindingModel();
serializer.Populate(reader, cRMIntegrationPromptMobilePhoneReturnModel);
return cRMIntegrationPromptMobilePhoneReturnModel;
.........
}
}
catch(Exception ex)
{
Models.CreateCRMIntegrationPromptLabelBindingModel cRMIntegrationPromptLabelReturnModelDefault = new Models.CreateCRMIntegrationPromptLabelBindingModel();
cRMIntegrationPromptLabelReturnModelDefault.IncludeInOutput = false;
cRMIntegrationPromptLabelReturnModelDefault.Label = string.Empty;
cRMIntegrationPromptLabelReturnModelDefault.Type = Models.CRMIntegrationDirectPromptType.Label;
return cRMIntegrationPromptLabelReturnModelDefault;
}
}
}
When I test with this code I can catch the exception
var obj = new CreateCRMIntegrationPromptMobilePhoneBindingModel();
obj.IncludeInOutput = true;
obj.FormatPhoneNumber = true;
obj.Label = "Test";
obj.ValidatorType = "Answer1APILib.CRMIntegration.ValidateCRMField_NonRequired";
obj.Type = CRMIntegrationDirectPromptType.Label;
obj.Value = "";
var test = JsonConvert.SerializeObject(obj);
var output = JsonConvert.DeserializeObject<CreateCRMIntegrationDirectPromptBaseBindingModel>(test);
Here is the JSON returned by the serialization
{
"FormatPhoneNumber":true,
"IncludeInOutput":true,
"Label":"Test",
"Value":"",
"Validator":null,
"ValidatorType":"Answer1APILib.CRMIntegration.ValidateCRMField_NonRequired",
"Type":0
}
You need to pass the jsonReader to serializer.Populate() rather than the incoming reader. Or eliminate the variable jsonReader entirely and pass in jo.CreateReader():
serializer.Populate(jo.CreateReader(), cRMIntegrationPromptMobilePhoneReturnModel);
You need to do this because you previously loaded the object at the initial location of the incoming JsonReader reader into JObject jo:
JObject jo = JObject.Load(reader);
Thus the incoming reader has been advanced past the object and on to whatever comes next. Using the reader a second time to populate you model will advance the reader further still, eventually causing the Unexpected initial token 'EndObject' you are seeing.
You might also want to check to see whether the incoming JSON token is null before loading it as an object:
if (reader.TokenType == JsonToken.Null)
return null;
JObject jo = JObject.Load(reader);
Since a null value in JSON file is actually loaded as a non-null JValue with JValue.Type equal to JTokenType.Null, attempting to load such a token as a JObject will fail.
(Finally, I'm not sure I would handle exceptions in ReadJson() itself. Newtonsoft already has a mechanism for handling exceptions and if you catch and swallow all exceptions inside ReadJson() rather than using the mechanism there's a chance you could fall into an infinite loop when reading a malformed, truncated JSON file. This is not the primary cause of the problem you are seeing though.)
Thus a fixed version of ReadJson() would look like:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var jo = JObject.Load(reader);
var typeValue = (string)jo.GetValue("Type", StringComparison.OrdinalIgnoreCase);
Models.CRMIntegrationDirectPromptType integrationPromptType;
if (!Enum.TryParse(typeValue, out integrationPromptType))
{
integrationPromptType = Models.CRMIntegrationDirectPromptType.Label;
}
Models.CreateCRMIntegrationDirectPromptBaseBindingModel model;
switch (integrationPromptType)
{
case Models.CRMIntegrationDirectPromptType.MobilePhone:
model = new Models.CreateCRMIntegrationPromptMobilePhoneBindingModel();
break;
case Models.CRMIntegrationDirectPromptType.Label:
model = new Models.CreateCRMIntegrationPromptLabelBindingModel();
break;
// Add other cases as required.
default:
throw new JsonSerializationException(typeValue);
}
serializer.Populate(jo.CreateReader(), model);
return model;
}
Working sample .Net fiddle here.
I've run into a confusing problem that i cannot seem to solve.
I'm using Json.Net and i've written a custom Json converter to handle a special case in my application.
The issue i've run into is in the deserialize or ReadJson method which is throwing an error when it tires to convert a JSON array into an array of strings:
The exact text of the error is: Unexpected token while deserializing object: PropertyName. Path 'RootPages', line 1, position 13.
As you can see from the inspector, the JProperty it is trying to deserialize (RootPages) has been parsed correctly and is valid JSON.
So i'm not entirely sure what is going on here, any enlightenment would be greatly appreciated..
If relevant, the original JSON string is as follows:
{
"RootPages": [
"TestItem1",
"TestItem2"
],
"Name": "root"
}
EDIT-CODE:
ReadJson Method:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
if (PartialChildPageSerialization) {
var jsonObject = JObject.Load(reader);
var properties = jsonObject.Properties().ToList();
foreach (var property in objectType.GetProperties()) {
var type = property.PropertyType;
object value = null;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ChildPageCollection<>)) {
var collection = Activator.CreateInstance(type);
var deserializedValue = properties.First(p => p.Name == property.Name).ToObject<string[]>();
type.GetMethod("PolulateFromSerializer").Invoke(collection, new object[] {deserializedValue});
value = collection;
}
else {
value = properties.First(p => p.Name == property.Name).ToObject(type);
}
property.SetValue(existingValue, value);
}
return existingValue;
}
return serializer.Deserialize(reader, objectType);
}
Snippet of the interesting part of ChildPageCollection:
public class ChildPageCollection<T> : IList<T> where T : DataPage
{
public string[] GetNames() => _internalNameList.ToArray();
internal void PolulateFromSerializer(string[] names) {
this.Clear();
_internalNameList.AddRange(names);
_hasFullList = false;
}
private void CheckFullList() {
if(!_hasFullList)
throw new InvalidOperationException("Collection has not been fully loaded, and full list is not avialable.");
}
private readonly List<T> _internalList = new List<T>();
private readonly List<string> _internalNameList = new List<string>();
private bool _hasFullList = true;
...
}
I expect this is because the first property is an object with a property called 'RootPages' which is a string[].
Unfortunately from the looks of your screenshots you are trying to turn an object into a string array.
This should work in the example you've given:
properties.First(p => p.Name == property.Name).Select(o => o.Children().Values<string>()).First().ToArray();
In place of:
properties.First(p => p.Name == property.Name).ToObject<string[]>();
I have my serialized JSON in this format:
string json = #"[{"Name": "std_id","Value": "111"}, {"Name": "cust_id","Value": "444"}]"
How do I deserialize it to a single anonymous object like this:
var paramObj = new {"std_id" = 111, "cust_id" = 444}
Since you said the values of the Name and Value properties in your JSON objects can vary, you will not be able to deserialize to an anonymous object. Anonymous types are defined at compile-time, which means you need to know the property names ahead of time to be able to define them. The only way to get around that is code generation, which I think is going to be overkill for this situation. Instead, I would suggest you deserialize into a JObject with a dynamic variable. This will get you pretty close to what you want. Here's how:
string json = #"[
{ ""Name"": ""std_id"", ""Value"": ""111"" },
{ ""Name"": ""cust_id"", ""Value"": ""444"" }
]";
dynamic obj = new JObject(JArray.Parse(json)
.Select(t => new JProperty((string)t["Name"], t["Value"])));
From there, you can access the properties like you would for an anonymous type (assuming you know what they are):
Console.WriteLine(obj.std_id);
Console.WriteLine(obj.cust_id);
If you don't know what the properties are, you can enumerate them like a dictionary:
foreach (var prop in obj)
{
Console.WriteLine(prop.Name + ": " + prop.Value);
}
Fiddle: https://dotnetfiddle.net/MRY2ny
Why anonymous object? you should deserialize to a type like below
public class RootObject
{
public string Name { get; set; }
public string Value { get; set; }
}
Then what you actually have is IEnumerable<RootObjct>. You can use use Linq and select First() from it like
RootObject = RootObjects.FirstOrDefault()
You could deserialize it into a dynamic. Like this:
var serializer = new JavaScriptSerializer();
var deserializedResult = serializer.Deserialize<dynamic>(json);
Reference:
JavaScriptSerializer Class
var definition = new { Name = "" };
string json1 = #"{'Name':'James'}";
var customer1 = JsonConvert.DeserializeAnonymousType(json1, definition);
Console.WriteLine(customer1.Name);
string json2 = #"{'Name':'Mike'}";
var customer2 = JsonConvert.DeserializeAnonymousType(json2, definition);
Console.WriteLine(customer2.Name);
source http://www.newtonsoft.com/json/help/html/DeserializeAnonymousType.htm
I know that this solution isn't perfect, but it works for the example provided by you and returns result that looks like paramObj in your example.
The idea is to create a custom Json converter.
First, let's create a DTO class to present a name-value item of incomming JSON.
public class NameValueJsonItem
{
public string Name { get; set; }
public string Value { get; set; }
}
Converter implementation:
public class DynamicJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token == null || token.Type != JTokenType.Array)
{
return null;
}
List<NameValueJsonItem> parsedJson = token.ToObject<List<NameValueJsonItem>>();
ExpandoObject result = new ExpandoObject();
foreach (NameValueJsonItem item in parsedJson)
{
if (!String.IsNullOrEmpty(item.Name))
{
(result as IDictionary<string, object>)[item.Name] = item.Value;
}
}
return result;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Sure, you can make it more safe by adding some exceptions handling etc inside the method if you want.
You can use this converter like that:
dynamic result = JsonConvert.DeserializeObject<dynamic>(json, new DynamicJsonConverter());
Hope it will help.
I have a touch of a predicament. I have a collection of System.Security.Claims.Claim and the constructor of Claim requires the value parameter to contain a string. The value of a particular claim needs to be a JArray that contains JObject and I can easily enough walk my structure during creation and build what I need to build, but I can't figure out how to instruct the Serializer to keep its hands off of that particular Claim.
string content = await response.Content.ReadAsStringAsync();
JObject json = JObject.Parse(content);
List<JObject> foos = new List<JObject>();
var foosComingFromSomewhereElseWithADifferentStructure = json["foos"];
bool first = true;
foreach (var _foo in foosComingFromSomewhereElseWithADifferentStructure.Children<JObject>())
{
JObject foo = new JObject();
string bar = (string)_foo["quux"].Value<JToken>();
string baz = (string)_foo["inga"].Value<JToken>();
foo.Add("Baz", baz);
foo.Add("Bar", bar);
foos.Add(foo);
}
claims.Add(new Claim("FooBar", (string)JsonConvert.SerializeObject(foos)));
After the block of code above runs, I continue processing and I then need to pass the whole enchilada through a Json Serializer ( my serialization essentially needs to be performed in multiple passes)
So if I were to now need to pass the list of claims through yet another JSON.NET JsonConverter, how would I best declare to the Serializer to keep its hands off of FooBar because its value has already been prepared as JSON?
Right now, my serializer is spitting out:
{
"FooBar": "[{\"Baz"\":\"inga\",\"Bar\":\"quux\"}]
}
when I need it to be spitting out
{
"FooBar": [{"Baz":"inga","Bar":"quux"}]
}
To do this kind of thing, you'd need to make a custom JsonConverter. The converter could detect whether the claim value is JSON or not, and if so, use a JRaw to insert the already prepared value into the rest of the JSON body without being escaped or changed. On the flip side, if the converter received an object instead of a string for the claim value (which it would if the value was unescaped JSON), then it would have to re-serialize that object to JSON before putting into the claim object.
Something like this might work:
class ClaimConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(System.Security.Claims.Claim));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var claim = (System.Security.Claims.Claim)value;
JObject jo = new JObject();
jo.Add("Type", claim.Type);
jo.Add("Value", IsJson(claim.Value) ? new JRaw(claim.Value) : new JValue(claim.Value));
jo.Add("ValueType", claim.ValueType);
jo.Add("Issuer", claim.Issuer);
jo.Add("OriginalIssuer", claim.OriginalIssuer);
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string type = (string)jo["Type"];
JToken token = jo["Value"];
string value = token.Type == JTokenType.String ? (string)token : token.ToString(Formatting.None);
string valueType = (string)jo["ValueType"];
string issuer = (string)jo["Issuer"];
string originalIssuer = (string)jo["OriginalIssuer"];
return new Claim(type, value, valueType, issuer, originalIssuer);
}
private bool IsJson(string val)
{
return (val != null &&
(val.StartsWith("[") && val.EndsWith("]")) ||
(val.StartsWith("{") && val.EndsWith("}")));
}
}
Demo:
class Program
{
static void Main(string[] args)
{
var someJson = #"
[
{ ""quux"": ""abc"", ""inga"": ""def"" },
{ ""quux"": ""pqr"", ""inga"": ""stu"" }
]";
JArray foosComingFromSomewhereElse = JArray.Parse(someJson);
List<JObject> foos = new List<JObject>();
foreach (var _foo in foosComingFromSomewhereElse.Children<JObject>())
{
JObject foo = new JObject();
string bar = (string)_foo["quux"].Value<JToken>();
string baz = (string)_foo["inga"].Value<JToken>();
foo.Add("Baz", baz);
foo.Add("Bar", bar);
foos.Add(foo);
}
Console.WriteLine("--- Original FooBar JSON ---");
string foobarJson = JsonConvert.SerializeObject(foos);
Console.WriteLine(foobarJson);
Console.WriteLine();
List<Claim> claims = new List<Claim>();
claims.Add(new Claim("FooBar", foobarJson));
JsonSerializerSettings settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new ClaimConverter() },
Formatting = Formatting.Indented
};
Console.WriteLine("--- Serialized list of claims ---");
string claimsJson = JsonConvert.SerializeObject(claims, settings);
Console.WriteLine(claimsJson);
Console.WriteLine();
Console.WriteLine("--- Deserialized claim values ---");
claims = JsonConvert.DeserializeObject<List<Claim>>(claimsJson, settings);
foreach(Claim claim in claims)
Console.WriteLine(claim.Value);
}
}
Output:
--- Original FooBar JSON ---
[{"Baz":"def","Bar":"abc"},{"Baz":"stu","Bar":"pqr"}]
--- Serialized list of claims ---
[
{
"Type": "FooBar",
"Value": [{"Baz":"def","Bar":"abc"},{"Baz":"stu","Bar":"pqr"}],
"ValueType": "http://www.w3.org/2001/XMLSchema#string",
"Issuer": "LOCAL AUTHORITY",
"OriginalIssuer": "LOCAL AUTHORITY"
}
]
--- Deserialized claim values ---
[{"Baz":"def","Bar":"abc"},{"Baz":"stu","Bar":"pqr"}]