2

My program depends on a library that decodes JSON that I provide into its internal data type.

The library tries to encapsulate too much. It hides connection details, data serialization format and the RPC used under the hood so that it's easy to use.

The problem is that the library rejects JSON that I have to deal with in my environment. In other words, it refuses to work for common errors with JSON payloads like uppercase/lowercase differences, unexpected but harmless keys, recoverable Number/String encoding errors.

I cannot rely on newtype trick as the library expects concrete data type to work with - not mine. I need to change the "internal" class instance it uses for deserialization.

Having no better idea I forked the library, added that as a dependency built from local directory (also had to delete everything stack related so that the build would take notice).

The data type was unchanged, only serialization format was altered making this change isolated to problem at hand.

Is there a better way to override class instance definition provided by the library?

sevo
  • 4,559
  • 1
  • 15
  • 31
  • 2
    There's an interesting question here, but it could use to be a lot more concrete. – leftaroundabout Dec 12 '18 at 18:58
  • I intentionally seek answer to the general problem of bundling predefined instances with a library that users might reasonably wish to alter without breaking the universe. – sevo Dec 12 '18 at 19:19
  • Ok, but that still should be phrased out in a manner that allows unambiguous answers, else the question is _too broad_. – leftaroundabout Dec 12 '18 at 19:30
  • 2
    You have described what your problem was, and what you did to correct the problem. Since we cannot see your code, we aren't in a position to comment on any part of that. – Bob Dalgleish Dec 12 '18 at 19:36
  • Possible duplicate of [How do you override Haskell type class instances provided by package code?](https://stackoverflow.com/questions/5643360/how-do-you-override-haskell-type-class-instances-provided-by-package-code) – jkeuhlen Dec 12 '18 at 21:05

1 Answers1

3

I've run into a similar problem a few times, and it's annoying. Some options to consider, if you haven't:

  1. We could hope that the library provides Haskell combinators to build the internal type. Then you could wrap the type and provide an alternative FromJSON instance:

    newtype WrappedT = WrappedT { toT :: Library.T }
        deriving (All, The, Classes, You, Care, About)
    
    instance FromJSON WrappedT where
        -- better instance
    
  2. Pre-normalize the JSON, that is, write a JSON-to-JSON layer that removes the unacceptable bits.

  3. If you don't want to maintain the fork, push a PR to the author exposing the internals in a .Internal module, then you can do solution 1. This will maintain compatibility while still allowing your change. (I also personally think "soft encapsulation" like this is better for the community than the more common "rigid encapsulation")

If you give more details of the problem there might be other solutions that spring to mind.

luqui
  • 59,485
  • 12
  • 145
  • 204
  • First solution is quite powerful solution since I could wrap entire API! Ugly but doable! Now, this made me realize that this is a two-fold design problem with the library because it hides RPC from me. – sevo Dec 13 '18 at 18:50
  • I'm marking this as the accepted answer as there seems to be no "linking phase" instance overriding mechanism in GHC. – sevo Dec 13 '18 at 18:53