MongoDB ObjectId conversion error in c# using Newtonsoft Deserialization - c#

I have been working to convert MongoDB BSON documents to List object in c#.
While converting, i get below error
"{"Unexpected character encountered while parsing value: O. Path '_id', line 1, position 10."}"
After searching for similar issues in stackoverflow, i found below link
JSON.NET cast error when serializing Mongo ObjectId
and i followed the same.
My Code:
Sample Entity/Model
public class BTMObj
{
[JsonConverter(typeof(MongoDataSerializer))]
public ObjectId _id { get; set; }
public string requestFormat { get; set; }
}
public class MongoDataSerializer : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ObjectId);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.String)
{
throw new Exception(
String.Format("Unexpected token parsing ObjectId. Expected String, got {0}.",
reader.TokenType));
}
var value = (string)reader.Value;
return String.IsNullOrEmpty(value) ? ObjectId.Empty : new ObjectId(value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is ObjectId)
{
var objectId = (ObjectId)value;
writer.WriteValue(objectId != ObjectId.Empty ? objectId.ToString() : String.Empty);
}
else
{
throw new Exception("Expected ObjectId value.");
}
}
}
public List<T> GetMongoCollection<T>(string collectionName)
{
try
{
List<T> list = new List<T>();
var client = new MongoClient(Convert.ToString(ConfigurationManager.AppSettings["MONGO_CONNECTION"]));
var database = client.GetDatabase(Convert.ToString(ConfigurationManager.AppSettings["MONGO_DB"]));
var collection = database.GetCollection<BsonDocument>(collectionName);
var documents = collection.Find(new BsonDocument()).ToList();
foreach (var document in documents)
{
try
{
list.Add(JsonConvert.DeserializeObject<T>(document.ToJson()));
}
catch (Exception ex)
{
}
}
return list;
}
catch (Exception ex)
{
throw;
}
}
Call to Method
list = mongoDBOperations.GetMongoCollection<BTMObj>(Collection);
MongoDataSerializer class overridden methods should get call which is not the case.
Our need is to get ObjectId as string in Model.
Please help in resolving this issue.
Sample document.toJson() value
{
"_id": ObjectId("611cf42e1e4c89336b6fe2f0"),
"requestFormat": "json"
}

You only used half of the relevant code.
If you write this JSON:
"_id": ObjectId("611cf42e1e4c89336b6fe2f0"),
"requestFormat": "json"
}
As BsonObject.ToJson() does, then that's not JSON. The ObjectId(...) dialect, just as Date(), NumberLong(), NumberInt() and NumberDecimal() are constructs that make MongoDB spit out invalid JSON, because it's a representation of its internal BSON storage format.
So if you want to treat it as JSON, write valid JSON. The code is right there in the link: you need to serialize the object yourself. See How to deserialize a BsonDocument object back to class.
First make sure the Mongo C# driver deserializes the BSON into your POCO:
// Prefer using statically-typed extension methods such as
// _collection.FindAs<MyType>()
var deserialized = BsonSerializer.Deserialize<BTMobj>(document);
Then serialize that object to JSON using your converter:
var json = JsonConvert.SerializeObject(deserialized);
Your output will then become the very parseable:
"_id": "611cf42e1e4c89336b6fe2f0",
"requestFormat": "json"
}
And your type's metadata (attributes) will tell the parser to parse "611cf42e1e4c89336b6fe2f0" as a BsonObjectId when you try to deserialize it into a BTMobj again.

Related

Newtonsoft.Json convertion xml document to json with array detection

