Skip to content

encode/decode nullary constructor as string (issue #66) #68

Closed
wants to merge 3 commits into from

4 participants

@yihuang
yihuang commented Feb 10, 2012

Please go to issue #66(#66) to see more information, don't know how to reference pull request to issues.

@basvandijk
Collaborator

That's a coincidence, I'm currently working on the exact same issue and a very related issue.

(sorry for the long post)

First of all I agree that nullary constructors of a sum type should be encoded to just a string. However I believe this should only happen when all the constructors are nullary. So if we have your Color type for example:

data Color = Red | Black | White

Red will be encoded as "Red".

However if some of the constructors have a higher arity all constructors should be encoded in the same way. The reason for this is that I would like to have a simple way for a JavaScript program to determine the constructor of a value.

The encoding for sum types I would like to propose is as follows:

  • A nullary constructor C is encoded as the string "C" if all the constructors are nullary. (your case above) In JavaScript, assuming x is the decoded JSON, testing for the constructor is simply:
if (x == "Red") {...} else if (x == "Black") {...} else if (x == "White") {...} else
  • If the type has constructors with higher arity all constructors will be encoded to a 2-element array. The first element will be a string with the constructor name and the second element will be the argument or arguments of the constructor.

    • A nullary constructor like C will be encoded as ["C"].
    • An unary constructor like C T will be encoded as ["C", <T>] where <T> is the encoding of T.
    • A constructor with 2 or more arguments like C T1 T2 ... Tn will be encoded as ["C", [<T1>, <T2> ... <Tn>]].
    • A constructor which is a record like C {...} will be encoded as ["C", <{...}>].

For example if we have the following sum type:

data Mix = Nullary
         | Unary Int
         | Normal String String
         | Record {field1 :: Int, field2 :: Int}
  • Nullary will be encoded as: ["Nullary"]
  • Unary 1 will be encoded as ["Unary", 1]
  • Normal "Hello" "World" will be encoded as ["Normal", ["Hello", "World"]]
  • Record {field1 = 1, field2 = 2} will be encoded as ["Record", {"field1" : 1, "field2" : 2}]

The advantage of this encoding is that a JavaScript program can simply determine the constructor by always testing the first element of the array. So if x is the decoded JSON document:

function constructor(x) {return x[0]}

if (constructor(x) == "Nullary")
{
 ...
}
else if (constructor(x) == "Unary")
{
 var theInt = x[1];
 ...
}
else if (constructor(x) == "Normal")
{
 var strs = x[1];
 var firstStr  = strs[0];
 var secondStr = strs[1];
 ...
}
else if (constructor(x) == "Record")
{
 var rec = x[1];
 var field1 = rec.field1;
 var field2 = rec.field2;
}

The differences between this encoding and the current encoding are:

  • The special rule for all nullary constructors.

  • Encoding a constructor to a 2-element array instead of an object like {"C" : ...}.

I was already working on a patch for this when I received your patch. I will see if I can integrate your patch with mine. Note that we have to change both the template haskell code, the SYB generics code and the new GHC generics code. The changes should not be big however.

@basvandijk basvandijk was assigned Feb 11, 2012
@yihuang
yihuang commented Feb 11, 2012

Thanks for your long reply, your idea is also reasonable for me, and I' glad that you can implement it for me ;-)

@nponeccop

I'd prefer to see { "Nullary" : null } if some constructors are non-nullary. Or [] if someone wants to save chars. Think of

{
   "Foo" : {"bar" : 5, "baz" : 6 }
}

I think it's better than

[
    "Foo", {"bar" : 5, "baz" : 6 }
]

For example, I can get the tail more readably as x["Foo"] instead of x[1]. Constructor testing can be if (x.Foo) and it's a common pattern in JS. Both operations don't rely on magic numbers unlike if (x[0] == "Foo") and x.slice(1).

However, constructor() becomes harder to implement (a for..in with break will be required)

@basvandijk
Collaborator

Constructor testing can be if (x.Foo)

If you test for your constructor like that you can't distinguish between the absence of that constructor or the existence of that constructor with a False field:

> Boolean({}.Foo)
false

> Boolean({"Foo" : false}.Foo)
false
@nponeccop

There are also cases of null, undefined, "" and 0 which are all false.

There is always hard artillery of

> Boolean({Foo : false}.hasOwnProperty('Foo'))
true

But for most code this distinction between a false property and no property doesn't matter.

@bos
Owner
bos commented Jun 16, 2012

@basvandijk, what's the status of your WIP patch for this?

@basvandijk
Collaborator

@bos I think I can make some time for this next week.

@bos
Owner
bos commented Dec 7, 2012

I'm going to release 0.6.1.0 shortly without a fix for this :-(

@basvandijk basvandijk closed this in 2dff7be Jan 2, 2013
@tolysz tolysz pushed a commit to tolysz/aeson that referenced this pull request May 18, 2015
@basvandijk basvandijk Add support for specifying how to encode datatypes in Data.Aeson.TH
Fixes #68 and fixes #66.
6fbb0c7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.