zio-json icon indicating copy to clipboard operation
zio-json copied to clipboard

Come up with a list of integrations for other libraries

Open jdegoes opened this issue 3 years ago • 11 comments

This ticket just exists so we can document the need to come up with a list of third-party library integrations.

jdegoes avatar Sep 24 '20 09:09 jdegoes

@jdegoes one thing that some coworkers would get really excited about would be "compat mode" derivations so that large code bases that have a public api that current "commits" to the encodings that circe chooses, how tractable would an alternative deriver that does "compatible" json encodings a la circe be?

cartazio avatar Feb 19 '21 16:02 cartazio

@cartazio I like the idea. Should be pretty straightforward to do, maybe with an annotation.

jdegoes avatar Feb 21 '21 02:02 jdegoes

@jdegoes if its that simple, could you explain how that'd be done? it would genuinely save me a week or so of craziness getting stuff in shape for colleagues!

cartazio avatar Feb 24 '21 21:02 cartazio

at least for my use case, it look like @jsonDiscriminator("type") on sealed traitsplus @jsonHint("CONSTRUCTORNAMEALLCAPS") is what i'll need to do. based on the comments, theres no way to have a smart config for doing the latter automatically right?

cartazio avatar Feb 26 '21 19:02 cartazio

Here are a few ideas:

  1. You could use the combination of annotations mentioned: jsonDiscriminator to put the tag for sum types in a type field, and jsonHint to change the case of constructors.
  2. You could introduce a new annotation @jsonCirce, which does both for you automatically.
  3. You could incorporate some notion of (maybe scoped?) format hints that are used in the absence of any annotations. I'd have to think about what the best way of doing this would be. It has to play well with the existing annotations and not (unreasonably) suffer from global reasoning requirements.

Another possibility is to just generalize the decoders so they can automatically handle Circe-style format. But that only helps you with decoding. When you want to encode, you still need to make a choice.

jdegoes avatar Feb 27 '21 15:02 jdegoes

So i'm not sure if the particulars of this use case are true of all circe encodings or just the one in this code base. I'm thinking that an annotation like

@JsonMigrated(tagMechanism,nameFunction) annotation where theres some sort of sealed sum type for tagMechanism etc?

so for all the root sealed traits i'd annotate in my work that need to emulate circe to maintain rest API compat, i'd have to do eg @JsonMigrated(DiscriminatorField("type"),toUpperString) or something? and that would inform any deriving magnolia stuff on that root trait or children thereof?

i assume any such annotation would need to be baked into zio-json based on my limited understanding of the code? (eg a patch that augments the current macros annotation file?).

Realistically, for near term, the brute force annotation approach thats currently possible with zio-json is what i'm going to have to do this coming week, but adding the latter machinery, either your style or my alternative "generalized migration helper annotation" style, would certainly be a net good on the world (and be a welcome improvment to cleanup this code with later).

Either way: zio-json has dramatically better build times vs its alternatives, its kinda criminal how bad the others are. and what would be the turn around from a working patch set to a new release that reflects such an improvement that my colleagues could enjoy?

cartazio avatar Feb 28 '21 04:02 cartazio

what would be the turn around from a working patch set to a new release that reflects such an improvement that my colleagues could enjoy?

If you can contribute it, we can cut a new 0.x release within a few hours.

fsvehla avatar Feb 28 '21 16:02 fsvehla

Cool! I will need some feedback on whatever patch I figure out to make sure it’s good enough. But this sounds fantastic. Since I have no clue what I’m doing. :)

does the fuzzy migration annotation idea /apimake sense as a starting point? This also presupposes that magnolia code can look up annotations up the sealed trait hierarchy?

cartazio avatar Feb 28 '21 16:02 cartazio

well, in lieu of that, i'm doing the following for now :)) perl -i -pe "s/^(?<PREFIX>[ ]*)case[ ]+class[ ]+(?<NAME>\w+)(?<REST>(.*))\$/\$+{PREFIX}\@jsonHint\(\\\"\U\$+{NAME}\E\\\"\) case class \$+{NAME}\$+{REST}/g" MYPROJECT/**.scala in my shell