what I am trying to do is to convert complex xml document to json format, and I am using Newtonsoft.Json to achieve my goal. and I have came across small - big problem. So for example
I have a model that looks like:
public class assets
{
public UInt32 id {get; set;}
public String providerName {get; set;}
public String provider {get; set;}
public String realm {get; set;}
public ICollection<unit> unit {get; set;}
}
My intention is that user will stream xml content to method that will change that xml to json and i will post it to API.
To simplify User is pasting simple xml (normal xml is far more complex, but basically it would looks like many levels of example bellow)
<assets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="assets.xsd" providerName="myProviderName" provider="myProvider" realm="myCatalog">
<unit idKey="newGeo_63119679"></unit>
<unit idKey="newGeo_63119179"></unit>
</assets>
Json result will look like:
{"#providerName":"myProviderName","#provider":"myProvider","#realm":"myCatalog","unit":[{"#idKey":"newGeo_63119679"},{"#idKey":"newGeo_63119577"}]}
So service that does all the magic looks like:
public async ValueTask<string> AddAsset(string body)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(body);
string json = JsonConvert.SerializeXmlNode(doc, Formatting.None, true);
HttpResponseMessage response = await this._httpClient.PostAsJsonAsync("/Asset/create_asset", json);
string responseJson = await response.Content.ReadAsStringAsync();
return responseJson;
}
Well OK this part works, but when I remove from xml one unit node (so only one unit node is left), my result is:
{"#providerName":"myProviderName","#provider":"myProvider","#realm":"myCatalog","unit":{"#idKey":"newGeo_63119679"}}
And now so needed array to deserialize it to model is gone. I know I could manipulate xml attributes to add json:Array='true'.
But I was wondering if there is more complex solution for example JsonConverter that can take search for property in given type and check it if its collection and if so assign it as json collection. How can I bite this problem?
And Also as I checked SerializeXmlNode has no converter parameter.
Well, I found my answer, by mixing information from #dbc and my happy innovation:
public class Converter<TEntity> : JsonConverter<TEntity> where TEntity : class
{
private readonly IEnumerable<Type> _entityTypes =
Assembly.GetExecutingAssembly().GetReferencedAssemblies()
.SelectMany(assembly => Assembly.Load(assembly).GetTypes().Where(t => t.Namespace == MigrationService.EntitiesNameSpace));
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, TEntity value, JsonSerializer serialize) =>
throw new NotImplementedException($"Write option is turned off for {nameof(CollectionConverter)} custom json converter");
public override TEntity ReadJson(JsonReader reader, Type objectType, TEntity existingValue, bool hasExistingValue, JsonSerializer serializer)
{
void SetSimpleTypes(JToken token, TEntity instance, PropertyInfo property)
{
var value = token[property.Name];
if(value == null) return;
var cast = Convert.ChangeType(value, property.PropertyType);
property.SetValue(instance, cast);
}
void SetClassTypes(JToken token, TEntity instance, PropertyInfo property)
{
var constructedConverterType = typeof(CollectionConverter<>).MakeGenericType(property.PropertyType);
if (Activator.CreateInstance(constructedConverterType) is not JsonConverter converter) return;
var propertyInstance = JsonConvert.DeserializeObject(token[property.Name].ToString(), property.PropertyType, converter);
property.SetValue(instance, propertyInstance);
}
void SetCollectionTypes(JToken token, TEntity instance, PropertyInfo property)
{
var constructedCollectionType = typeof(List<>).MakeGenericType(property.PropertyType.GetGenericArguments().Single());
var collectionInstance = Activator.CreateInstance(constructedCollectionType) as IList;
var value = token[property.Name];
void HandleSingleCollectionType(string body)
{
var propertyCollectionType = property.PropertyType.GetGenericArguments().Single();
var constructedConverterType = typeof(CollectionConverter<>).MakeGenericType(propertyCollectionType);
if (Activator.CreateInstance(constructedConverterType) is not JsonConverter converter) return;
var convertedInstance = JsonConvert.DeserializeObject(body, propertyCollectionType, converter);
collectionInstance?.Add(convertedInstance);
}
switch (value)
{
case JArray array:
foreach (var body in array)
{
HandleSingleCollectionType(body.ToString());
}
break;
case JObject:
HandleSingleCollectionType(value.ToString());
break;
default:
Debug.WriteLine($"Unknown or empty json token value for property {property.Name}");
break;
}
property.SetValue(instance, collectionInstance);
}
JToken token = JToken.Load(reader);
var instance = (TEntity)Activator.CreateInstance(typeof(TEntity));
var properties = instance?.GetType().GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);
if (properties == null) return default;
foreach (PropertyInfo property in properties)
{
try
{
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
SetCollectionTypes(token, instance, property);
}
else if (this._entityTypes.Any(type => type == property.PropertyType) && property.PropertyType.IsClass)
{
SetClassTypes(token, instance, property);
}
else
{
SetSimpleTypes(token, instance, property);
}
}
catch (NullReferenceException ex)
{
Debug.WriteLine($"Null value for entity class property {property.Name}, exception: {ex}");
}
catch (FormatException ex)
{
Debug.WriteLine($"Can not convert value property {property.Name} in {instance.GetType().Name}, exception: {ex}");
}
catch (Exception ex)
{
Debug.WriteLine($"Undefined exception for {property.Name} exception: {ex}");
}
}
return instance;
}
}
It converts very complex xml structures based on class structure so if class structure says something is collection it will build collection, if its an object (I mean class) it will create instance of a class and so on. Additionally it uses itself to convert any child xml has.

