w3c / activitystreams

Activity Streams 2.0
https://www.w3.org/TR/activitystreams-core/
Other
284 stars 60 forks source link

Stylistic Consistency For Non-Functional Single-Value JSON in Examples #442

Closed cjslep closed 6 years ago

cjslep commented 6 years ago

Please Indicate One:

Please Describe the Issue:

(I am splitting my email to the public-socialweb mailing list into separate issues, for background please see those emails).

This is ActivityStream vocabulary specific: https://www.w3.org/TR/activitystreams-vocabulary

Based on examples, it seems like certain properties are expected to be serialized as JSON arrays even when containing only one element. These include the properties:

I realize these are non-functional property types and therefore could have multiple values. But since these examples deviate from others, they may lead to confusion where an implementor mistakenly believe there is an implication to be had, when in reality it makes no matter if the property value is a single value or an array with a single value. So this just seems to be a stylistic inconsistency, as some examples of other non-functional properties that are never shown as JSON arrays with a single value:

This also brings into question the use of an array with the items property in Example 104.

The reason this is important to me is that one of the tests I conduct goes from example, to deserialized concrete type, and then back to serialized JSON-LD form. I would prefer to be honest and keep my examples in sync with the spec when I claim compliance; but having certain properties be arrays-with-one-value seems like an unnecessary complication.

gobengo commented 6 years ago

but having certain properties be arrays-with-one-value seems like an unnecessary complication.

Write a nice 'formatting' function that will walk a JSON object as a tree, unwrapping any 1-length-array values. Use that when rendering.

I for one think it's good to have 'inconsistent' examples like this as a way of showing all the variations, instead of for each example showing all the variations. It would add verbosity.

cjslep commented 6 years ago

I'm not rendering anything. Everything I write is library code in golang. The method you suggest may work and be trivial in Javascript or some other dynamically-typed language. However, it's not trivial in golang. Here is one way it could be done to read a single value of "to" that is either an array of one string or just a string:

b := "{\"to\":[\"test\"]}"
var m map[string]interface{}
err := json.Unmarshal(b, &m)
if err != nil {
  return err
}
if v, ok := m["to"].([]interface{}); ok {
  for _, elem := range v {
    if s, ok := elem.(string); ok {
      fmt.Printf("Got: %s\n", s)
    } else /* all other types it could be */ {
      // ...
    }
  }
} else if s, ok := m["to"].(string); ok {
  fmt.Printf("Got: %s\n", s)
} else /* all other types it could be */ {
  //...
}

(Available for playing with at: https://play.golang.org/p/x1-HCTwlMwe)

So to me, the cost of implying anything about array-of-one vs just-one-value is high. I value stylistic consistency within a spec, so implementors can easily break the problem down with respect to their tools and technology of choice. I do not mind being provided a library of test data that has the more varied examples.

I realize this is a nitpicky stylistic thing to bring up, but I want others to be aware of how these subtle differences can come across outside the non-Javascript world.

gobengo commented 6 years ago

@cjslep I am using a non-JavaScript typed language, but I chose one with generics.

These are a bit sloppy right now because iteration, but you can see how my Activity type uses OneOrMore<InnerType> to allow one InnerType object, or an array of them. Given that the spec now talks about 'Functional' properties that can only have one value, it migh make more sense to label these like ASFunctionalValue<InnerType>

type ISO8601 = string
type xsdAnyUri = string

type OneOrMore<T> = T | T[]

// ASLinked Data
type LDIdentifier = xsdAnyUri
export type LDValue<T> = (LDIdentifier | T)
export type LDValues<T> = T | T[]
export type LDObject<T> = {
    [P in keyof T]?: LDValues<T[P]>;
}
type JSONLDContext = OneOrMore<string | {
    '@vocab'?: string
    '@language'?: string
    [key:string]: string | {[key:string]: string}
}>
export class JSONLD {
  '@id': string
}

class ASBase {
  '@context'?: JSONLDContext
}

// @TODO (bengo): enumerate known values?
type LinkRelation = string

export class ASLink {
  type: ASObjectType<'Link'>
  href: string
  mediaType?: string
  rel?: LinkRelation
}
export const Link = ASLink

export const isASLink = (obj: any): obj is ASLink => {
  return obj.type === 'Link'
}

// @TODO (bengo)
type RdfLangString = string
type NaturalLanguageValue = {
    // @TODO (bengo) this could be more specific about keys than just string
    [key: string]: string
}

type ASObjectType<T> = T | [T]
export type ASValue = string | ASObject | ASLink
// W3C ActivityStreams 2.0
export class ASObject extends ASBase {
  attachment?: OneOrMore<ASObject|ASLink>
  attributedTo?: LDValue<ASObject>
  bcc?: LDValue<ASObject>
  cc?: OneOrMore<LDValue<ASObject>>
  content?: string
  generator?: LDValue<ASObject>
  id?: string
  image?: OneOrMore<string|ASLink|ASImage>
  inReplyTo?: LDValue<ASObject>
  location?: ASObject
  name?: string
  nameMap?: NaturalLanguageValue
  preview?: ASValue
  published?: ISO8601
  replies?: LDValue<Collection<ASObject>>
  summary?: string|RdfLangString
  tag?: ASObject|ASLink
  to?: LDValue<ASObject>
  bto?: LDValue<ASObject>
  type?: ASObjectType<string>
  url?: OneOrMore<ASValue>
}

export const isASObject = (obj: any): obj is ASObject => {
  return typeof obj === 'object'
}

I realize this is a nitpicky stylistic thing to bring up, but I want others to be aware of how these subtle differences can come across outside the non-Javascript world.

I feel for you. Not saying this will be easy in Go (or C++, or Cobol, or awk). Just doing a run of open issues. Given that AS2 is now a W3C Rec and the WG is closed-ish, when should we close this issue?

cjslep commented 6 years ago

I see, closing.