cartazio avatar Mar 01 '21 18:03 cartazio

hrm, this sadly, wont even suffice in my case :(. cause i need to convert constructors from Caml case to snake case and then map the names to upper :(, the smarter annoation may be the sanest appraoch

cartazio avatar Mar 01 '21 20:03 cartazio

a more concrete spec for what a "fully fleshed out" option might be perhaps is seen in https://hackage.haskell.org/package/aeson-1.5.6.0/docs/Data-Aeson.html#g:16

for more details of this configurability see the below paste

-- | Options that specify how to encode\/decode your datatype to\/from JSON.
--
-- Options can be set using record syntax on 'defaultOptions' with the fields
-- below.
data Options = Options
    { fieldLabelModifier :: String -> String
      -- ^ Function applied to field labels.
      -- Handy for removing common record prefixes for example.
    , constructorTagModifier :: String -> String
      -- ^ Function applied to constructor tags which could be handy
      -- for lower-casing them for example.
    , allNullaryToStringTag :: Bool
      -- ^ If 'True' the constructors of a datatype, with /all/
      -- nullary constructors, will be encoded to just a string with
      -- the constructor tag. If 'False' the encoding will always
      -- follow the `sumEncoding`.
    , omitNothingFields :: Bool
      -- ^ If 'True', record fields with a 'Nothing' value will be
      -- omitted from the resulting object. If 'False', the resulting
      -- object will include those fields mapping to @null@.
      --
      -- Note that this /does not/ affect parsing: 'Maybe' fields are
      -- optional regardless of the value of 'omitNothingFields', subject
      -- to the note below.
      --
      -- === Note
      --
      -- Setting 'omitNothingFields' to 'True' only affects fields which are of
      -- type 'Maybe' /uniformly/ in the 'ToJSON' instance.
      -- In particular, if the type of a field is declared as a type variable, it
      -- will not be omitted from the JSON object, unless the field is
      -- specialized upfront in the instance.
      --
      -- The same holds for 'Maybe' fields being optional in the 'FromJSON' instance.
      --
      -- ==== __Example__
      --
      -- The generic instance for the following type @Fruit@ depends on whether
      -- the instance head is @Fruit a@ or @Fruit (Maybe a)@.
      --
      -- @
      -- data Fruit a = Fruit
      --   { apples :: a  -- A field whose type is a type variable.
      --   , oranges :: 'Maybe' Int
      --   } deriving 'Generic'
      --
      -- -- apples required, oranges optional
      -- -- Even if 'Data.Aeson.fromJSON' is then specialized to (Fruit ('Maybe' a)).
      -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit a)
      --
      -- -- apples optional, oranges optional
      -- -- In this instance, the field apples is uniformly of type ('Maybe' a).
      -- instance 'Data.Aeson.FromJSON' a => 'Data.Aeson.FromJSON' (Fruit ('Maybe' a))
      --
      -- options :: 'Options'
      -- options = 'defaultOptions' { 'omitNothingFields' = 'True' }
      --
      -- -- apples always present in the output, oranges is omitted if 'Nothing'
      -- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit a) where
      --   'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options
      --
      -- -- both apples and oranges are omitted if 'Nothing'
      -- instance 'Data.Aeson.ToJSON' a => 'Data.Aeson.ToJSON' (Fruit ('Maybe' a)) where
      --   'Data.Aeson.toJSON' = 'Data.Aeson.genericToJSON' options
      -- @
    , sumEncoding :: SumEncoding
      -- ^ Specifies how to encode constructors of a sum datatype.
    , unwrapUnaryRecords :: Bool
      -- ^ Hide the field name when a record constructor has only one
      -- field, like a newtype.
    , tagSingleConstructors :: Bool
      -- ^ Encode types with a single constructor as sums,
      -- so that `allNullaryToStringTag` and `sumEncoding` apply.
    , rejectUnknownFields :: Bool
      -- ^ Applies only to 'Data.Aeson.FromJSON' instances. If a field appears in
      -- the parsed object map, but does not appear in the target object, parsing
      -- will fail, with an error message indicating which fields were unknown.
    }

cartazio avatar Mar 02 '21 18:03 cartazio