Json.net deserilize all empty strings as null

I have an endpoint that returns all null values as empty strings, even when the type is completely different. For example, this data
[{
"field1": 3,
"field2": "bob",
"field3": ["alpha", "beta", "gamma"],
"field4": { "some": "data" }
},
{
"field1": "", // needs to be deserialized as null
"field2": "", // same
"field3": "", // ..
"field4": "" // ..
}]
would need to be serialized to (an array of) a model like:
public class Root
{
public int? Field1 { get; set; }
public string Field2 { get; set; }
public string[] Field3 { get; set; }
public JObject Field4 { get; set; }
}
But Json.Net throws an exception:
Unhandled Exception: Newtonsoft.Json.JsonSerializationException: Error setting value to 'Field4' on 'Root'. ---> System.InvalidCastException: Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'Newtonsoft.Json.Linq.JObject'.
I have tried using Contracts, ValueProviders, and Converters with no luck. How could I go about doing this?
None of these links has helped me:
Customize Json.NET serialization to consider empty strings as null
Convert empty strings to null with Json.Net
Json Convert empty string instead of null
EDIT1: Fixed typo.
EDIT2: This is the code for the converter I tried using:
public class VdfNullConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// ...
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String && (reader.Value as string == ""))
return null;
// I don't know what to do here
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
My issue is that I don't know how to handle the case where the data is in fact not an empty string. In that case, I need the other converters to be called, but I have no way to "cancel" halfway through ReadJson.
I finally found a simple, but hacky solution. I used a regular JsonConverter and backtracked using code like this:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String && (reader.Value as string == ""))
return null;
skip = true;
return serializer.Deserialize(reader, objectType);
}
private bool skip = false;
public override bool CanConvert(Type objectType) // If this is ever cached, this hack won't work.
{
if (skip)
{
skip = false;
return false;
}
return true;
}
If your model really consists of just 4 fields you can consider using serialization properties
public class Root{
[JsonIgnore]
public int? Field1 {get;set;}
[JsonProperty("field1")]
protected string Field1Data{
{
get { return Field1?.ToString(); }
set {
if (string.IsNullOrEmpty(value))
Field1 = null;
else {
int parsed;
if (Int32.TryParse(value, out parsed)){
Field1 = parsed;
} else {
throw new ArgumentException($"{value} could not be parsed", nameof(Field1));
}
}
}
}
}
}
If your model has a larger variety of types and fields then you'll have to use a custom json converter.
Either way, the JObject in your sample looks suspicious. It will likely need to be of some other type, such as Object.
EDIT:
Since you have a very large variety of types you may want to consider pre-processing your json before deserializing it, since using converters would require more specificity.
You could parse your input as a JObject, select all the empty string values and delete the properties:
var jobj = JObject.Parse(File.ReadAllText("sample.json"));
var tokens = jobj.SelectTokens("$..[?(#=~/^$/)]").ToList();
tokens.ForEach(t=>t.Parent.Remove()); // you could filter some of the removals if you need to
string nowUseThisAsYourImpup = jobj.ToString();

