10

My application accepts long JSON templates from clients that I deserialize and process. I would like to provide better error handling information that contains lineNumber of invalid objects in the JSON text to customers. Note that this is for errors that occur in post-processing and NOT for errors that occur during deserialization as this is already handled by the Newtonsoft.

As an example, I have the below JSON and its corresponding .Net type

{
    "Version": "1.0.0.0",                        
    "MyComplexObject": [
    {
        "Prop1": "Val1",
        "Prop2": "Val2",
        "Prop3": 1
    }
    ]
}

public class MyComplexObject
{
    [JsonProperty]
    public string Prop1 { get; set; }

    [JsonProperty]
    public string Prop2 { get; set; }

    [JsonProperty]
    public int Prop3 { get; set; }

    **public int LineNumber;
    public int LinePosition;**
}

public class RootObject
{
    [JsonProperty]
    public string Version { get; set; }

    [JsonProperty]
    public List<MyComplexObject> MyComplexObject { get; set; }
}

I would like the LineNumber and LinePosition properties to be populated at deserialization so that it may be used at a later time. I am currently deserializing the JSON using the below code

JsonConvert.DeserializeObject<RootObject>(value: rawJson,settings: mysettings);

Appreciate any responses

2
  • You say that this is not for errors that occur during deserialization but then say that you want LineNumber and LinePosition to be populated during deserialization. What errors are you trying to capture exactly? Commented Sep 26, 2014 at 0:54
  • 1
    For instance, if Prop1 in the above example turns out to be a database name but the database doesn't exist(which is a runtime check), I want to be able to throw an exception with message "Prop1 at Line 2 and position 10 is non-existent". This would be of huge help to customers who use large json templates. Commented Sep 26, 2014 at 2:54

2 Answers 2

9

I was able to get around this by implementing a custom converter like below. Any class that implements JsonLineInfo will automatically get the line number info for itself and its properties when it is deserialized.

public class LineInfo
{
    [JsonIgnore]
    public int LineNumber { get; set;}

    [JsonIgnore]
    public int LinePosition { get; set;}        
}

public abstract class JsonLineInfo : LineInfo
{
    [JsonIgnore]
    public Dictionary<string, LineInfo> PropertyLineInfos { get; set; }
}

class LineNumberConverter : JsonConverter
{
    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException("Converter is not writable. Method should not be invoked");
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(JsonLineInfo).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType != JsonToken.Null)
        {
            int lineNumber = 0;
            int linePosition = 0;
            var jsonLineInfo = reader as IJsonLineInfo;
            if (jsonLineInfo != null && jsonLineInfo.HasLineInfo())
            {
                lineNumber = jsonLineInfo.LineNumber;
                linePosition = jsonLineInfo.LinePosition;
            }

            var rawJObject = JObject.Load(reader);
            var lineInfoObject = Activator.CreateInstance(objectType) as JsonLineInfo;
            serializer.Populate(this.CloneReader(reader, rawJObject), lineInfoObject);

            return this.PopulateLineInfo(
                lineInfoObject: lineInfoObject,
                lineNumber: lineNumber,
                linePosition: linePosition,
                rawJObject: rawJObject);
        }

        return null;
    }

    private JsonReader CloneReader(JsonReader reader, JObject jobject)
    {
        var clonedReader = jobject.CreateReader();

        clonedReader.Culture = reader.Culture;
        clonedReader.DateParseHandling = reader.DateParseHandling;
        clonedReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
        clonedReader.FloatParseHandling = reader.FloatParseHandling;
        clonedReader.MaxDepth = reader.MaxDepth;

        return clonedReader;
    }

    private object PopulateLineInfo(JsonLineInfo lineInfoObject, int lineNumber, int linePosition, JObject rawJObject)
    {
        if (lineInfoObject != null)
        {
            lineInfoObject.PropertyLineInfos = new Dictionary<string, LineInfo>(StringComparer.InvariantCultureIgnoreCase);
            lineInfoObject.LineNumber = lineNumber;
            lineInfoObject.LinePosition = linePosition;

            foreach (var property in rawJObject.Properties().CoalesceEnumerable())
            {
                var propertyLineInfo = property as IJsonLineInfo;
                if (propertyLineInfo != null)
                {
                    lineInfoObject.PropertyLineInfos.Add(
                        property.Name,
                        new LineInfo
                        {
                            LineNumber = propertyLineInfo.LineNumber,
                            LinePosition = propertyLineInfo.LinePosition
                        });
                }
            }
        }

        return lineInfoObject;
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

I'm trying to get line number of deserialized object by using Json.Net I tried your method, but i got syntax error about 'CoalesceEnumerable()' method. which namespace must be written in using blog ? Also, can i ask you to give an example for 'ReadJson' method call? Thanks.
@SerkanYılmaz You can try this simple extension: using System.Collections.Generic; public static class IEnumerableExtensions { public static IEnumerable<TSource> CoalesceEnumerable<TSource>(this IEnumerable<TSource> source) { return source == null ? new TSource[0] : source; } }
0

Here's how I handled this to handle error when using converters to get references to data:

        static string GetPosition(JsonReader reader)
        {
            if (reader is JsonTextReader textReader)
                return $"{CurrentFilename}({textReader.LineNumber},{textReader.LinePosition}):";
            else
                return $"{CurrentFilename}({reader.Path}):";
        }

        public class AudioClipConverter : JsonConverter<AudioClip>
        {
            public override void WriteJson(JsonWriter writer, AudioClip value, JsonSerializer serializer)
            {
                writer.WriteValue(value.name);
            }

            public override AudioClip ReadJson(JsonReader reader, Type objectType, AudioClip existingValue, bool hasExistingValue, JsonSerializer serializer)
            {
                var value = (string)reader.Value;
                if (String.IsNullOrEmpty(value))
                    return null;

                var result = Instance.GetAudioClip(value);
                if (result == null)
                    Debug.LogWarning($"{GetPosition(reader)} AudioClip {value} not found in database.");
                return result;
            }
        }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.