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.
Related
I have the following model:
public class UserPtr
{
public int my_var1 { get; set; }
public int my_var2 { get; set; }
public int my_var3 { get; set; }
public int my_var4 { get; set; }
}
And some API response JSON which is:
[
{
"name": "my_var1",
"ptr": 1 // "Value_my_var1"
},
{
"name": "my_var2",
"ptr": 2 // "Value_my_var2"
},
{
"name": "my_var3",
"ptr": 3 // "Value_my_var3"
},
{
"name": "my_var4",
"ptr": 4 // "Value_my_var4"
}
]
I want to set my_var1 = Value_my_var1, my_var2 = Value_my_var2, my_var3 = Value_my_var3
Normally I would use:
JsonConvert.DeserializeObject<UserPtr>(strJson);
But when I do, I get the following exception:
Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'UserPtr' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
How can I deserialize this array of objects containing property names and values into my model?
You would like to serialize your model as an array of objects containing property names and property values, where the names and values come from the "default" JSON serialization for your model. You can do this with a custom generic JsonConverter<T> that translates between the default serialization and the array serialization.
By default, your UserPtr model should be serialized as follows:
{
"my_var1": 1,
"my_var2": 2,
"my_var3": 2,
"my_var4": 4
}
But instead, you are receiving an array of objects containing single name/value pairs as shown in your question, where the names correspond to your model's property names. You would like to bind this array to your model. To accomplish this, you can create a generic converter similar to the one from Deserialize JSON from a Sharepoint 2013 search result into a list of MyClass as follows:
public class NamePtrPropertyArrayConverter<T> : JsonConverter<T> where T : class, new()
{
struct NamePtrDTO
{
public string name;
public object ptr;
}
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
{
var obj = (JObject)JsonExtensions.DefaultFromObject(serializer, value);
serializer.Serialize(writer, obj.Properties().Select(p => new NamePtrDTO { name = p.Name, ptr = p.Value }));
}
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
var array = serializer.Deserialize<List<NamePtrDTO>>(reader);
var obj = new JObject(array.Select(i => new JProperty(i.name, i.ptr)));
existingValue = existingValue ?? (T)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
using (var subReader = obj.CreateReader())
serializer.Populate(subReader, existingValue);
return existingValue;
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
// DefaultFromObject() taken from this answer https://stackoverflow.com/a/29720068/3744182
// By https://stackoverflow.com/users/3744182/dbc
// To https://stackoverflow.com/questions/29719509/json-net-throws-stackoverflowexception-when-using-jsonconvert
public static JToken DefaultFromObject(this JsonSerializer serializer, object value)
{
if (value == null)
return JValue.CreateNull();
var dto = Activator.CreateInstance(typeof(DefaultSerializationDTO<>).MakeGenericType(value.GetType()), value);
var root = JObject.FromObject(dto, serializer);
return root["Value"].RemoveFromLowestPossibleParent() ?? JValue.CreateNull();
}
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
// If the parent is a JProperty, remove that instead of the token itself.
var contained = node.Parent is JProperty ? node.Parent : node;
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (contained is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
interface IHasValue
{
object GetValue();
}
[JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy), IsReference = false)]
class DefaultSerializationDTO<T> : IHasValue
{
public DefaultSerializationDTO(T value) => this.Value = value;
public DefaultSerializationDTO() { }
[JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)]
public T Value { get; set; }
object IHasValue.GetValue() => Value;
}
}
public class NoConverter : JsonConverter
{
// NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
// By https://stackoverflow.com/users/3744182/dbc
// To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
public override bool CanConvert(Type objectType) { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
Then, either deserialize by adding the converter to JsonSerializerSettings.Converters:
var settings = new JsonSerializerSettings
{
Converters = { new NamePtrPropertyArrayConverter<UserPtr>() },
};
var model = JsonConvert.DeserializeObject<UserPtr>(strJson, settings);
Or apply the converter directly to your model as follows:
[JsonConverter(typeof(NamePtrPropertyArrayConverter<UserPtr>))]
public class UserPtr
{
// Contents unchanged
}
Demo fiddle here.
I thought this would pretty easily to convert from JSON array to a comma-separated string and back using Newtonsoft, but I am having issues getting the ReadJson to work. I thought I would just deserialize from the reader to a string array and then call Join, but I keep getting errors: Unexpected token while deserializing object: PropertyName. Path '[0]..
Here is the code I am using:
public class myModel
{
[JsonConverter(typeof(CommaSeperatedStringJsonConverter))]
public string myString { get; set; }
public int myNumber { get; set; }
}
public class CommaSeperatedStringJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var stringArray = serializer.Deserialize(reader, typeof(string[]));
return string.Join(",", stringArray);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is null)
{
writer.WriteNull();
}
else
{
serializer.Serialize(writer, value.ToString().Split(','), typeof(string[]));
}
}
}
Try using the generic version of Deserialize instead. In other words, change this line:
var stringArray = serializer.Deserialize(reader, typeof(string[]));
To this:
var stringArray = serializer.Deserialize<string[]>(reader);
Fiddle: https://dotnetfiddle.net/KpQSiG
It shouldn't matter, but for some reason it does in this situation. If I have more time I'll try to dig into the source code to see what is happening.
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();
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 am using JSON.net and am trying to serialize and deserialize a Distance object from the opensource UnitClassLibrary. Currently, I have an object serialized as the following JSON:
{
"ThirtySecondsOfAnInch": 454,
"SixteenthsOfAnInch": 227,
"EighthsOfAnInch": 113.5,
"QuartersOfAnInch": 56.75,
"HalvesOfAnInch": 28.375,
"Inches": 14.1875,
"Feet": 1.1822916666666667,
"Yards": 0.3940972222222222,
"Miles": 0.00022391887626262627,
"Millimeters": 360.36249999999995,
"Centimeters": 36.03625,
"Meters": 0.3603625,
"Kilometers": 0.0003603625,
"Architectural": "1'2 3/16\""
}
I can take any one of these and turn it into a distance object using this class. For example, using the last architectural string, I could use this constructor:
Distance newDistance = new Distance("1'2 3/16\"");
Or, using the 32nd of a inch, I could do:
Distance newDistance = new Distance(DistanceType.ThirtySecond, 454.0);
However, I am not sure the best way to write a JsonConstructor (the type of constructor that JSON.net can specifically use) to take either type of output upon passing a JSON string with JsonConvert.DeserializeObject<Distance>(json);.
How can I write a constructor to take a new distance object?
Here's an example JsonConverter to get you started.
public class DistanceConverter : JsonConverter
{
private readonly IDictionary<string, DistanceType> _distanceTypeMap;
public DistanceConverter()
{
_distanceTypeMap = new Dictionary<string, DistanceType>
{
{"Meters", DistanceType.Meter},
{"Yards", DistanceType.Yard}
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Distance distance = value as Distance;
if (distance == null)
{
writer.WriteNull();
return;
}
writer.WriteStartObject();
foreach (KeyValuePair<string, DistanceType> pair in _distanceTypeMap)
{
writer.WritePropertyName(pair.Key);
writer.WriteValue(distance.GetValue(pair.Value));
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Distance result = null;
while (reader.Read())
{
var key = reader.Value;
string value = reader.ReadAsString();
if (result == null && key != null)
{
DistanceType distanceType;
if (_distanceTypeMap.TryGetValue(key.ToString(), out distanceType))
{
double parsedValue = JToken.Parse(value).Value<double>();
result = new Distance(distanceType, parsedValue);
}
}
}
return result;
}
public override bool CanConvert(Type objectType)
{
return typeof(Distance) == objectType;
}
}
This has problems at the moment. I couldn't understand why do I get zero distance even if I create the object in deserialization. Here's a few tests to see the results:
Update: I fixed the bug while reading it again. It works at the moment but you may get exceptions when you add other units to the dictionary.
public class DistanceConverterTests
{
private JsonSerializerSettings _jsonSerializerSettings;
[SetUp]
public void Setup()
{
_jsonSerializerSettings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new DistanceConverter() }
};
}
[Test]
public void DeserializeTest()
{
string json = File.ReadAllText("data.json");
var distance = JsonConvert.DeserializeObject<Distance>(json, _jsonSerializerSettings);
distance.Meters.Should().BeInRange(0.360, 0.361);
}
[Test]
public void SerializeTest()
{
Distance distance = new Distance(DistanceType.Meter, 2.20);
string json = JsonConvert.SerializeObject(distance, _jsonSerializerSettings);
Console.WriteLine(json);
}
}