Unexpected initial token 'EndObject' when populating object exception deserializing abstract class

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.

Deserialize an Anonymous Type From a Collection

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.

Deserialize JSON that might be an integer or a list of strings in C#

I am using C# to deserialize a collection of JSON strings. Each string has a property employer_normalized that is supposed to contain a string and List<int> where the values in the List are always positive. In some cases employer_normalized is set to -1, so I want to override these cases with some behavior that sets employer_normalized to null.
Here is my class:
public class EmployerNormalized
{
public string company;
public List<int> code;
}
Good JSON
"employer_normalized": {
"company": "self",
"code": [
"4581 ",
"6732 ",
"9121",
"9999 ",
"5947 ",
"8322 ",
"8351 ",
"7335 ",
"9999 ",
"4225 ",
"8399 "
]
}
Bad JSON
"employer_normalized": -1
I am currently using Json.NET to do my JSON parsing. What is an elegant solution to solving this problem? Is it best to just set the employer_normalized value to null if it is -1? If so, how can I do this?
You can use a custom JsonConverter to deal with this situation. Wherever you are expecting a EmployerNormalized, the converter can check whether the value of that property is -1 and return null, otherwise deserialize it normally.
Here is the code for the converter:
public class EmployerNormalizedConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(EmployerNormalized));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Object)
{
return token.ToObject<EmployerNormalized>();
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And here is a demo showing how to use it:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""good"" : {
""company"": ""self"",
""code"": [
""4581 "",
""6732 "",
""9121"",
""9999 "",
""5947 "",
""8322 "",
""8351 "",
""7335 "",
""9999 "",
""4225 "",
""8399 ""
]
},
""bad"" : -1
}";
Wrapper wrapper =
JsonConvert.DeserializeObject<Wrapper>(json,
new EmployerNormalizedConverter());
DumpEmployer("good", wrapper.good);
DumpEmployer("bad", wrapper.bad);
}
private static void DumpEmployer(string prop, EmployerNormalized emp)
{
Console.WriteLine(prop);
if (emp != null)
{
Console.WriteLine(" company: " + emp.company);
Console.WriteLine(" codes: " +
string.Join(", ", emp.code.Select(c => c.ToString())));
}
else
Console.WriteLine(" (null)");
}
public class Wrapper
{
public EmployerNormalized good { get; set; }
public EmployerNormalized bad { get; set; }
}
public class EmployerNormalized
{
public string company;
public List<int> code;
}
}
Here is the output:
good
company: self
codes: 4581, 6732, 9121, 9999, 5947, 8322, 8351, 7335, 9999, 4225, 8399
bad
(null)
Important note: You might be tempted to decorate the EmployerNormalized class with [JsonConverter(typeof(EmployerNormalizedConverter))], but if you do that, then the converter, in its current form, will end up calling itself recursively until it errors out with a StackOverflowException. If you need/want to use the attribute, then the code of the ReadJson method in the converter will need to be changed so that it manually creates an instance of the EmployerNormalized class and populates all of its properties individually rather than calling token.ToObject<EmployerNormalized>(). Here is an alternate version of ReadJson that will avoid the recursion problem:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Object)
{
EmployerNormalized employer = new EmployerNormalized();
employer.company = token["company"].ToString();
employer.code = token["code"].ToObject<List<int>>();
return employer;
}
return null;
}
I know this sucks, but you can first try to parse the JSON string to an Object with the structure of your Good JSON, and in case of parsing error, parse to the second case (Bad JSON).

Categories

Resources