Способ первый самый простой
Когда нужны все данные из json.
public class Data
{
public string m { get; set; }
/* куча проперти */
}
var data = JsonConvert.DeserializeObject<Dictionary<string, Data>>(json)
Почему тип Dictionary<string, Data>, т.к. у вас именно JObject идет первым (как я это определил ниже), то все элементы на его верхнем уровне считаются JProperty. У каждого есть имя и значение, где в качестве значения может быть друга вложенная структура данных.
В нашем случае словарь по сути это массив этих самых JProperty, где key=имя проперти и value=значение.
Как я опередил что это JObject. Первый уровень не имеет ни одного JProperty и начинается с фигурных скобок. Пример ниже будет уже считаться JArray. Такой формат библиотека newtonsoft считает массивом.
{
[{
"m": "StatTrak™ AK-47 | Blue Laminate (Field-Tested)",
"c": 1
},
{
"m": "StatTrak™ P250 | Valence (Field-Tested)",
"c": 1
}]
}
Разбор в словарь также подходит для случаев, когда ваш json имеет похожую структуру, но поля разные. Например как здесь.
[
{
"filterType": "PRICE_FILTER",
"minPrice":"0.01000000",
"maxPrice":"10000000.00000000",
"tickSize":"0.01000000"
},
{
"filterType":"LOT_SIZE",
"minQty":"0.00001000",
"maxQty":"10000000.00000000",
"stepSize":"0.00001000"
},
{
"filterType":"MIN_NOTIONAL",
"minNotional":"10.00000000"
}
]
В таком случае вам не нужно будет создавать один класс со всеми этими полями.
var data = JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(json)
В случае если все таки решитесь на отдельный класс, то все проперти, которые не будут найдены в json останутся инициализированы по дефолту.
Второй вариант, но мудренее
var data = JObject.Parse(a)
.SelectTokens("$.*")
.Select(o => o.ToObject<Data>())
.ToArray();
Переменная data будет типом Data[], но значения 1,2 будут потеряны.
Этот вариант применим, когда исходный json очень большой и имеет сложную структуру, вам не нужны все данные из него, то делать структуру 1 в 1 неразумно. В этом случае можно вытащить с помощью xpath нужные элементы. Xpath это путь до элемета/ов. Работает по такому же принципу, как select запрос в бд. С его помощью можно указать путь и условия по которым вы хотите собрать данные.
- С помощью этого сайта можно практиковать xpath запросы для json структуру
- А с помощью этого можно получать структуры, которые будет соответствовать вашему json.
- Здесь можно почитать о всех фишках библиотеки Newtonsoft
I need to convert JSON data that I get from a REST API and convert them to CSV for some analytic. The problem is that the JSON data do not necessarily follow the same content, so I can’t define a type for mapping. This has become a challenge that is taking too much of my time. I have already created some code, but of course it is not working as it throws exception on this line
var data = JsonConvert.DeserializeObject<List<object>>(jsonData);
The error is:
Additional information: Cannot deserialize the current JSON object
(e.g. {«name»:»value»}) into type
‘System.Collections.Generic.List`1[System.Object]’ because the type
requires a JSON array (e.g. [1,2,3]) to deserialize correctly.To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path ‘data’, line 2, position 10.
please let me know what I can do to get this going.
A sample of data would be like this, the fields of data can change very often, for example a new field can be added the next day, so I don’t have the liberty to create a .Net class to map the data.
{
"data": [
{
"ID": "5367ab140026875f70677ab277501bfa",
"name": "Happiness Initiatives - Flow of Communication/Process & Efficiency",
"objCode": "PROJ",
"percentComplete": 100.0,
"plannedCompletionDate": "2014-08-22T17:00:00:000-0400",
"plannedStartDate": "2014-05-05T09:00:00:000-0400",
"priority": 1,
"projectedCompletionDate": "2014-12-05T08:10:21:555-0500",
"status": "CPL"
},
{
"ID": "555f452900c8b845238716dd033cf71b",
"name": "UX Personalization Think Tank and Product Strategy",
"objCode": "PROJ",
"percentComplete": 0.0,
"plannedCompletionDate": "2015-12-01T09:00:00:000-0500",
"plannedStartDate": "2015-05-22T09:00:00:000-0400",
"priority": 1,
"projectedCompletionDate": "2016-01-04T09:00:00:000-0500",
"status": "APR"
},
{
"ID": "528b92020051ab208aef09a4740b1fe9",
"name": "SCL Health System - full Sitecore implementation (Task groups with SOW totals in Planned hours - do not bill time here)",
"objCode": "PROJ",
"percentComplete": 100.0,
"plannedCompletionDate": "2016-04-08T17:00:00:000-0400",
"plannedStartDate": "2013-11-04T09:00:00:000-0500",
"priority": 1,
"projectedCompletionDate": "2013-12-12T22:30:00:000-0500",
"status": "CPL"
}
]
}
namespace BusinessLogic
{
public class JsonToCsv
{
public string ToCsv(string jsonData, string datasetName)
{
var data = JsonConvert.DeserializeObject<List<object>>(jsonData);
DataTable table = ToDataTable(data);
StringBuilder result = new StringBuilder();
for (int i = 0; i < table.Columns.Count; i++)
{
result.Append(table.Columns[i].ColumnName);
result.Append(i == table.Columns.Count - 1 ? "n" : ",");
}
foreach (DataRow row in table.Rows)
{
for (int i = 0; i < table.Columns.Count; i++)
{
result.Append(row[i].ToString());
result.Append(i == table.Columns.Count - 1 ? "n" : ",");
}
}
return result.ToString().TrimEnd(new char[] {'r', 'n'});
}
private DataTable ToDataTable<T>( IList<T> data )
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
for (int i = 0 ; i < props.Count ; i++)
{
PropertyDescriptor prop = props[i];
table.Columns.Add(prop.Name, prop.PropertyType);
}
object[] values = new object[props.Count];
foreach (T item in data)
{
for (int i = 0 ; i < values.Length ; i++)
{
values[i] = props[i].GetValue(item);
}
table.Rows.Add(values);
}
return table;
}
}
}
I need to convert JSON data that I get from a REST API and convert them to CSV for some analytic. The problem is that the JSON data do not necessarily follow the same content, so I can’t define a type for mapping. This has become a challenge that is taking too much of my time. I have already created some code, but of course it is not working as it throws exception on this line
var data = JsonConvert.DeserializeObject<List<object>>(jsonData);
The error is:
Additional information: Cannot deserialize the current JSON object
(e.g. {«name»:»value»}) into type
‘System.Collections.Generic.List`1[System.Object]’ because the type
requires a JSON array (e.g. [1,2,3]) to deserialize correctly.To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path ‘data’, line 2, position 10.
please let me know what I can do to get this going.
A sample of data would be like this, the fields of data can change very often, for example a new field can be added the next day, so I don’t have the liberty to create a .Net class to map the data.
{
"data": [
{
"ID": "5367ab140026875f70677ab277501bfa",
"name": "Happiness Initiatives - Flow of Communication/Process & Efficiency",
"objCode": "PROJ",
"percentComplete": 100.0,
"plannedCompletionDate": "2014-08-22T17:00:00:000-0400",
"plannedStartDate": "2014-05-05T09:00:00:000-0400",
"priority": 1,
"projectedCompletionDate": "2014-12-05T08:10:21:555-0500",
"status": "CPL"
},
{
"ID": "555f452900c8b845238716dd033cf71b",
"name": "UX Personalization Think Tank and Product Strategy",
"objCode": "PROJ",
"percentComplete": 0.0,
"plannedCompletionDate": "2015-12-01T09:00:00:000-0500",
"plannedStartDate": "2015-05-22T09:00:00:000-0400",
"priority": 1,
"projectedCompletionDate": "2016-01-04T09:00:00:000-0500",
"status": "APR"
},
{
"ID": "528b92020051ab208aef09a4740b1fe9",
"name": "SCL Health System - full Sitecore implementation (Task groups with SOW totals in Planned hours - do not bill time here)",
"objCode": "PROJ",
"percentComplete": 100.0,
"plannedCompletionDate": "2016-04-08T17:00:00:000-0400",
"plannedStartDate": "2013-11-04T09:00:00:000-0500",
"priority": 1,
"projectedCompletionDate": "2013-12-12T22:30:00:000-0500",
"status": "CPL"
}
]
}
namespace BusinessLogic
{
public class JsonToCsv
{
public string ToCsv(string jsonData, string datasetName)
{
var data = JsonConvert.DeserializeObject<List<object>>(jsonData);
DataTable table = ToDataTable(data);
StringBuilder result = new StringBuilder();
for (int i = 0; i < table.Columns.Count; i++)
{
result.Append(table.Columns[i].ColumnName);
result.Append(i == table.Columns.Count - 1 ? "n" : ",");
}
foreach (DataRow row in table.Rows)
{
for (int i = 0; i < table.Columns.Count; i++)
{
result.Append(row[i].ToString());
result.Append(i == table.Columns.Count - 1 ? "n" : ",");
}
}
return result.ToString().TrimEnd(new char[] {'r', 'n'});
}
private DataTable ToDataTable<T>( IList<T> data )
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
for (int i = 0 ; i < props.Count ; i++)
{
PropertyDescriptor prop = props[i];
table.Columns.Add(prop.Name, prop.PropertyType);
}
object[] values = new object[props.Count];
foreach (T item in data)
{
for (int i = 0 ; i < values.Length ; i++)
{
values[i] = props[i].GetValue(item);
}
table.Rows.Add(values);
}
return table;
}
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
[ { "id": 7542826, "title": "Адрес 140", "address": "Адрес 140", "longitude": 05.961548, "latitude": 04.742206, "kkms": [ { "id": 65231138, "orgId": 3122, "retailPlaceId": 7128126123, "internalName": "000423532061244", "model": "ZR2", "serialNumber": "0364589429", "onlineStatus": 1, "status": 4, "createdAt": 1580367599792, "declineReason": "", "fnsKkmId": "15975313354061244", "regStatus": "REGISTRATION_SUCCESS", "regStatusChangedAt": 165465370307890, "fiscalDrive": { "producer": "", "number": "54165453465469161", "activationDate": 1580367540000 }, "lastShift": { "kkmId": "5275257275061244", "updateDate": 1752752752396335, "shiftNumber": 187243, "sells": 8895.61, "returns": 0, "lastTransaction": 1597252577410360000, "shiftClosed": false, "fiscalDriveReplaceRequired": false, "fiscalDriveMemoryExceeded": false, "fiscalDriveExhausted": false, "fiscalDocumentsCount": 47, "lastTransactionType": "TICKET" } }, { "id": 657765130, "orgId": 3122, "retailPlaceId": 7128126123, "internalName": "000433185565000601", "model": "ZR2", "serialNumber": "08388284512361", "onlineStatus": 0, "status": 4, "createdAt": 1584534534534507, "declineReason": "", "fnsKkmId": "0004534534534530601", "regStatus": "REGISTRATION_SUCCESS", "regStatusChangedAt": 15453453453890, "fiscalDrive": { "producer": "", "number": "45354345354345566357", "activationDate": 4534534534560000 }, "lastShift": { "kkmId": "45354345345300601", "updateDate": 1596094816137, "shiftNumber": 1611, "sells": 0, "returns": 0, "lastTransaction": 1597252577410360000, "shiftClosed": false, "fiscalDriveReplaceRequired": false, "fiscalDriveMemoryExceeded": false, "fiscalDriveExhausted": false, "fiscalDocumentsCount": 0, "lastTransactionType": "OPEN_SHIFT" } } ], "kkmsOnlineCount": 2 } ] |
Hello,
I am trying to get the following JSON response into a list so I can reference on value and get another :
{
"Result": [
{
"Key": "19e848a2-cbb6-4c72-8e24-05ce47da7754",
"Name": "Bistro"
},
{
"Key": "d274591a-06fd-4d50-a973-5a629a3a6d3a",
"Name": "Sports Bar"
},
{
"Key": "b648abd8-da53-4089-a7f9-5ef7ddece38c",
"Name": "Constable Suite"
},
{
"Key": "69a4b8b9-427d-4cca-b62a-62e6cbc8a27c",
"Name": "Haywain Suite"
},
{
"Key": "142a6bc3-86ac-4953-a022-8641f318ffa0",
"Name": "Hotel Lounge Main Bar"
},
{
"Key": "6294ae4e-273b-408a-8e5f-df5853badf90",
"Name": "Garden Room"
},
{
"Key": "f0516ab6-9bce-4f35-aa40-f8f49c464023",
"Name": "Gallery Grill"
}
],
"RID": "1",
"CloudDateTime": "2020-07-04T08:16:36.0000000Z",
"Success": true,
"Message": ""
}
and I have defined my classes as
public class Dept
{
public Result _Result { get; set; }
public string _RID { get; set; }
public DateTime _CloudDateTime { get; set; }
public bool _Success { get; set; }
public string _Message { get; set; }
}
public class Result
{
public string _Key { get; set; }
public string _Name { get; set; }
}
but, when i use the following to grab the data :
List<Dept> Departments = JsonConvert.DeserializeObject<List<Dept>>(response.Content);
foreach (var item in Departments)
{
var _Key = item._Result._Key;
var _Name = item._Result._Name;
richTextBox5.AppendText(_Key + " - " + _Name + Environment.NewLine);
}
I get the message
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[myprogram.Form1+Dept]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'Result', line 1, position 10.
if I change public Results _Result {get; set;} to public List<Result> _Result { get; set; }
I then get swiggles un der _Key and _Name
var _Key = item._Result._Key;
var _Name = item._Result._Name;
saying «does not contain a definition for «_Key» ….»
How do I get eh response into a list so I can then reference the name to the key ie :
if(_Name == "Bistro")
{
var deptKey = theCorrespondingDeptKeyFromList
MessageBox.show(_Name + " " + deptKey)
}
thanks 🙂

Photo by Annie Spratt
Spring Boot projects primarily use the JSON library Jackson to serialize and deserialize objects. It is especially useful that Jackson automatically serializes objects returned from REST APIs and deserializes complex type parameters like @RequestBody.
In a Spring Boot project the automatically registered MappingJackson2HttpMessageConverter is usually enough and makes JSON conversions simple, but this may have some issues which need custom configuration. Let’s go over a few good practices for them.
Configuring a Custom Jackson ObjectMapper
In Spring REST projects a custom implementation of MappingJackson2HttpMessageConverter helps to create the custom ObjectMapper, as seen below. Whatever custom implementation you need to add to the custom ObjectMapper can be handled by this custom converter:
public class CustomHttpMessageConverter extends MappingJackson2HttpMessageConverter {
private ObjectMapper initCustomObjectMapper() {
ObjectMapper customObjectMapper = new ObjectMapper();
return customObjectMapper;
}
// ...
}
Additionally, some MappingJackson2HttpMessageConverter methods, such as writeInternal, can be useful to override in certain cases. I’ll give a few examples in this article.
In Spring Boot you also need to register a custom MappingJackson2HttpMessageConverter like below:
@Bean
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new CustomHttpMessageConverter();
}
Serialization
Pretty-printing
Pretty-printing in Jackson is disabled by default. By enabling SerializationFeature.INDENT_OUTPUT in the ObjectMapper configuration pretty-print output is enabled (as in the example below). Normally a custom ObjectMapper is not necessary for setting the pretty-print configuration. In some cases, however, like one case of mine in a recent customer project, this configuration might be necessary.
For example, passing a URL parameter can enable pretty-printing. In this case having a custom ObjectMapper with pretty-print enabled and keeping the default ObjectMapper of MappingJackson2HttpMessageConverter as is could be a better option.
public class CustomHttpMessageConverter extends MappingJackson2HttpMessageConverter {
private ObjectMapper initiatePrettyObjectMapper() {
ObjectMapper customObjectMapper = new ObjectMapper();
customObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// additional indentation for arrays
DefaultPrettyPrinter pp = new DefaultPrettyPrinter();
pp.indentArraysWith(new DefaultIndenter());
customObjectMapper.setDefaultPrettyPrinter(pp);
return customObjectMapper;
}
}
Conditionally Filtering the Fields
When serializing a response object you may need to include or ignore one or more fields depending on their values. Let’s assume a model class UserResponse like below.
Notice that we used @JsonIgnore which is completely discarding the annotated field from serialization. Conditional filtering is different and it can be done using SimpleBeanPropertyFilter objects set to the filter provider of the ObjectMapper objects. Also notice that @JsonFilter annotation is used for UserResponse which points to which filter will be used by ObjectMapper during the serialization.
@JsonFilter("userCodeFilter")
public class UserResponse {
public Integer userId;
public String username;
public Integer code;
@JsonIgnore
public String status;
}
Here we add a filter called userCodeFilter—like the one we added to the custom ObjectMapper of CustomHttpMessageConverter—which will include the UserResponse class’s code field in the serialization if its value is greater than 0. You can add multiple filters to ObjectMapper for different models.
public class CustomHttpMessageConverter extends MappingJackson2HttpMessageConverter {
private ObjectMapper initiatePrettyObjectMapper() {
ObjectMapper customObjectMapper = new ObjectMapper();
customObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// additional indentation for arrays
DefaultPrettyPrinter pp = new DefaultPrettyPrinter();
pp.indentArraysWith(new DefaultIndenter());
customObjectMapper.setDefaultPrettyPrinter(pp);
PropertyFilter userCodeFilter = new SimpleBeanPropertyFilter() {
@Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
throws Exception {
if (include(writer)) {
if (!writer.getName().equals("code")) {
writer.serializeAsField(pojo, jgen, provider);
return;
}
int intValue = ((UserResponse) pojo).code;
if (intValue > 0) {
writer.serializeAsField(pojo, jgen, provider);
}
} else if (!jgen.canOmitFields()) {
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
@Override
protected boolean include(BeanPropertyWriter writer) {
return true;
}
@Override
protected boolean include(PropertyWriter writer) {
return true;
}
};
FilterProvider filters = new SimpleFilterProvider().addFilter("userCodeFilter", userCodeFilter);
customObjectMapper.setFilterProvider(filters);
return customObjectMapper;
}
}
Deserialization
JSON String Parse Error Handling in Spring Boot
This one is a little tricky. Deserialization of a JSON @RequestParam object can cause parsing errors if the JSON object is not well-formed. The errors thrown in Jackson’s deserialization level just before it’s pushed to Spring Boot occur at that level, so Spring Boot doesn’t catch these errors.
Deserialization of Jackson maps JSON to POJOs and finally returns the expected Java class object. If the JSON is not well-formed, parsing cannot be done and MappingJackson2HttpMessageConverter internally throws a parsing error. Since this exception is not caught by Spring Boot and no object is returned, the REST controller would be unresponsive, having a badly-formed JSON payload.
Here we can override the internal read method of MappingJackson2HttpMessageConverter, hack the ReadJavaType with a customReadJavaType method, and make it return an internal error when the deserialization fails to parse the JSON input, rather than throwing an exception which is not seen or handled by Spring Boot.
@Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
objectMapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
JavaType javaType = getJavaType(type, contextClass);
return customReadJavaType(javaType, inputMessage);
}
private Object customReadJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
try {
if (inputMessage instanceof MappingJacksonInputMessage) {
Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
if (deserializationView != null) {
return this.objectMapper.readerWithView(deserializationView).forType(javaType).
readValue(inputMessage.getBody());
}
}
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
}
catch (InvalidDefinitionException ex) {
//throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
return "Type definition error";
}
catch (JsonProcessingException ex) {
//throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
return "JSON parse error";
}
}
This way you can return errors occurring at the deserialization level to Spring Boot, which expects a deserialized object but gets a String value which can be caught and translated into a ControllerAdvice handled exception. This also makes it easier to catch JSON parsing errors without using any third party JSON libraries like Gson.
json
rest
java
frameworks
spring
Json.NET supports error handling during serialization and deserialization. Error handling lets
you catch an error and choose whether to handle it and continue with serialization or let the error
bubble up and be thrown in your application.
Error handling is defined through two methods:
the Error event on JsonSerializer and the OnErrorAttribute.
Error Event
The Error event is an event handler found on JsonSerializer. The error event is raised whenever an
exception is thrown while serializing or deserialing JSON. Like all settings found on JsonSerializer
it can also be set on JsonSerializerSettings and passed to the serialization methods on JsonConvert.
List<string> errors = new List<string>();
List<DateTime> c = JsonConvert.DeserializeObject<List<DateTime>>(@"[
""2009-09-09T00:00:00Z"",
""I am not a date and will error!"",
[
1
],
""1977-02-20T00:00:00Z"",
null,
""2000-12-01T00:00:00Z""
]",
new JsonSerializerSettings
{
Error = delegate(object sender, ErrorEventArgs args)
{
errors.Add(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
},
Converters = { new IsoDateTimeConverter() }
});
// 2009-09-09T00:00:00Z
// 1977-02-20T00:00:00Z
// 2000-12-01T00:00:00Z
// The string was not recognized as a valid DateTime. There is a unknown word starting at index 0.
// Unexpected token parsing date. Expected String, got StartArray.
// Cannot convert null value to System.DateTime.
In this example we are deserializing a JSON array to a collection of DateTimes. On the JsonSerializerSettings
a handler has been assigned to the Error event which will log the error message and mark the error as handled.
The result of deserializing the JSON is three successfully deserialized dates and three error messages:
one for the badly formatted string, «I am not a date and will error!», one for the nested JSON array and one
for the null value since the list doesn’t allow nullable DateTimes. The event handler has logged these messages
and Json.NET has continued on deserializing the JSON because the errors were marked as handled.
One thing to note with error handling in Json.NET is that an unhandled error will bubble up and raise the event
on each of its parents, e.g. an unhandled error when serializing a collection of objects will be raised twice,
once against the object and then again on the collection. This will let you handle an error either where it
occurred or on one of its parents.
JsonSerializer serializer = new JsonSerializer();
serializer.Error += delegate(object sender, ErrorEventArgs args)
{
// only log an error once
if (args.CurrentObject == args.ErrorContext.OriginalObject)
errors.Add(args.ErrorContext.Error.Message);
};
If you aren’t immediately handling an error and only want to perform an action against it once then
you can check to see whether the ErrorEventArg’s CurrentObject is equal to the OriginalObject.
OriginalObject is the object that threw the error and CurrentObject is the object that the event is being raised
against. They will only equal the first time the event is raised against the OriginalObject.
OnErrorAttribute
The OnErrorAttribute works much like the other .NET serialization attributes that Json.NET supports.
To use it you simply place the attribute on a method which takes the correct parameters: a StreamingContext and a ErrorContext.
The name of the method doesn’t matter.
public class PersonError
{
private List<string> _roles;
public string Name { get; set; }
public int Age { get; set; }
public List<string> Roles
{
get
{
if (_roles == null)
throw new Exception("Roles not loaded!");
return _roles;
}
set { _roles = value; }
}
public string Title { get; set; }
[OnError]
internal void OnError(StreamingContext context, ErrorContext errorContext)
{
errorContext.Handled = true;
}
}
In this example accessing the the Roles property will throw an exception when no roles have
been set. The HandleError method will set the error when serializing Roles as handled and allow Json.NET to continue
serializing the class.
PersonError person = new PersonError
{
Name = "George Michael Bluth",
Age = 16,
Roles = null,
Title = "Mister Manager"
};
string json = JsonConvert.SerializeObject(person, Formatting.Indented);
Console.WriteLine(json);
//{
// "Name": "George Michael Bluth",
// "Age": 16,
// "Title": "Mister Manager"
//}
One error during deserialization can cause the whole process to fail. Consider the following JSON. The second object has invalid data (can’t convert string to int), which will result in deserialization failing:
Code language: JSON / JSON with Comments (json)
[ { "Color":"Red", "Grams":70 }, { "Color":"Green", "Grams":"invalid" } ]
With Newtonsoft, you can choose to ignore deserialization errors. To do that, pass in an error handling lambda in the settings:
Code language: C# (cs)
using Newtonsoft.Json; var apples = JsonConvert.DeserializeObject<List<Apple>>(applesJson, new JsonSerializerSettings() { Error = (sender, error) => error.ErrorContext.Handled = true }); Console.WriteLine($"How many good apples? {apples?.Count()}");
This outputs the following:
Code language: plaintext (plaintext)
How many good apples? 1
All deserialization errors are ignored, and objects with errors are excluded from the results. In other words, the “bad apples” are removed from the bunch.
System.Text.Json doesn’t have this functionality
System.Text.Json currently doesn’t have the ability to ignore all errors. If you need this functionality right now, I suggest using Newtonsoft. Otherwise you’d have to write a custom converter to attempt to do this.
It should be noted that there is a GitHub issue about adding this functionality to System.Text.Json, so it’s possible they’ll add it in the future.
What happens when an object has an error
When an object has a deserialization error:
- It’s excluded from the results.
- Its parent object is excluded from the results (and so on, recursively, all the way up to the root object).
- Arrays containing the object aren’t excluded.
This functionality is useful when deserializing arrays of objects, because objects in an array are independent of each other, allowing you to filter out objects with errors while preserving the rest.
You can also use this to suppress exceptions when deserializing a single JSON object (in case you can’t or don’t want to wrap the deserialization call in a try/catch).
Note: Malformed JSON may result in it returning a null, even for arrays. It depends on where the malformation is located. So do a null check on the results.
Example – Child object with an error
Consider the following array with one Coder object. The Coder object has a Project object with an error in it (id is null). This will result in a deserialization error.
Code language: JSON / JSON with Comments (json)
[ { "Id":1, "Project":{"Id":null, "Language":"C#"} } ]
Deserialize and ignore errors:
Code language: C# (cs)
var coders = JsonConvert.DeserializeObject<List<Coder>>(codersJson, new JsonSerializerSettings() { Error = (sender, error) => error.ErrorContext.Handled = true }); Console.WriteLine($"How many coders? {coders?.Count()}");
This outputs the following:
Code language: plaintext (plaintext)
How many coders? 0
It returned an empty array. Errors cause objects to be excluded recursively. Hence, the child object (Code.Project) caused the parent object (Coder) to be excluded.
Example – Error object in an array
The second Movie object in the following array of movies will fail deserialization:
Code language: JSON / JSON with Comments (json)
[ { "Title": "Terminator 2: Judgment Day", "Year": 1991 }, { "Title": "Jurassic Park", "Year": "invalid" } ]
Deserialize and ignore errors:
Code language: C# (cs)
var movies = JsonConvert.DeserializeObject<List<Movie>>(moviesJson, new JsonSerializerSettings() { Error = (sender, error) => error.ErrorContext.Handled = true }); foreach (var movie in movies ?? Enumerable.Empty<Movie>()) { Console.WriteLine($"{movie.Title} {movie.Year}"); }
This outputs the following:
Code language: plaintext (plaintext)
Terminator 2: Judgment Day was released in 1991
The second Movie object had a deserialization error and it was excluded. This shows that objects in an array are independent, and only objects with errors are excluded from the results.
Example – Malformed JSON returns a null
Sometimes malformed JSON results in it returning a null. For example, consider the following malformed JSON with a # just sitting there:
Code language: JSON / JSON with Comments (json)
[ { # "Id":1, "Project":{"Id":1, "Language":"C#"} } ]
Now deserialize this while ignoring errors and check if the result is null:
Code language: C# (cs)
var coders = JsonConvert.DeserializeObject<List<Coder>>(codersJson, new JsonSerializerSettings() { Error = (sender, error) => error.ErrorContext.Handled = true }); Console.WriteLine($"Coders is null? {coders is null}");
This outputs the following:
Code language: plaintext (plaintext)
Coders is null? True
In this case, malformed JSON was detected while deserializing one of the objects in the array, and it affected the whole array and returned a null. Always null check the result.
Reporting errors
Besides being able to ignore errors, you can also use the error handler to collect errors for reporting. You can report the errors to the user, or log them, or return them in an API call. The error information is available in the ErrorContext object.
Here’s an example of reporting the errors to the user by writing them out to the console. First, take a look at the following JSON array. Both objects have errors.
Code language: JSON / JSON with Comments (json)
[ { "Id":1, "Project":{"Id":null, "Language":"C#"} }, { "Id":"invalid", "Project":{"Id":1, "Language":"C#"} }, ]
Deserialize, collecting the error information in a list, and then writing it out to the console at the end:
Code language: C# (cs)
var errors = new List<string>(); var coders = JsonConvert.DeserializeObject<List<Coder>>(codersJson, new JsonSerializerSettings() { Error = (sender, error) => var errors = new List<string>(); var coders = JsonConvert.DeserializeObject<List<Coder>>(codersJson, new JsonSerializerSettings() { Error = (sender, error) => { errors.Add(error.ErrorContext.Error.Message); error.ErrorContext.Handled = true; } }); foreach (var error in errors) { Console.WriteLine(error); Console.WriteLine(); }
This will output the two errors:
Code language: plaintext (plaintext)
Error converting vError converting value {null} to type 'System.Int32'. Path '[0].Project.Id', line 4, position 26. Could not convert string to integer: invalid. Path '[1].Id', line 7, position 20.