Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scalar types (Int, Long, Float, Double, Boolean) as map keys #315

Open
OndrejSpanel opened this issue Nov 11, 2020 · 3 comments
Open

Scalar types (Int, Long, Float, Double, Boolean) as map keys #315

OndrejSpanel opened this issue Nov 11, 2020 · 3 comments

Comments

@OndrejSpanel
Copy link
Contributor

OndrejSpanel commented Nov 11, 2020

Scalar types cannot be used as map keys when serializing to JSON, which I find inconvenient (I think Circe allows this). Quoting them as strings should be trivial. If you are not opposed to the idea, I can try preparing a PR.

@sirthias
Copy link
Owner

hmm... yes, I think there is no real risk in allowing for non-string, simple types as map keys, which get converted to their default toString results at the lowest-possible level.
The only change required would be in the JsonRenderer (plus a few extra test cases).

Sure, I'd be happy to merge your PR here, thank you!

@OndrejSpanel
Copy link
Contributor Author

If anyone is interested, here is user space workaround (for Json only, Cbor does not need it):

https://gist.github.com/OndrejSpanel/bcae5a077fb7a72f232e12e828027aac

Core is:

  trait MapKeyCodec[K] {
    def keyToString(k: K): String
    def keyFromString(s: String): K
  }

  implicit def mapEncoder[K: MapKeyCodec: Encoder, T: Encoder]: Encoder[Map[K, T]] = Encoder { (w, value) =>
    if (w.writingCbor) { // any key type is fine on Cbor
      Encoder.forMap[K, T, Map].write(w, value)
    } else { // on Json we need string keys only
      val k = implicitly[MapKeyCodec[K]]
      val valueMap = value.map(kv => k.keyToString(kv._1) -> kv._2)
      Encoder.forMap[String, T, Map].write(w, valueMap)
    }
  }
  implicit def mapDecoder[K: MapKeyCodec : Ordering : Decoder, T: Decoder]: Decoder[Map[K, T]] = Decoder { r =>
    if (r.readingCbor) {
      Decoder.forMap[K, T].read(r)
    } else {
      val k = implicitly[MapKeyCodec[K]]
      val stringMap = Decoder.forMap[String, T].read(r)
      stringMap.map(kv => k.keyFromString(kv._1) -> kv._2)
    }
  }

@sirthias
Copy link
Owner

sirthias commented Apr 8, 2024

Thank you for this snippet, @OndrejSpanel, this is definitely a workable solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants