GithubHelp home page GithubHelp logo

Comments (16)

dtolnay avatar dtolnay commented on August 15, 2024 1

Seems like a bug in Value. I filed #77 to look into it.

from json.

ArtemGr avatar ArtemGr commented on August 15, 2024

I stumbled into the "key must be a string" error while using the newtype pattern (https://aturon.github.io/features/types/newtype.html).

pub struct Kind (pub String);
BTreeMap<Kind, Json>

Also I often use the small string optimization (around https://github.com/fitzgen/inlinable_string) to avoid the heap allocations. I gather Serde won't let me do BTreeMap<InlString, Json> because it thinks that InlString is not a string.

from json.

oli-obk avatar oli-obk commented on August 15, 2024

@ArtemGr it shouldn't be a problem, you just need to make sure that your Serialize impl calls serialize_str. InlString is probably serialized as a struct.

Edit: oh InlinableString doesn't implement Serialize at all... Well then you obviously can't use it. You can make a PR to the crate to conditionally add serde, or you can use the serialize_with attribute.

wrt newtypes, I'm not sure if it's a good idea to make the default serialization work in this case. You created a newtype out of a reason (type safety), so I'm not sure if serde should break that as the default. But I'm open to be convinced otherwise ;)

from json.

ArtemGr avatar ArtemGr commented on August 15, 2024

Oh, thanks for the hint @oli-obk. I thought the problem is the string check but it looks like it's the lack of newtype support.

I sure would like for Serde to support the newtype pattern, maybe with some kind of new tag.

from json.

oli-obk avatar oli-obk commented on August 15, 2024

serde does support newtypes. It's just a question of whether it would be a good default to serialize newtypes exactly like their inner type. Right now that's not the default.

from json.

ArtemGr avatar ArtemGr commented on August 15, 2024

Yeah, what I mean is the transparent newtype support.

When we write BTreeMap<String, u32> we add type information that can't be represented in JSON: that the value is an unsigned integer and only 32 bits long. That's all well and good, because Rust is a more statically typed language than JavaScript and because serializing the type information is costly and unnecessary.

But with newtype pattern it suddenly works differently, Serde wants to actually write down in JSON that struct Foo (String) is a Foo string, not just any string.

whether it would be a good default

I guess defaulting transparent newtype support would be a breaking change. It would be much easier to just stick a new tag on it.

from json.

oli-obk avatar oli-obk commented on August 15, 2024

It would be much easier to just stick a new tag on it.

What do you mean by that? You can already add #[serde(serialize_with(...))] and you can implement Serialize manually.

from json.

ArtemGr avatar ArtemGr commented on August 15, 2024

What do you mean by that?

This: "to serialize newtypes exactly like their inner type".

You can already add #[serde(serialize_with(...))] and you can implement Serialize manually.

That's what I'm trying to avoid.

from json.

oli-obk avatar oli-obk commented on August 15, 2024

I don't understand where you want to add a new tag to prevent a breaking change (since you obviously dislike serialize_with, I don't see any other places where you can add some annotation). Can you give a hypothetical code example of what you want?

from json.

ArtemGr avatar ArtemGr commented on August 15, 2024
#[derive(Serialize, Deserialize)]
#[serde(transparent_newtype)]
pub struct Foo (pub String)

#[derive(Serialize, Deserialize)]
#[serde(transparent_newtype)]
pub struct Bar (pub BTreeMap<Foo, i32>)

or

#[derive(Serialize, Deserialize)]
#[serde(transparent_newtype)]
pub struct Foo (pub InlString)

#[derive(Serialize, Deserialize)]
#[serde(transparent_newtype)]
pub struct Bar (pub BTreeMap<Foo, i32>)

Where InlString should be treated as a string because it implements

impl serde::Deserialize for InlString {
  fn deserialize<D: serde::Deserializer> (deserializer: &mut D) -> Result<InlString, D::Error> {
    struct InlStrVisitor; impl serde::de::Visitor for InlStrVisitor {type Value = InlString;
      fn visit_str<E: serde::de::Error> (&mut self, s: &str) -> Result<InlString, E> {
        Ok (InlString (InlinableString::from (s)))}}
    deserializer.deserialize (InlStrVisitor)}}
impl serde::Serialize for InlString {
  fn serialize<S: serde::Serializer> (&self, serializer: &mut S) -> Result<(), S::Error> {
    serializer.serialize_str (self.0.as_ref())}}

What doesn't work currently:

a) pub struct Bar (pub BTreeMap<Foo, i32>) puts the map inside a JSON array, e.g. "[{"foo": 123}]". For newtype pattern to work transparently it must serialize to inner type, "{"foo": 123}".

b) Using pub struct Foo (pub String) as the map key gives the "key must be a string" error.

from json.

erickt avatar erickt commented on August 15, 2024

I'd be fine with newtype structs being serialized without serializing the wrapper struct, that's why I added support for it. Frankly I thought I already implemented this for JSON. This would need a major version bump though since it'd change the serialization format.

from json.

dtolnay avatar dtolnay commented on August 15, 2024

a) pub struct Bar (pub BTreeMap<Foo, i32>) puts the map inside a JSON array, e.g. [{"foo": 123}]. For newtype pattern to work transparently it must serialize to inner type, {"foo": 123}.

That behavior would be very surprising to me and I was not able to reproduce it. Can you share your code? Here is mine.

#![feature(custom_derive, plugin)]
#![plugin(serde_macros)]

use std::collections::btree_map::BTreeMap;

extern crate serde_json;

pub type Foo = String;

#[derive(Serialize, Deserialize)]
pub struct Bar(pub BTreeMap<Foo, i32>);

fn main() {
    let mut b = Bar(BTreeMap::new());
    b.0.insert("foo".to_string(), 123);

    // prints {"foo":123}
    println!("{}", serde_json::to_string(&b).unwrap());
}

from json.

dtolnay avatar dtolnay commented on August 15, 2024

b) Using pub struct Foo (pub String) as the map key gives the "key must be a string" error.

Ditto Erick, I would have expected this to work already. This is a bug that we need to fix.

from json.

ArtemGr avatar ArtemGr commented on August 15, 2024

That behavior would be very surprising to me and I was not able to reproduce it. Can you share your code?

Turns out it's kind of a corner case.

I'm not serializing to a String, instead I have an untyped JSON structure (a part of technical debt) that I work with as simply BTreeMap<String, Json> and I'm using typed structs for parts of this bigger untyped structure. In other words, I'm using from_value and to_value.

Here's some code:

use serde_json as json;
use serde_json::Value as Json;
use std::collections::BTreeMap;

#[derive(Serialize, Deserialize)]
pub struct Bar (pub BTreeMap<String, i32>);

fn main() {
  let mut map = BTreeMap::new();
  map.insert (String::from ("foo"), 123);
  let bar = Bar (map);
  println! ("json: {}", json::to_string (&bar) .unwrap());
  let mut outer: BTreeMap<String, Json> = BTreeMap::new();
  outer.insert (String::from ("bar"), json::to_value (&bar));
  println! ("outer: {}", json::to_string (&outer) .unwrap());

With Serde 0.7.0 it prints

json: {"foo":123}
outer: {"bar":[{"foo":123}]}

I'm glad to see that the first line printed is indeed a transparent newtype.

My use case is the second one though and here Serde seems to wrap the inner type in array.

from json.

dtolnay avatar dtolnay commented on August 15, 2024

Another data point: Go allows integer keys:

package main

import (
	"fmt"
	"encoding/json"
)

func main() {
	m := map[int]string{1: "s"}
	j, err := json.Marshal(m)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(j)) // {"1":"s"}
}

from json.

arthurprs avatar arthurprs commented on August 15, 2024

I think we can close this now 😃

from json.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.