question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Improving error message generation

See original GitHub issue

Hi!

I have a (hopefully simple) question about how to properly use this library. Apologies in advance if this is covered in the docs somewhere; I read through a lot of it and much of the code and couldn’t find anything. Consider something like:

public class Holder {
  public Int64 Foo { get; set; }
}

var json = @"{""Foo"": ""a string instead of a number""}";

JsonSerializer.Deserialize<Holder>(json, options);

This throws an exception like "The JSON value could not be converted to Holder. Path: $ | LineNumber: 1 | BytePositionInLine: 32.". While this error message isn’t useless, it could be a lot better. Ideally, it would include the actual offending field name; in this example it doesn’t matter, but in the real world our objects have a lot of (often nested) fields, and the error message will always say Path: $.

I notice that the MemberConverter has access to the member name when it’s calling the converter, so theoretically we could hack something there to add the member name to the message. Would rather avoid hacky stuff like this, however.

What would you suggest? How can we improve our error messages? Are they supposed to be as vague as we’re getting, or is it because we’re doing something wrong?

Thanks!

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
tyler-boydcommented, Sep 4, 2020

For anyone else who has a similar issue, this is something I hacked together last night that will give error messages like path.to.bad.field: Cannot get the value of a token type 'String' as a number.:

// configure your JsonSerializerOptions to use the hacky provider
options.GetObjectMappingConventionRegistry().RegisterProvider(new FriendlyObjectMappingConventionProvider());
public class FriendlyJsonException : JsonException
  {
    private readonly List<string> _context;

    public FriendlyJsonException(string field, Exception cause) : base(null, cause)
    {
      if (cause is FriendlyJsonException ex)
      {
        _context = new List<string>(ex._context);
      }
      else
      {
        _context = new List<string>();
      }

      _context.Insert(0, field);
    }

    public override string Message
    {
      get
      {
        Exception inner = this;
        while (inner.InnerException != null)
        {
          inner = inner.InnerException;
        }

        var path = String.Join(".", _context);
        return $"{path}: {inner.Message}";
      }
    }
  }

  public class FriendlyMemberConverter<T, TM> : IMemberConverter where T : class
  {
    private readonly IMemberConverter _memberConverter;

    public FriendlyMemberConverter(JsonSerializerOptions options, IMemberMapping mapping)
    {
      _memberConverter = new MemberConverter<T, TM>(options, mapping);
    }

    public void Read(ref Utf8JsonReader reader, object obj, JsonSerializerOptions options)
    {
      try
      {
        _memberConverter.Read(ref reader, obj, options);
      }
      catch (Exception ex)
      {
        throw new FriendlyJsonException(_memberConverter.MemberNameAsString, ex);
      }
    }

    public void Write(Utf8JsonWriter writer, object obj, JsonSerializerOptions options)
    {
      try
      {
        _memberConverter.Write(writer, obj, options);
      }
      catch (Exception ex)
      {
        throw new FriendlyJsonException(_memberConverter.MemberNameAsString, ex);
      }
    }

    public object Read(ref Utf8JsonReader reader, JsonSerializerOptions options)
    {
      try
      {
        return _memberConverter.Read(ref reader, options);
      }
      catch (Exception ex)
      {
        throw new FriendlyJsonException(_memberConverter.MemberNameAsString, ex);
      }
    }

    public void Set(object obj, object value, JsonSerializerOptions options)
    {
      _memberConverter.Set(obj, value, options);
    }

    public bool ShouldSerialize(object obj, Type declaredType, JsonSerializerOptions options)
    {
      return _memberConverter.ShouldSerialize(obj, declaredType, options);
    }

    public ReadOnlySpan<byte> MemberName => _memberConverter.MemberName;

    public bool IgnoreIfDefault => _memberConverter.IgnoreIfDefault;

    public string? MemberNameAsString => _memberConverter.MemberNameAsString;

    public RequirementPolicy RequirementPolicy => _memberConverter.RequirementPolicy;
  }

  public class FriendlyMemberMapping<T> : IMemberMapping
  {
    private readonly MemberMapping<T> _memberMapping;
    private readonly JsonSerializerOptions _options;

    public FriendlyMemberMapping(JsonSerializerOptions options,
      IObjectMapping objectMapping, MemberInfo memberInfo, Type memberType)
    {
      _options = options;
      _memberMapping = new MemberMapping<T>(options, objectMapping, memberInfo, memberType);
    }

    public IMemberConverter GenerateMemberConverter()
    {
      var defaultConverter = _memberMapping.GenerateMemberConverter();
      if (typeof(T).IsStruct())
      {
        return defaultConverter;
      }

      var memberConverterType = typeof(FriendlyMemberConverter<,>).MakeGenericType(typeof(T), MemberType);
      var memberConverter =
        (IMemberConverter?)Activator.CreateInstance(memberConverterType, _options, this);

      if (memberConverter == null)
      {
        throw new JsonException($"Cannot instantiate {memberConverterType}");
      }

      return memberConverter;
    }

    public MemberInfo? MemberInfo => _memberMapping.MemberInfo;

    public Type MemberType => _memberMapping.MemberType;

    public string? MemberName => _memberMapping.MemberName;

    public JsonConverter? Converter => _memberMapping.Converter;

    public bool CanBeDeserialized => _memberMapping.CanBeDeserialized;

    public bool CanBeSerialized => _memberMapping.CanBeSerialized;

    public object? DefaultValue => _memberMapping.DefaultValue;

    public bool IgnoreIfDefault => _memberMapping.IgnoreIfDefault;

    public Func<object, bool>? ShouldSerializeMethod => _memberMapping.ShouldSerializeMethod;

    public RequirementPolicy RequirementPolicy => _memberMapping.RequirementPolicy;

    public void Initialize()
    {
      _memberMapping.Initialize();
    }

    public void PostInitialize()
    {
      _memberMapping.PostInitialize();
    }
  }

  public class FriendlyObjectMappingConvention : IObjectMappingConvention
  {
    private readonly DefaultObjectMappingConvention _defaultConvention = new DefaultObjectMappingConvention();

    public void Apply<T>(JsonSerializerOptions options, ObjectMapping<T> objectMapping)
    {
      _defaultConvention.Apply(options, objectMapping);

      objectMapping.SetMemberMappings(objectMapping.MemberMappings.Select(mm =>
        new FriendlyMemberMapping<T>(options, objectMapping, mm.MemberInfo, mm.MemberType)));
    }
  }

  public class FriendlyObjectMappingConventionProvider : IObjectMappingConventionProvider
  {
    public IObjectMappingConvention? GetConvention(Type type)
    {
      return new FriendlyObjectMappingConvention();
    }
  }
0reactions
mcatanzariticommented, Oct 3, 2020

fix in #59

Read more comments on GitHub >

github_iconTop Results From Across the Web

Use AI to improve your error messages
Use AI to improve your error messages ... One of the fun things about working with a giant text-generating model with general internet...
Read more >
Write better error messages
The goal of writing better error messages isn't to help the people who never read error messages, it's to help the people who...
Read more >
Improper Error Handling
Improper handling of errors can introduce a variety of security problems for a web site. The most common problem is when detailed internal...
Read more >
Best Practices To Create Error Codes Pattern For an ...
3 Answers 3 · Use a custom exception type with an additional ErrorCode property. · Do not start at 1 and don't use...
Read more >
2.5 Error Handling and Generation | Mastering Software ...
Creating clear and informative error messages is essential for building quality software. One closing tip I recommend is to put documentation for your...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found